From bc4ff29956a2f440692714e875bf58ba7334d02a Mon Sep 17 00:00:00 2001 From: Juan Manuel Leflet Estrada Date: Mon, 3 Jun 2024 15:16:28 +0200 Subject: [PATCH] :sparkles: Gradle support (#609) :eyes: See [Gradle support enhancement](https://github.com/konveyor/enhancements/blob/master/enhancements/gradle-support/README.md) (https://github.com/konveyor/analyzer-lsp/issues/598) Adds support for fetching dependencies with Gradle projects. (https://github.com/konveyor/analyzer-lsp/issues/599) Adds support for downloading dependency sources. - This implementation assumes that there is a Gradle wrapper included in the project, otherwise it is not possible to know which Gradle version is needed. - To maximize compatibility with most Gradle versions, JDK8 must be used. There are a number of ways to specify this on newer Gradle versions, but for older versions the only thing that seems to work is setting `JAVA_HOME`. :exclamation: This PR supersedes https://github.com/konveyor/analyzer-lsp/pull/591 :green_circle: Tested against https://github.com/andyjduncan/gradle-example and https://github.com/kissaten/gradle-multi-project-example --------- Signed-off-by: Juan Manuel Leflet Estrada --- debug/debug.Dockerfile | 38 ++- demo-dep-output.yaml | 132 ++++++++++ demo-output.yaml | 31 +++ examples/golang/go.sum | 1 + .../java-external-provider/Dockerfile | 2 +- .../gradle-multi-project-example/.gitignore | 105 ++++++++ .../gradle-multi-project-example/LICENSE | 21 ++ .../gradle-multi-project-example/Procfile | 2 + .../gradle-multi-project-example/README.md | 53 ++++ .../gradle-multi-project-example/build.gradle | 47 ++++ .../codequality/checkstyle.xml | 238 +++++++++++++++++ .../gradle/check.gradle | 15 ++ .../gradle/heroku/clean.gradle | 5 + .../gradle/heroku/stage.gradle | 8 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54727 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + .../gradle-multi-project-example/gradlew | 172 +++++++++++++ .../gradle-multi-project-example/gradlew.bat | 84 ++++++ .../settings.gradle | 4 + .../template-core/build.gradle | 10 + .../java/io/jeffchao/template/core/Core.java | 8 + .../io/jeffchao/template/core/CoreTest.java | 16 ++ .../template-server/build.gradle | 10 + .../io/jeffchao/template/server/Server.java | 32 +++ .../jeffchao/template/server/ServerTest.java | 16 ++ .../java-external-provider/go.mod | 12 +- .../java-external-provider/go.sum | 8 +- .../pkg/java_external_provider/dependency.go | 227 +++++++++++++++++ .../java_external_provider/dependency_test.go | 235 +++++++++++++++++ .../pkg/java_external_provider/filter.go | 53 +++- .../pkg/java_external_provider/provider.go | 240 +++++++++++++++++- .../java_external_provider/service_client.go | 10 + .../pkg/java_external_provider/util.go | 33 ++- provider_container_settings.json | 10 + provider_local_external_images.json | 10 + provider_pod_local_settings.json | 10 + rule-example.yaml | 10 +- 37 files changed, 1867 insertions(+), 47 deletions(-) create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/.gitignore create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/LICENSE create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/Procfile create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/README.md create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/build.gradle create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/codequality/checkstyle.xml create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/check.gradle create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/heroku/clean.gradle create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/heroku/stage.gradle create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/wrapper/gradle-wrapper.jar create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/wrapper/gradle-wrapper.properties create mode 100755 external-providers/java-external-provider/examples/gradle-multi-project-example/gradlew create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/gradlew.bat create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/settings.gradle create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/build.gradle create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/src/main/java/io/jeffchao/template/core/Core.java create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/src/test/java/io/jeffchao/template/core/CoreTest.java create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/build.gradle create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/src/main/java/io/jeffchao/template/server/Server.java create mode 100644 external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/src/test/java/io/jeffchao/template/server/ServerTest.java diff --git a/debug/debug.Dockerfile b/debug/debug.Dockerfile index 0870c3d7..169c2735 100644 --- a/debug/debug.Dockerfile +++ b/debug/debug.Dockerfile @@ -1,22 +1,32 @@ FROM golang:1.19 as builder WORKDIR /analyzer-lsp -COPY ../cmd /analyzer-lsp/cmd -COPY ../engine /analyzer-lsp/engine -COPY ../output /analyzer-lsp/output -COPY ../jsonrpc2 /analyzer-lsp/jsonrpc2 -COPY ../lsp /analyzer-lsp/lsp -COPY ../parser /analyzer-lsp/parser -COPY ../provider /analyzer-lsp/provider -COPY ../tracing /analyzer-lsp/tracing -COPY ../external-providers /analyzer-lsp/external-providers -COPY ../go.mod /analyzer-lsp/go.mod -COPY ../go.sum /analyzer-lsp/go.sum -COPY ../Makefile /analyzer-lsp/Makefile - -# Install delve (go debugger) +COPY ../cmd /analyzer-lsp/cmd +COPY ../engine /analyzer-lsp/engine +COPY ../event /analyzer-lsp/event +COPY ../output /analyzer-lsp/output +COPY ../jsonrpc2 /analyzer-lsp/jsonrpc2 +COPY ../jsonrpc2_v2 /analyzer-lsp/jsonrpc2_v2 +COPY ../lsp /analyzer-lsp/lsp +COPY ../parser /analyzer-lsp/parser +COPY ../provider /analyzer-lsp/provider +COPY ../tracing /analyzer-lsp/tracing +COPY ../external-providers /analyzer-lsp/external-providers +COPY ../go.mod /analyzer-lsp/go.mod +COPY ../go.sum /analyzer-lsp/go.sum +COPY ../Makefile /analyzer-lsp/Makefile + RUN go install github.com/go-delve/delve/cmd/dlv@latest +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest as yq-builder +RUN microdnf install -y wget tar xz gzip && \ + microdnf clean all +ARG TARGETARCH +ARG YQ_VERSION="v4.40.5" +ARG YQ_BINARY="yq_linux_${TARGETARCH}" +RUN wget "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}.tar.gz" -O - | tar xz && \ + mv ${YQ_BINARY} /usr/bin/yq + RUN go build -gcflags="all=-N -l" -o konveyor-analyzer ./cmd/analyzer/main.go RUN go build -gcflags="all=-N -l" -o konveyor-analyzer-dep ./cmd/dep/main.go RUN cd external-providers/generic-external-provider && go mod edit -replace=github.com/konveyor/analyzer-lsp=../../ && go build -gcflags="all=-N -l" -o generic-external-provider main.go diff --git a/demo-dep-output.yaml b/demo-dep-output.yaml index 7cbd4b12..656f10dd 100644 --- a/demo-dep-output.yaml +++ b/demo-dep-output.yaml @@ -1494,6 +1494,138 @@ - konveyor.io/dep-source=open-source - konveyor.io/language=java prefix: file:///root/.m2/repository/org/yaml/snakeyaml/1.28 +- fileURI: file:///analyzer-lsp/examples/gradle-multi-project-example/build.gradle + provider: java + dependencies: + - name: antlr.antlr + version: 2.7.7 + indirect: true + - name: com.apple.AppleJavaExtensions + version: "1.4" + indirect: true + - name: com.beust.jcommander + version: "1.48" + indirect: true + - name: com.google.code.findbugs.bcel-findbugs + version: "6.0" + indirect: true + - name: com.google.code.findbugs.findbugs + version: 3.0.1 + - name: com.google.code.findbugs.jFormatString + version: 2.0.1 + indirect: true + - name: com.google.code.findbugs.jsr305 + version: 1.3.9 + indirect: true + - name: com.google.code.findbugs.jsr305 + version: 2.0.1 + indirect: true + - name: com.google.code.gson.gson + version: "2.5" + indirect: true + - name: com.google.errorprone.error_prone_annotations + version: 2.0.18 + indirect: true + - name: com.google.guava.guava + version: "23.0" + - name: com.google.guava.guava + version: 23.2-jre + indirect: true + - name: com.google.j2objc.j2objc-annotations + version: "1.1" + indirect: true + - name: com.puppycrawl.tools.checkstyle + version: "8.4" + - name: commons-beanutils.commons-beanutils + version: 1.9.3 + indirect: true + - name: commons-cli.commons-cli + version: "1.4" + indirect: true + - name: commons-collections.commons-collections + version: 3.2.2 + indirect: true + - name: commons-io.commons-io + version: "2.4" + indirect: true + - name: commons-lang.commons-lang + version: "2.6" + indirect: true + - name: dom4j.dom4j + version: 1.6.1 + indirect: true + - name: jaxen.jaxen + version: 1.1.6 + indirect: true + - name: junit.junit + version: "4.12" + - name: net.bytebuddy.byte-buddy + version: 1.7.4 + indirect: true + - name: net.bytebuddy.byte-buddy-agent + version: 1.7.4 + indirect: true + - name: net.java.dev.javacc.javacc + version: "5.0" + indirect: true + - name: net.jcip.jcip-annotations + version: "1.0" + indirect: true + - name: net.sf.saxon.Saxon-HE + version: 9.8.0-5 + indirect: true + - name: net.sourceforge.pmd.pmd-core + version: 5.6.1 + indirect: true + - name: net.sourceforge.pmd.pmd-java + version: 5.6.1 + - name: net.sourceforge.saxon.saxon + version: 9.1.0.8 + indirect: true + - name: org.antlr.antlr4-runtime + version: "4.7" + indirect: true + - name: org.apache.commons.commons-lang3 + version: "3.4" + indirect: true + - name: org.apache.logging.log4j.log4j-api + version: 2.9.1 + - name: org.apache.logging.log4j.log4j-core + version: 2.9.1 + - name: org.apache.logging.log4j.log4j-slf4j-impl + version: 2.9.1 + - name: org.codehaus.mojo.animal-sniffer-annotations + version: "1.14" + indirect: true + - name: org.hamcrest.hamcrest-core + version: "1.3" + indirect: true + - name: org.mockito.mockito-core + version: 2.11.0 + - name: org.objenesis.objenesis + version: "2.6" + indirect: true + - name: org.ow2.asm.asm + version: 5.0.2 + indirect: true + - name: org.ow2.asm.asm + version: 5.0.4 + indirect: true + - name: org.ow2.asm.asm-commons + version: 5.0.2 + indirect: true + - name: org.ow2.asm.asm-debug-all + version: 5.0.2 + indirect: true + - name: org.ow2.asm.asm-tree + version: 5.0.2 + indirect: true + - name: org.slf4j.slf4j-api + version: 1.7.25 + indirect: true + - name: xml-apis.xml-apis + version: 1.0.b2 + indirect: true - fileURI: file:///analyzer-lsp/examples/inclusion-tests/pom.xml provider: java dependencies: [] diff --git a/demo-output.yaml b/demo-output.yaml index c04b5877..80c1a0db 100644 --- a/demo-output.yaml +++ b/demo-output.yaml @@ -354,6 +354,30 @@ variables: name: sigs.k8s.io/structured-merge-diff/v4 version: v4.2.1 + java-gradle-project: + description: | + This rule looks for a class only present in the gradle project + category: mandatory + incidents: + - uri: file:///examples/gradle-multi-project-example/template-server/src/main/java/io/jeffchao/template/server/Server.java + message: Only incidents in gradle project should appear + codeSnip: " 1 package io.jeffchao.template.server;\n 2 \n 3 import java.io.IOException;\n 4 import java.io.OutputStream;\n 5 import java.net.InetSocketAddress;\n 6 \n 7 import com.sun.net.httpserver.HttpExchange;\n 8 import com.sun.net.httpserver.HttpHandler;\n 9 import com.sun.net.httpserver.HttpServer;\n10 \n11 public class Server {\n12 \n13 public static void main(String[] args) throws IOException {\n14 String portString = System.getenv(\"PORT\");\n15 int port = portString == null ? 8080 : Integer.valueOf(portString);\n16 HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);\n17 server.createContext(\"/\", new MyHandler());" + lineNumber: 7 + variables: + file: file:///examples/gradle-multi-project-example/template-server/src/main/java/io/jeffchao/template/server/Server.java + kind: Module + name: com.sun.net.httpserver.HttpExchange + package: io.jeffchao.template.server + - uri: file:///examples/gradle-multi-project-example/template-server/src/main/java/io/jeffchao/template/server/Server.java + message: Only incidents in gradle project should appear + codeSnip: "14 String portString = System.getenv(\"PORT\");\n15 int port = portString == null ? 8080 : Integer.valueOf(portString);\n16 HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);\n17 server.createContext(\"/\", new MyHandler());\n18 server.setExecutor(null); // creates a default executor\n19 server.start();\n20 }\n21 \n22 static class MyHandler implements HttpHandler {\n23 @Override\n24 public void handle(HttpExchange t) throws IOException {\n25 String response = \"Hello from Gradle!\";\n26 t.sendResponseHeaders(200, response.length());\n27 OutputStream os = t.getResponseBody();\n28 os.write(response.getBytes());\n29 os.close();\n30 }\n31 }\n32 }\n" + lineNumber: 24 + variables: + file: file:///examples/gradle-multi-project-example/template-server/src/main/java/io/jeffchao/template/server/Server.java + kind: Method + name: handle + package: io.jeffchao.template.server + effort: 3 java-inclusion-test: description: "This rule tests includedPaths config of the java provider. There should be two instances of this issue in the example app. \nWe are filtering one of them using includedPaths in provider config.\n" category: mandatory @@ -381,6 +405,13 @@ description: "" category: potential incidents: + - uri: file:///examples/gradle-multi-project-example/build.gradle + message: dependency junit.junit with 4.12 is bad and you should feel bad for using it + codeSnip: " 1 apply plugin: 'idea'\n 2 \n 3 ext {\n 4 log4jVersion = '2.9.1'\n 5 }\n 6 \n 7 buildscript {\n 8 repositories {\n 9 jcenter()\n10 }\n11 dependencies {" + lineNumber: 0 + variables: + name: junit.junit + version: "4.12" - uri: file:///examples/java/pom.xml message: dependency io.fabric8.kubernetes-client with 6.0.0 is bad and you should feel bad for using it codeSnip: "26 \n27 \n28 \n29 junit\n30 junit\n31 4.11\n32 test\n33 \n34 \n35 io.fabric8\n36 kubernetes-client\n37 6.0.0\n38 \n39 \n40 io.fabric8\n41 kubernetes-client-api\n42 6.0.0\n43 \n44 \n45 javax\n46 javaee-api" diff --git a/examples/golang/go.sum b/examples/golang/go.sum index 16ccc8cf..b5a4d86b 100644 --- a/examples/golang/go.sum +++ b/examples/golang/go.sum @@ -125,6 +125,7 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= diff --git a/external-providers/java-external-provider/Dockerfile b/external-providers/java-external-provider/Dockerfile index e2661f72..a9b27b51 100644 --- a/external-providers/java-external-provider/Dockerfile +++ b/external-providers/java-external-provider/Dockerfile @@ -22,4 +22,4 @@ COPY --from=builder /java-provider/java-external-provider /usr/local/bin/java-ex EXPOSE 14651 -ENTRYPOINT ["java-external-provider", "--port", "14651"] +ENTRYPOINT ["java-external-provider", "--port", "14651"] \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/.gitignore b/external-providers/java-external-provider/examples/gradle-multi-project-example/.gitignore new file mode 100644 index 00000000..0c740ded --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/.gitignore @@ -0,0 +1,105 @@ +### Intellij+iml ### +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Intellij+iml Patch ### +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +.idea/ + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Gradle ### +.gradle +**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/LICENSE b/external-providers/java-external-provider/examples/gradle-multi-project-example/LICENSE new file mode 100644 index 00000000..25408ddc --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [author] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/Procfile b/external-providers/java-external-provider/examples/gradle-multi-project-example/Procfile new file mode 100644 index 00000000..40dc36e6 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/Procfile @@ -0,0 +1,2 @@ +web: java -jar build/libs/template-server-all.jar +worker: java -jar build/libs/template-core-all.jar diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/README.md b/external-providers/java-external-provider/examples/gradle-multi-project-example/README.md new file mode 100644 index 00000000..64e41321 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/README.md @@ -0,0 +1,53 @@ +# gradle-multi-project-example + +Basic gradle template with subprojects, deployable to Heroku as separate dyno processes. + +## What's included? + +1. Gradle Plugins + - application plugin + - shadowjar plugin +2. Code Style + - checkstyle + - findbugs + - pmd +3. General Libraries + - guava + - junit + - mockito + - log4j2 via slf4j +4. Multi-Project Gradle Setup + - see: [settings.gradle](settings.gradle) +5. Heroku Deployment + - see: [Procfile](Procfile), [stage.gradle](gradle/heroku/stage.gradle) + +## Development + +### Building + +``` +$ ./gradlew clean build +``` + +### Testing + +``` +$ ./gradlew clean test +``` + +### Building Deployment Artifacts + +``` +$ ./gradlew clean stage +``` + +### Running + +Use the Gradle [application plugin](https://docs.gradle.org/current/userguide/application_plugin.html). +However, `./gradlew run` will run applications in lexicographical order. +Instead, explicitly specify which subproject to run: + +``` +$ ./gradlew template-core:run +$ ./gradlew template-server:run +``` diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/build.gradle b/external-providers/java-external-provider/examples/gradle-multi-project-example/build.gradle new file mode 100644 index 00000000..d81723d2 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'idea' + +ext { + log4jVersion = '2.9.1' +} + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + mavenCentral() // maven { url: 'http://jcenter.bintray.com' } + } +} + +apply from: file('gradle/check.gradle') +apply from: file('gradle/heroku/clean.gradle') + +subprojects { + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'java' + + group = "io.jeffchao.${rootProject.name}" + + dependencies { + implementation 'com.google.guava:guava:23.0' + + testImplementation 'junit:junit:4.12' + + compile "org.apache.logging.log4j:log4j-api:$log4jVersion" + compile "org.apache.logging.log4j:log4j-core:$log4jVersion" + compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion" + + testCompile 'org.mockito:mockito-core:2.11.0' + + } + + apply from: file("$rootProject.projectDir/gradle/heroku/stage.gradle") + +} \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/codequality/checkstyle.xml b/external-providers/java-external-provider/examples/gradle-multi-project-example/codequality/checkstyle.xml new file mode 100644 index 00000000..2d6336e9 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/codequality/checkstyle.xml @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/check.gradle b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/check.gradle new file mode 100644 index 00000000..19af4208 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/check.gradle @@ -0,0 +1,15 @@ +subprojects { + apply plugin: 'checkstyle' + checkstyle { + ignoreFailures = true + configFile = rootProject.file('codequality/checkstyle.xml') + toolVersion = '8.4' + } + + apply plugin: 'findbugs' + findbugs { + ignoreFailures = true + } + + apply plugin: 'pmd' +} diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/heroku/clean.gradle b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/heroku/clean.gradle new file mode 100644 index 00000000..67329835 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/heroku/clean.gradle @@ -0,0 +1,5 @@ +apply plugin: 'base' + +clean.doLast { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/heroku/stage.gradle b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/heroku/stage.gradle new file mode 100644 index 00000000..819e4e83 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/heroku/stage.gradle @@ -0,0 +1,8 @@ +task stage(dependsOn: ['clean', 'shadowJar']) + +task copyToLib(type: Copy) { + from "$buildDir/libs" + into "$rootProject.buildDir/libs" +} +copyToLib.dependsOn(shadowJar) +stage.dependsOn(copyToLib) \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/wrapper/gradle-wrapper.jar b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..27768f1bbac3ce2d055b20d521f12da78d331e8e GIT binary patch literal 54727 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

i| z%T)<(DrJc1>wW;MNR}y;M~6yB{yhXKDv>S?J88p`<;lWue;~<$7r+fd+b$ElDFfmp zuxZm|(^5f-+7N1B^6OoL|3gT(8asxym{_)bkETNKpcK>hX4TG+6 z%%ATBdi;I=+oa}i2fduW{kKf)e@gWPMe_e;ttb3t)}R69e9#(dDL5sE3@qG()bCtO zZ4M~@U`xa08-l2))oROg$BSpOdG_H7I1C>GE+`auY-Q89ZC#O4JuJN@p?zsNL1vD# z=0tQA_su*Nz)(Fq?cP{OATS9mtVt{`|A`VIu&{gNmWaR?>Y`CMk?0tWLvRu+Ag&#@ zSGbc$RPZGxe##EyX?hH@1sLfGitds98ubqIK%MIOH>5KJipH)3e%)+Bo4KB-@6rIiq34E3w+UMLjO zz3waN8u44BvF+stgcx&j4O;D5vq$G{7%VT_Nm1CcS{S%q>>IUYMG^v%XvPbj9o9%_ z*S`Uv&rEN3GW#Ob2X@@i4kROK1EBphCh0>lyvBwp<0-3Bq8ZWwvTz~1Gvc=e%X~!< zN$E-SG+<~Uv+BNYXtNZB@yj_78|=&zUrt zs*&!}_(xuqYV}GHI_nfgXQG|$;ZyarmyBN+?c+f6n7iAPhi5v}p>N;_YvPP$ReEky zS_v|krw;`>$YtkK%mZ!J-1?BCF_hyGSSN`eY=lEh^pPzl!gpZCh!CR>rFBB&!xz*w zl+-_a`xQ|3nd(&Q+3)q`GyD3AUkx_)55chWOmiKWUFzCJPa8I5yx8fMD$50S^X`%F zIlIORRDGQ>@G{j{W{%g4A18#CC~Hek3s!#%`7t?QGelEN{ynO;l%m;tf!@G4tYPI* z$cg|&v+WO>oS5yD0g#Gfb~J(IQH!o1dS56ZGIF4a``uIG5#_ukE<*Rv-X%UV19Si% zivf7Dj^tzQYf%koBwRF;us>Y8f8#;u!tu=J|5e6={V!96e}4k~$G`F)Wv9bG{*4uh z*0OWoOB`QKSZBweSmdEoQ2u;S3AuTp^zxqIBSJ`yVeRxTmN*NQ%r3$=M9CW$AMrK!d3xXg*jq*>D;s)2g?!blNfvB5)YH$=GJ;+jp#elS(A$ zIMoEE73+I-t}}@!YCnuKZr)vL(LCslbvKd%)0BxI@HsNpix~O^IP_G|dg#`u=Hymp z9B+Xei5-DKNSeR_>Z648Nfp$^V<8Ef7FT>g z8I-OZXD20opU9c3t?p<|cKF{cU+;HJAlFeRDtQVM-?{MgO9{?@(< z&Y&KierF=jZzB<||KIlYpP5L&*yNY}x2MRzO-0skinmYby2<`#^WR;)^Wa(Q#VdME1xl1d&mOMEX$a=!{7CMz&k*CXCmMs|R<-VEvz_8i`5+3NB?DrCJM$ z>UAoLQ5zXHW=+avmFgG*w5P!~wDje&?tQwVY=;{xS|%3h{G(}Yn0*-f%NFwzX-=Zl z$|H!Qsm2Yh6&kH6tWj|}WAHjNm+483e>9!irpcMT7|5}LbJbT$HL5Iu)9;8eE>1&b zFv;=w+Ct~tP=opB$d^lvkMLGn&22p=>Gq>H)auRRt1?H{fgZq^m6f9;O7%2b?RFW$!OG)Q@hbX;;ZONoi18F9TVCILaCBUSSngXiVwu2nXlXX?}O zQdmsO{uG!qE=WZkq1+*~nG_}+JVTm;?g@AwPsTMfPT%7Mp_CvrNZlztie-smo3?!d z*-1X_OJpqr>wvcxq~TSezM#v^M>Uc4?m5wMD>|zQ+w(_f@t_pw(4kbPPHu4L=3o^} zKKs^Qb{maasD7|GO?nkWo)QG5G(wp$X1|4 zo!BJjzG~@Ml?JM7Du9xqQ1>V9=bg6eCZ|q`N&~FR3f1n%9jNj0-qQ95+;dmIbVffF z;e8I|9A_j*KwkUYFkE)=j7`SD~wm9sUELqARwsO z(0j7k%9nXr@C!kjL37Md1Rb~5m>z@U`g_hvB$Buv3`GTII+ZX5wWI2CIP02=R zcw+Tdw`Q&iv3UnkYUdo4F55oTOgehBS;#(m$x}vC$B`MyNSZyvBM&V*PpyHF`KsT} zrYG#4SwH1Zhe&R;boerK1}IJuuzg)=ObKxNpqho7=^nDhc|4^*SmWOD{uP+qPi89Q z_|BV)-xCy(|H~O7sPAAbZsTBV<6!RiZBV56yVGo}|IvKd4QU6>I0@!LD7O?SbU9XFbnHQH&!R ztVoc7e)!ArOl}90$@B9kJl#$}v+aK0=s3Sf4h7e|=pqhS<>vDI()>U9lfP}mRfDaA zg<9+3!qp{4`TS^n;~Xg>jU19)tVlJYFcP zLPzheLJLu%QExXjl4!LQcAvrURslmz7&Q*lX!6$^gO6c0l)sRQ1*O@!UWB58(UbL% z7Ut`uTNyyret`3p)8Kpi2)Xe_Pz|rlh!-0%Vi%JM*%{O+kdV2?zZYxin1KG6h?Fn5 znT(gVTO;R7fUJ|q=Hp7O_jwWDRuv!0Xx*_R-UHb$tg;rI-gk+(KPC@4+#$OCKHW&! z2T`Y1nNTUCnFNUVRB#RDcF*ee4)h5O80HzwEH;f2(aNRdbmc>R8JGRn=;TE+`x^SL z=t903fZYF==#;eiHgEq&Rrimar|78fX#9`*ZbQw|75M3V{^f?z%@smS_OeHSTER>rl|72xv$3C)WQooN;oj~eh*cRvY4f%bWw>b!@= zJlU^Dw^uH&*RAXdZc`KIZ++P6Fy6PL^zU`J^-hPk$;*MSEFS!LEUJRXnzivmGjJ`MB^n0&@Z@357b^WgPz}nyCdSjlS+3xiScjp3 z@qsJJLAlmd=BLiG0uI<42xb>`=dp_jnh|98i)y`Q7d3-}OpKeRDX-oW&W>%Q={_NR zEmi#6r(@NxTteCi>7uB5H%k3==wXH9cFd~Dw&BfQNTBEh(+ca0KiyfJv?L3jlM=l` z8tf{V4=}?PdHU>5tOk7P4J>R%Nqd-~qFr9!0!^apL7y%S>@JIU=IgaT4?97mI_BtL znk2UcyzFj`MRJ>uA~@aMCauts!5`G@ZWmFcX0tIl3)aBu1tEHcUdvOG(C4iJo&Xs7 zHxbob;?1Q~I+fXH)^*l>d&~DVR~XtZV&_wAS^?Wm@A?+1*OeeFG2CK3?EuS`pVBTK z&qTEpsHauBtZ>Ri99?1#$2D~{wrtB>Fm|O#9Umo9lWPGR7RsuSkW^M&u+~$*vudwb5wg^nqzaxfEUL@YS$VY^4CqDkH7KBT+tpCD^*(@ zXY%E+7>Z+oCVzft2qqdEMUHr9ys#7g=O*Bx2?!3=50#3=1r8^R^;w*SdaZ?p%X#Gq zr8$f(fe$;Ly(b)w@}c3{t!;6ZD+&-inIz*Z&D?AB$%3`B}4_GhfUuQQKt+6}= z#Lt1U=FDJ3V9v$V;u58I&lnMYcwH{;qR^p1{b2c^)VT^Wt@pWT4RHmb{_6FWnNI{s zZ3K4S45-b>(7ph$%#bPmMIIWRhfm{13!@41^nxi(z6JVJ!IU-KsVL5&hN|O*1&Ygu zBB&N?YVOQrqT_=6;&|=&C5*svb?#PguF^p5R0tl?<&XX>QLdkME8}2{+0bqck-FOQ z+dD|uiJs52i(XSzt4cekBwdZNntkPwn*EFc1m$A4p8H)J2NQb0bT9v+7C3_Lsd_sc zG?b@Zu{FXLZEvZW8H_PPj}<+vI_fU)A=62pv|x|-Gs;p0LfD%NlWltAaMUPwYQvs_ zZ>LK_V96`SmL5W$?`-=w&TiN9LM$41O#}=cBaM=^wZ1VL>&1M$R*ty7B7q_CLGLnK zmSbEZNIvQFtBaG~C(ZS=@940JGHTR+^_YdiA&2Kw>hMKPp&i*yGujyvcd{RYZ9$om z!@#DeZ1n8edvrx3(GMS01Mf>OQ)PKeir8c!8^mYNXs1cJ6+2;<9DMRlg@Ehaj?Vi@ zi**mv4aF5+C6TQ|NlL{?U#XTN5buK8vQgsf z=h%8R410HG3KMAw3~pnuv-w6%Zj;qaLdpeGDW<^4}BssZrGfZwI(}w#gjGJ93LitkG+ej>PT)eR*WKj(d`z59g z)pp%V8#CsVPoJ<^@6_2YZ!Pe$CLA&GuDr(rWOJ-N4LN~TP$Jr?myz6(jm{91HALEH ze2vP@@34~d9i)=ra5=qV4CogE%Go?!>#8v#2dG!=p&?j0Ao21e$4{S{+d> z)u32&P^z!bWrE8#r|+MM-=@KLIwFGwL#q;L=asaGS9+n;h0f6v08$5>fsytkyQnq? z#B5kxU(lwv;Vm;z(i)Rs?b;7R& zF{b6y*keic#*rRz$c^2m&Q z!4XzUn4ypUVm>C_W^v*OHol3|?}{H{S(~Y0a~G~l^J`UcPtgcfp7s($_(qaav8_A> zmf-axX#{^9#b5{l%r$D4U@acMRSZFukrH{jfN6cJ%Hr%%zWZWM%z9N#*NBW2);oAO zqGM>kNgP)L_6UL^-tVSdM4=8j=nu?)VWinPn z4K#uDb;XQrM06O@aV7#5j{FYZS96d4B(pTO=#&$Tt243<&hS&1_=X=zW16xAYmDua z~4ZT}60~MTRnd=r&Tn66(T#_noy|pa&8b z8hxrF7z=ZBy*ZF1OiZBU_US5Eweo(K*vF(D7WLqAh4g;Z_zGsI7Ni78~TS5xz|9 z2eu|sz@tJTav1oD&ptLduLBA>V^1vW<#1__uvl#dfXGNb=e!v}qsR5O27~M+Nw5p6 z72;#tMz`kQ49A|TN6tXy=HZu*7<;Od`+R%|t#?=)H03UY7Y{6>OX$5sFjTQx@w(!% z)ojO8_kun9Yf3BoNBl`hD4H04f_Nu~>7%BvTtAZpty z>=OWQKoQ^#_^mnezfIp+*Us>7bL3K`BUvQC!mUoL@yMwXCDU^aTo0iU8H%Mp9}1Cy z7&d8|xx=gONFA-N>D%$_C$TfghfR1H;c#MJZ$Mm_Mx6R&lE_B-=;&~weV+5TB*R+47mj0LOs=BC`^<_EX4HrdfFmU1ZwulGRM!K%89-XN)>)7YZlizpRVHP*!!^qQOR;{NI zhj%+VY3>B$yOw;tf86cl;$6v8cGAc)vQqo*!pO6$R+vE)P#y6_b(|rXiPK77u_r5n zgt}ODqB4XfFyQTWxN$2*E%o~Cwla%26U;TVR1Fsl6WJy=Hy&of%8?}8LQRjtXe7Zi zopIp??rU_?E)_1WRqf^aZ5&u9YKu7xFxQr+wQxF@fJK^fx*^5A+TrkQhN=Z8&Ty~}qt2I;cv@o`Y6jHHvZf3^-zH_rOHsU+4Ya>jAd`r)+u{|(|BO2Xq<2-~8aTvkhI_Hux zO}jquyaN|M1S`9$%r2Aws|{w?*q@|vatQUY0-0N6*{tWE#os3Oct1*oz&V6nM)930 zMkwtKWEJQ}iwNr8jrCubg~TEy?-~FmUkWgJw%=J6{$cVjy%e97%mEI6bWhp233*QR z&8%VQU6K?7`%$XK;(;?7>+I@u@rm2czV-%0~$sgIQB%o;Wi6KmW&)z zy3@javfUhiH8=Aq9Z1rJiYS}|!|#E?+Z7U;QJ8u#M+Ou)@UrrG`vos1IIBdJQk~WOeW0-|FJefC@sM%< zAr5%lLG1Fk%5@B%0|s)GK8BVm%bQk-;O(LVmg+!b?1en#I-2kbnJ$hkU0(Dw>kqfd zyyR?!E8ob+?>lMW1f_UzGnM=E7xScGrq00TPDvWgr#6(o?nl|`nbfW`SF5k7$qKENCR1pF?&3JQxTU+#Jr->@wh``K{elj zmDG}aq%$7@tH$d7W#cAqQ^UtmmhOn^LrKT(&m%nUnTpZxLq;@PpX!X75OMP1Af zn-6umF%ZLFzyv1<+N=+KdYHfMk~R}9uyXXKR)!w7T{p^iRswv{wY03pRT?8G!W%`$Tu^r|a>Tp7}?O@t>CCCyX z)4|vtb|Ef4OE2Dr6bsFfm>*~o1iVm_2JSb>sbr#TdS^b zY*D~yvU8yh6sSgnIyKL>ls*r;i(|=eD-egBR&)UcF7F#0bu}*gGnFtXJ_X5ytDo^Z z_vBVfQM7Ji&qLZL2+Rrvtee~^(IaaEzL4A@w6M31nDOX?F=D#pGK38zA3A9d;{)`K z2~~I+LH!LFjIO*oZY6yDzQ!7OJo~^S?}&oj+(6VT>jCji6E68&Z1; z?uPYzZR-go>J;Y=SFVhUE6sm^HG>~C+_lghy^JEGe&b0hta}Ce*P&2s!ssv>FchW$ zj@dE&{!sXb+xFkWPzr!=KA_*H;A>-Rv}KD9Jbf0%pXdfZ%?xwSqY_*Mxv}3_;j%yG*%|#;n%-9h8(;CuGGa;f^P&XQ z0_`ajCli8lbqQc$4NZ$Csq<`9(zGUR-gmtYWWP>^X{h0Oiqe2{PM$T|U9_@K)NMBp zs@;kHqSxe9KS-}}$TOErVaY&jrY%HoFlV7sa#H8y{~UM1F6i`qf9dN+E6pZ(B82mi zx4`OKSS~|y_wB~cat>|?kRx^TwAJb)UTgNwBCcAcb9I_yR)bKsC3ye$?BQgu67wM5 z&kHQBr_Z^D-i4t`J^JSfmT#K7^aBOXp-sB-rWYlN98UQ%E19BVK%sRoz?^;10ujh; ztmZ#eKa1YKhmx`WaPO(rT)jQs=SDd!6&#_9&S{4p^(`ub8b(jM(8Q%gAA<@8X*oCj zWKmY=hBHk^sSj3~p&}&WAYt+}Hq(w`AEx*D4vWhz3zu;?g^%gOkO+rWb~4T$oZxX# z2N&0pA^L%RL+X&Ja!+8TwFh8P^>CAX5ze1>{3IDc^75V36v;$mMEv2V=tZ zwx%qFEqM#jRTzDU!9uF`X{*KU) z!x|MAdW6<`9lkyyE!?b?Idu{Xq;WE_=wP+6#hsRcs;w1cI*QFg1N83{%G_7XaK)c< z*=_on)X(=jzoNBHI?b8-i&5%`pQQN@+FuL4ZwL>W<3?zO;7KP?bJW^X!A1aywn=6g zvz~{2kIgw*#x+Q4p->;xI1jxJJ~_6WQaG$LOB5P1x?|qAp*SC5gXILSc6^CkguE&8 zRWiu~e$C7+An6UZtEV{o)m>1fUFrC7M`cOSwzgcRnQwdWsmGWK>3 zvM_$J75*}Rz`o)YT*o6XgqAJoN5~wEcXO^x77sha4{;?^)PZNojXm^>&!i@_*n6y< z*}J*pHX|3I9gI9Iq2D%8d8A)!vc;9?R#`+dGX~RX`g-99==;yUI@+Sh5tnlUst>o_ zUC+95@LMGk_0-9C@kuyC%{zk=wQxIAF<qw#>Mc6qyY)~G1!?uvF)2tUCj0VXw z-b%?W;{|mp4|C37aLfMf2IRXtA_;GRQrbs|QpXS{NK;Eh1%v^dC4z9I1|v@g+2fS5O39qtu~Dohn=)Uc`N*l>#u`14T^~`IKZ--0Gn@&zcYCM zZN2tcVfBab=#wl3GPHgBk|Hw_8#X=bzB?1T3~^FIq$Q*gyjv50S7WS({UXgB-|a>y zDen#VjTpw5l%5QOreF#`)1KvrP;q>S-Egh(wjN zi>x_+#THvZdajOfk`gDLJzVXu`?5RoJ6<=*WgYwnq)cv0xfCOZZvp;Gm2WePKSTx3 zCqCon7IU^j2*tx|Ec1t_L?H^TI)b(CIQX8a_GgwwZYkwYF8X(>y6-hv6z=XSY=K5s zXrH8oO0C}rMxv_9-WOLg0I?o8`bMaRr(7E7f9!MJJs)^kt>g{HnFI{ES`+2V+Q*x)8v3v%YaFl z1Q@_5E2a4tQVdOYjVLa0zRhXSCo>F-B1X4&FJK<~pxfZU>#YTm3%!pJn>$RZ967Nx z;!+qU_n|iFACcIQitEiOP2Bp9oPNQQ&YYHkn9mcwS!WY(h(WE z;2!O-X0@d^L$(euD=Wax8Q<@im6DbDKkS>eC=I>)F+boLAl7B%hj?=q5KKPs24X#v zFqkkmR|#1?ph{vY@lxUvb&uhJNo#9w)jTOy2iBJfFB)03{ zR*o01Q(8TaN46eM>P~>RY&8U6HlaA_Cj^R9=wmv!dOBi#O^1bTSwhTV?7nWM;r3t) zJs>y_H8zm~!|cCaoLx2yjUW1usH@jw8=kWMJu7zyDlSpONs`10O+{Lxd_#19?Hq>S z7!zjTv+)DynA#Gnoq3x10vJvYbdYM`diF4{TxCQ$eiY~wYl{dNk4H)+hk#p;@hnE? zkZe@Q0V+lD=gGWd-fziqwAx$9^);hf3Wt6=^KNF*;;-cncWTckJ?pmZXku*; z6Vg@4mDAU;GFMzk_xgA_HpzK8yLY^sBP26+)^dI~?zQq16PofR!amwDNY zDKH84l@J*n>WP&b?fV_&fUC#w-kMi4l~fGEc%5)}s)3Qnu$fBls{5~}Nxmb9XL&GJ zK2}pr&`P(y*9VWRuH^BrKE&-@xWV1R;f#zVO!k##dO~2l2MO>HWxMy~y+X;~l`clq z0Wt>iBB3>SlGLQQrIMEp&N8;8t>=`|Hjr4Kt8pVF>}RD>-KHq%ty@%B=u>C{`iZwulwCIpq-Ff{f`|#8yxn(# zY(mv}x$dv!jnRlo%KPGUwBVXpIrw{_39NBAd_apF?`FX9_!LG8l~B4q^Y5UGzE0H_ zzvm29>OBebXGo_BmHpm@{81m(O!Yy3bc0#R^&>Z;o`sPuPsziJQ&j=E)o8|uKtR2e z|0`(akJ0##Npz|jw2R_QjW*RedrZu0;wT_LZbJA0{b(RT?^8x$#aIw}h`=BhaoK2} z0qKN9Ao+rt`+!pxy^-zMSiylx*vc<}~y$}t~l;-6&k4z@BC zIFEED3qPuDVy8NoYH?y5&VKFEPMl@FGEGVDWz2#zT{u$augwBux79jM-#h z%EP_3m&pN&K6DE)T*|RX@5(l@diy(Me+bmAB9tHHI+p_P7h$;?D<|CaF8eKoj5Ezt zRQsCVa|iXoa~ACk+i=+-mrU83X7OND^Jd}v^ByQE$HuotsOJrsbNddJ^qRf)?wVxE z9CAi+_a^z`9PfG2cHIfeBUeN)-=~Njxa591V6lokrbK91=rb2Sk#b)mZ<{l7FO*e* z*mTsyZ@JtE@xB1YeE)5e^y?g0s=90T1?#QL7u6lR)Vfm?&gABqzL6}*hl#8&J*B)> zF#}HFe$w3rB@jWSCR+VrJtgQ<2}-GFI>bxppTN2-9it*-nap~LX{6^ z7qpC~2O~qd`-jlmJ;NQ$8B#0FE*DUWF>9HpXX#d}8l8?7w&R)UZ&j?AoRgHa&U6YW z&1%$|ij|XXO;EJ^nJM+Cno76^^c683TfRajE%oYX%!fIPRCaAAehEEHCtzAqHe^z* zXGF9tHVaLnAt)~5KrWFyv^1o*_&P|d37iIMM2`Gb32(`=hIqKA8>`|qOWHc!FmkPG zs(kTR#a3@*a(JwTD!(X7mTF$iZhF;p1{pz$qPR4)utW_ZRO(|bWEk*GsRT`u+=GNA z$0$@OR_GecM$TIGi5fu&c`Bk2Ba>7N*uj(T46YSie@q2lUF%w7VRs41a(_ueIzG3^ zfhh{9gm0fMuq}EfE#yPIq>}h;`pt1~AUohJfKrE?*)#^cyK%q8=g60OlU8-De`Jd7 zg!EzI63%|8l9W4-+0<5hPrcBVH5rj1?Yq4a+4CUM?q>L^jV9#pdZg)qLL4raJ zjOnM^%{Wmy5u)G0kw_F881()_zRqYa!xvcDi+s1d+Al~5R>mlZtHB2N7rWxsJt3qq z{kSMKK-Q&$C<9Rs%r(kj`dNkMO*63b^QM7~_|y6UoAS3UOID$bQLagtaY|8T^&lD9 zf&Kg`LOA>E{RXz4cIeDROtFD>X>Uq=W@mJdjgpcq1^P}?<7zUeB552J0;d=@>pvWi z_r+{-Ue}7hL8vVtw)D^5#Me|iLxc8#U+msaL>pA{t8S5a4!vq$ze!WUffsR&~bLFarpY7 zjIL%Rybo`AuYulxv$|wrnD>#|ZgB0ALpz%`FRod&PG$t{G30GzM)(OFM)hL8H$25% zp@QKfN-s_<3gW)PK2TTl2=BdzX^ktNa%t;G-#&nS!d?YR8H%9sv)+0g>=y#%$2Z0X zP|c%YZrMjwW7|Y8D42J--5A*hTkQmgY5m-$b87h@;%B|X1!O`ZrJoBK((~C6Y;^!U zV3*eKMX$F23h^^Hp@w06C$;d zc($gluhqy8`T8Xq!G7_^s+axf*hZS(2kYtEmbPRJJl(Ze8mDM8ibZ;UD3#J7_gw)) zqcB`_#EOHF*%Q+Tft+3Ibz@lGUOCex{c*X5xwTETA%*n5)oHV_e4FZHdX?WM z1`8e{cP`4{>|(>YJD^v8oAPMWfqcUppS!;1@hw~Ic5e^o9o=K|UPMo6gaAC`m2I<4 zM#&gwfsz{EhjAJ&zs7IInW%$2+MuN&L>Q6AQq%W^IpasKk%>rL4(%Tw>R~uQ-9Z|k#cR{XSvg*YwS+>2* z@N1mFQRWkkkL5&Jy~Q!QR&Hn!+ZUlVBnj0NBiWq0`d8PFOjJ&lOGzzxjTgXL*ske$>M_22xDw0D zQrJp(UyXX?FJTJG7f}@Y5t+{eOKdcnwE`$$BGDk(ggYg`zflctvBrgZma!HNk2;0$ z(nyADrB)QB*{a{RnR)t))Q{pcW;EbJDJ6(jF+^J1x$VOrGdn9DrXKB8BU zZ8y{AAz5bv51jYmJ)vbdyjN5M?a+W6PcW|z_UI^(QW)Lv=ebKW%h0Paj|^n6bGpKo zyYJh5<#Nr9-g0FTX_gi5Ga3actDmKxIR_w@i2x3cdCTKa_KL0*qgd54S{Trk*P+ok ztZAliu~CL6Ka4;mOzQRc$`_M3!Xts~cKXkT*(lYW%ZYyoY6}@xtM*F@?w8ukbrGc# zvpVU%5SeMidA5yGdGsryiFMiioa@uh#y~ zQ;NP6S{@N+LJ^*0zsGw4lbmv#4<3wM431R78Y4C#OMQ^5SuvITf#L+cVVostaJhwf z1N{tWQ&5~#J@5p$qBtAUAq|nf*C_f8eoNCXLGZj7{8T)Hv3Y8Cf!)yHr>TBi%uB_P zQkA_r%5WvC5G7h3!B2Wm5O-BacXTv(gYUug~F6Y6UaPZvK*BcZ(%r(6S*H`1rZw_B|IEcL6yWXNZ&iqo` z!fJ>>ZUj;KZSLV_yH&81rXvfwqCk;`Q);4-2l8FTrCT#aGZLb2P0 zMoSiL0##i~CGw-!qA8W50+W^ujRTUJc%`zi8WxY#(FG#tUQ>0rCs@%nxiH>4@YF2& zPZG&d6`%Liopk+-3Gga6OXKxr5^D^9u;u(HZhku0B@%9zUVYncs7eS9_h0kO^e zmg55$5B%qIu8M93iA!u}M)(Ogkh}HS_BIhK9I-gr?f3F{JmAUKWDKJ6Jn2!Ttlcf% zPmmuNd^qhH2r5i5UyENI->ZF+<+%2iRx-J=kj`Bsc4|X3GLhX-6L-I2_3LUb#P&3 zCm0f@MpS!rk1xFsuDadU^u6J01`g0!Qv5{t+C-41VPOqex)L9}*{(6HHMR+7qz4vA zTOBtMF=8*IV$JsNh*-__T`U~Q~Ydlt07dFR_g1XNFE620E0zLS!*Blsv+rIJ`L1`c*Rc9;xEw5nhYH<0#zX|9vs$2KCq=nul8|x)`GU-X$d6WaZ-xE znil`vk*9awjfX7yl-Q+?lRqiEv>ZCO$G&`9dBr+=sV{Oth5CG}q-)5v?j+QlPW#c= z<$;I8N2y(7Lj0`^lUSZmCv8Zu+-2fDlIp1tZeO{XK2ysY@O1(yTAbz_rW%7()yz+` z#oLlMi9Ve}OLT7)Vt`Gi8^d|35wO(Xh#wmx)xHB6U(|GIXrYh;bB*EZn9v63>ie(9 z2Iu=Zlj9Z2;mBqh!0)2I5?>})55x+{{ z&nr0YE={JEUkCJYb>00#^3J&Pt>zu>+{AT5_{zH01?{RN->REAYEP5VxA1iJroIqKG%QYIl&#HWL=D z;0r6FR)>e6cZHHn5=k5;mONf@ljl1?N*c3t^hW0{L_M24Ik~iSLm%&U;!QHawWj4% z+7pdh+$3(zw`erKRF!UjAp+-JB9S3LgnbaK#V=+BGRCLA6a&6OYvAaAW6sUESezKQX@x?`vG z3U|!;aMD!^j5f5(*G>e!6fl;$mr=g!#B75}KV)Xgqmz_`V#Hptygx0$t zlf+p^JlTNmdp*^ch;xnU9B09iZ*n%Tb3zm;n?7qF^CP}mfri1Rn_&iC<(n|MW5RzF zN|N*PvE%<1cp3(WUP{( z8j5>|ryJb!EY;?$dN@csnTqz@A#XQ&O8kuXIHAh`FT{9wnLk}xpg!^@oX^kc;w`f3 zF|VN29Fw9lI+yI3G5JMx5dm0;zu8tl_r+5Qu}5$TY8bYNR(<(LJ+!Rla4rqD1%Pu+ z$PHzI$K=|#&v;zB^r7DGs==`l^~e!D-QS6CVJxc!_gut;FX#%anyt}(7ieM3?}@aU z&Cd#F`~t`NnD0d>x`vBYLM*_^+lTN=SJl7#QwINzIRBx+_wW5)m9Jh{Dkx8)i~W~V%1fyCo3g`u0IGT4h0wh}h#P)O#4a*@Wd6a61GB&9OP19Edglj1y> zLVa?WAxZh-*lx~7v82Z;ZT zk(~jzzUd2PY)x3Jq$3%Rh&OQO@UcR-br)%VAF+vY=BZ@TOe*Wi^09oqO4U;f$X%%S zz_vMxAHFrQJK05Q*IkOcl?K;(;3mTV$mr{=OtzhY>ujw3TD3ZE z)Hq`?8thD&dXj%k_z+UgLa`D5kWiJKB5h3$^sR3JaN$XuEvDfJF;NmCUIDV(~H8IcY9>>4;@ z$#RI@7OYy1+Co?z?Onw}-UbGmVO{W)@(ayhK)V)XE<3#mAl z9^LL|pk-3`{XwJft1J9KVh85Q3e7TkTXuf0i{s;u)nOop`clRsy&JV^R5w=^@82TE zN+|)9jKVu}3Zb+7V(=UomCqs#RSF=Ei=7(Gq;}0XZE}j@p_0T)OH?BElmpZ!IikL1 zoB~rhI1DxVJk?Scd{*Z<>Sr~-m`I)lohfdZvdWLb$HiicGbd}Ras=hljgwqx?y6&N zX)!>}uO=zox;7T8sPflb3z7lGgQ&&8zH5LLp2=GL0A7Q_a=#nc5M8Du_h|5SuDU@2 zXmMJwLx4Lwu4sYu;)`as)bTay{v3oCC;#>F_Oq9NalKHDQF*@UEJ()GTz83D&9@r; zMw8PB(woOlQ_!F@R!A+fy?S-EwSX)gp!;NAn^UT0Yj|>Y|JR7eYR{ZjYWr3AsvTmd z@)#;8&3?{??kXMEryihu?eHW9$KTkPYFU(#A0YVR&X8EUMUM?16g$RF?IFQiY}r%x z3b&ZT(W08>Tr@tmfVC^^&{ajE46nudqCEJjjFBq%Ig9XSuf^Y>1c{dWQUG#$0Np;a zC>uVAc91dTuhre)h`BC@>Et8Nyc;RbR{6&ADJZo}E^#GI(z=)aNLrw&eHZ#(q?fk8 zK5vav8KpTWANc{dcw~!}fs7yp#WBh%O+KZo(YdBl5i=?8cuu zAw^`9-uWZ3uy5+YSXwQZ(D7K9B`g-KxiU#O`_#QnEk?5|*|^_4=#;wRM+W5aS~*Yp z{=1Q^xS=F@RXq1_Ka2BX5?*V}+M>{1XOM1u2n+b69GGVE-FY+za`Iut&G}7NS?YC{ zOnPlfc^fG__)`@#GB07VNK_nnNI%VhQ-ZRuyH9ucb3v>nKW+$!Z+XKA2D`kOz3B~( zo1>dRDV(t~8#EwR`Tg}`^$x+d92sOQ1N1h)q94uQ5=-yQRmgNIrQ}3LpQ>1-H-szi zSHp^dSrN7bx)H;OHD#q~i;73+-P=%K^Ac)Rmi1#g`P({ekG3fvH#?_}`ZILyydrmZ zV%oxaC|wmOjNuz`X54aP_;^nrs#Q^mMYrME><^<&Dv`}?oAT*yOKmsls<&c4)g0)bZ%8|Z7|pAbYkAqSyWk4JOlHY6ldu-&5t-F=n) zWxX1SuE-ol1MI`3S_4wQ*dq{-;xJHl*S~r8E;EMep!Z0p37Ia#~14g zCD(V2C|v1RrjIZr+D_UJC|Uji5hg;MO>DwD>iOqhU89S;iBcz_>@!PrP*ZAhVktu3ROj_P-pC?==U^*nc&dQQttradTBgw+ez!LG1 z3m07`$Gxm7fOsPsWq7H%8rjY$b^lHa7-dxeRHRRU9%xyJxf$fxYRynIR%<2E#Dkfk z;@El+Qe8TQGJi5Uordtn$%$)9+%5;QZwFZ&N3#2C=uL_7J?z-kTQ5teyUUo;V!TGi zia^caMGE;k&5NLJkjI@5yH*T5T?Dt(%dQc1?dK`?bT`LG8{{7I6nP+SZOYO@twTZ) z*@QwQ;Pz);f8D^1No_`H25jKs15Uh9|9u1ZZ{PJl4)p!;bq`n2_}2lK+B%ve!dy*c z0dllVn!ymX=C{Ql3z*i9djy^qWJ1`_!f;uPCJFS<7n$%Xq^Q54j zs_lBKDrciL1RM&{ZP>zAMIf(g=qh35yMLjI{{aovZXr~cp7zi>lu@H+yziF*YN7HE z5fx$EjJd>;orv0M0?hB{72jyo9KI97+Eoj_1mL|xpMi& zZc)|h?*?}5lg^pvjlXB?+rCt4n$S_!dS}VZqpP7vYieVyfccg_fi5Lp7+3a#Dtm6kOF-y->2w^m=f55Cn-6*poUC6vk5HF!lGq6vxkOA9Q3C4 zeP4q~dVgKe*ZF2D*g;291DJPFGd4J1ph#tl%aTdi64Wje`sP+<>&HZby_r?&8j`i>8tS{n&lLp4k46L@2UD$pwrJ#k4x|#RT;aIG|Lh=Us-xux^*qp8+^J>G+~OfU znLIG=m*q1+JL&8#ivU9?>t@DL8c*>~G}ehf&jljaC9HpzAnQHC{b6?bIOQqsBm7V3RRPK9-`7VZ1Gpc3BTK|j9t z7B2j2Z3hS5=jRvazCs09EZhO-TA=~wS~-957T8&v8R|Ryj;?QNw3J*YKXSkrD2T8- z-`lRN!*&o%B*#r6`7o+1V)Sbvt~dtEAeh&X&yp&nv=6VI$TyLT80LjHsgL(kI94y5 z@~ltj%7LybHTY4nTrIv;eiow$I>L5>_=u*F(#v+y;VZS>uQ;W`yNSD_pLR*+q;Hl;Vv%OSA`s6a0wS;} zgWi8E*lyvwoDJqACk*2DS@75MlAH;@f<(h9E1eH<_-=HdO(}Isr46a_e%C1One9VW5@%BPDZ!|g4GB#}Vh{rE& zSO^MK;R}*I4s;%B{;TXzBzMRm2F*|F7wY@AU^E^=Q}1^rfs@iihCc3^0VgGjP(c>l zI+GP%z6e%{0pd#h(Wei9(T(HpeflyL+n?4hFBCXaqlYBB_>lw0=8G+BYG=)6M3z_t zk%YSg&>~UM-qF3?^Gw2>iXuiLof2G;RPlwzYY##sGksGi(5;rjbUyYxlG4!Z)!h23 z{gp*LK72T#1#+gE{|K-JN`?r&*C03P7^K0%T_k_)P@j0lf-&xj^fE$-8>e0DyA%6R zP9aKFX4&qNlnU>5`E=;TYET?56LmNya9#X~7NjLH0t_&%;KkAZrzjEUL%PY$9oK_6;4B7I$Jt7<(}-N-5IZKQSB3~4Jsq?D;)ZxmHs2C_mf z+hUD`K@~HAM1XU|GO)Yf_NgHIY`&7TEHm+}D(%H%<`6hCb1AKvsDLe&?>9)r&Sc0Y}gOS(JYJr4&5`1Oxy=0C4>*=zv>2M^g&}8aqRMLsJ`v zKg-)o(NK;KkDXJE$Vk#uu}m<50hY($5JrPfegL@uAi$a!@b@cVWFSE8{saxMynhvd zeA|m6BcdokBOxnF_wq5-PEz_G2dNYR*N>n2v;0tv{lCX#1Y{*dMHCciWkg>h{CMI& z#DK$oe=13Uduu!6zj6NfFaLQ0pzrgi(h9i$@x;I7`TvRPM?3s1dw|_DFZ@zegQ2EkaMuG);0K#VBkx@L2FKXctA7p1M18C7eG#cU*w+v0pAT5R{=){ z6M)vTss2ytl9vpTX9%P4KN2ki5-$+^g&`TxKL5b*$8_u^+_Ws+awY&~5O6X41#S=R zAK?J?HMTRfx0ePa^ft8mPa1`n@Sef+2-<+A+yXk%7Do{YcZfz$pP&&u9G*TUkz*>Ea!13xj~O}zpPCis7< z9S{%}%Rk}$x^}s)^o`1Z4gvzK9RNM@r{W?0OEhU~yOF zUgk6Z$$}~Kzgd3W3@`J({=^gojN-rO^p{hQzhr@ZS>u;k7k{FYs{IoE-$we29E>la zUnaf#2@S0IPtbo&f%g*iW%ih#sPKjW{qujlqyLyo<|W_{fFD-&qx{Gh^Rrk10RPm! zKSI!6KKwF!%+H5Y|NiiQ5_tUgx!_Cqml;8R!jqf)t#1E;|DAQjOQM&m{y&LEEdECH zr~3aFjsKVMFXicf!s}c86a0&*@=Ms4s_Z{uyR82S_Rn61mzXaFfPZ3^IQ|pnA4h2a z+sOD*YWF8A-ClCOlbkmnsV{sb0pj|D?i-{%tD2_+s;C4ZfEoFT;d?l2Cm9ZIVCU*FR~dykvP9 zkNT5^H2$|){v4h9lHg@D;7nG1l$KQBfPCNf(vH#;U{?hOAlcu2S z|E6^R%?tCNI{(M#@@J>X51-4=ati?aZyuPpQlNl!(2v+fMxgfqf6Ke>AAkKnZ|KqV literal 0 HcmV?d00001 diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/wrapper/gradle-wrapper.properties b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..7e4921d3 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Nov 01 15:30:19 PDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/gradlew b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/gradlew.bat b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/settings.gradle b/external-providers/java-external-provider/examples/gradle-multi-project-example/settings.gradle new file mode 100644 index 00000000..9c10890a --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'gradle-multi-project-example' + +include 'template-core' +include 'template-server' diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/build.gradle b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/build.gradle new file mode 100644 index 00000000..7fb7ee66 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'application' + +mainClassName = 'io.jeffchao.template.core.Core' + +dependencies { +} + +run.doFirst { + // Environment variables go here. +} \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/src/main/java/io/jeffchao/template/core/Core.java b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/src/main/java/io/jeffchao/template/core/Core.java new file mode 100644 index 00000000..b430f2b1 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/src/main/java/io/jeffchao/template/core/Core.java @@ -0,0 +1,8 @@ +package io.jeffchao.template.core; + +public class Core { + + public static void main(String[] args) { + System.out.println("hello, template!"); + } +} \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/src/test/java/io/jeffchao/template/core/CoreTest.java b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/src/test/java/io/jeffchao/template/core/CoreTest.java new file mode 100644 index 00000000..d88ab5a1 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-core/src/test/java/io/jeffchao/template/core/CoreTest.java @@ -0,0 +1,16 @@ +package io.jeffchao.template.core; + +import org.junit.After; +import org.junit.Before; + + +public class CoreTest { + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } +} \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/build.gradle b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/build.gradle new file mode 100644 index 00000000..47600b02 --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'application' + +mainClassName = 'io.jeffchao.template.server.Server' + +dependencies { +} + +run.doFirst { + // Environment variables go here. +} diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/src/main/java/io/jeffchao/template/server/Server.java b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/src/main/java/io/jeffchao/template/server/Server.java new file mode 100644 index 00000000..cc6d373d --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/src/main/java/io/jeffchao/template/server/Server.java @@ -0,0 +1,32 @@ +package io.jeffchao.template.server; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +public class Server { + + public static void main(String[] args) throws IOException { + String portString = System.getenv("PORT"); + int port = portString == null ? 8080 : Integer.valueOf(portString); + HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); + server.createContext("/", new MyHandler()); + server.setExecutor(null); // creates a default executor + server.start(); + } + + static class MyHandler implements HttpHandler { + @Override + public void handle(HttpExchange t) throws IOException { + String response = "Hello from Gradle!"; + t.sendResponseHeaders(200, response.length()); + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + } +} \ No newline at end of file diff --git a/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/src/test/java/io/jeffchao/template/server/ServerTest.java b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/src/test/java/io/jeffchao/template/server/ServerTest.java new file mode 100644 index 00000000..6f63c10e --- /dev/null +++ b/external-providers/java-external-provider/examples/gradle-multi-project-example/template-server/src/test/java/io/jeffchao/template/server/ServerTest.java @@ -0,0 +1,16 @@ +package io.jeffchao.template.server; + +import org.junit.After; +import org.junit.Before; + + +public class ServerTest { + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } +} \ No newline at end of file diff --git a/external-providers/java-external-provider/go.mod b/external-providers/java-external-provider/go.mod index 19bbba6e..8229d63f 100644 --- a/external-providers/java-external-provider/go.mod +++ b/external-providers/java-external-provider/go.mod @@ -6,14 +6,16 @@ require ( github.com/go-logr/logr v1.4.1 github.com/konveyor/analyzer-lsp v0.4.0-alpha.1.0.20240520232004-8af6f5c84a59 github.com/swaggest/openapi-go v0.2.50 - github.com/vifraa/gopom v1.0.0 go.lsp.dev/uri v0.3.0 go.opentelemetry.io/otel v1.11.2 google.golang.org/grpc v1.62.1 // indirect gopkg.in/yaml.v2 v2.4.0 ) -require github.com/sirupsen/logrus v1.9.0 +require ( + github.com/sirupsen/logrus v1.9.0 + github.com/vifraa/gopom v1.0.0 +) require ( github.com/golang-jwt/jwt/v5 v5.2.1 // indirect @@ -34,8 +36,10 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.11.2 // indirect go.opentelemetry.io/otel/sdk v1.11.2 // indirect go.opentelemetry.io/otel/trace v1.11.2 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002 // indirect ) + +replace github.com/konveyor/analyzer-lsp => ../../ diff --git a/external-providers/java-external-provider/go.sum b/external-providers/java-external-provider/go.sum index 27274d0c..5ed222b1 100644 --- a/external-providers/java-external-provider/go.sum +++ b/external-providers/java-external-provider/go.sum @@ -66,15 +66,19 @@ go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZ go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002 h1:V7Da7qt0MkY3noVANIMVBk28nOnijADeOR3i5Hcvpj4= google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/external-providers/java-external-provider/pkg/java_external_provider/dependency.go b/external-providers/java-external-provider/pkg/java_external_provider/dependency.go index d980e85e..4171036b 100644 --- a/external-providers/java-external-provider/pkg/java_external_provider/dependency.go +++ b/external-providers/java-external-provider/pkg/java_external_provider/dependency.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "io" "io/fs" @@ -36,6 +37,21 @@ const ( baseDepKey = "baseDep" ) +const ( + maven = "maven" + gradle = "gradle" +) + +func (p *javaServiceClient) GetBuildTool() string { + bf := "" + if bf = p.findPom(); bf != "" { + return maven + } else if bf = p.findGradleBuild(); bf != "" { + return gradle + } + return "" +} + // TODO implement this for real func (p *javaServiceClient) findPom() string { var depPath string @@ -51,10 +67,45 @@ func (p *javaServiceClient) findPom() string { if err != nil { return "" } + if _, err := os.Stat(f); errors.Is(err, os.ErrNotExist) { + return "" + } return f } +func (p *javaServiceClient) findGradleBuild() string { + if p.config.Location != "" { + path := filepath.Join(p.config.Location, "build.gradle") + _, err := os.Stat(path) + if err != nil { + return "" + } + f, err := filepath.Abs(path) + if err != nil { + return "" + } + return f + } + return "" +} + func (p *javaServiceClient) GetDependencies(ctx context.Context) (map[uri.URI][]*provider.Dep, error) { + if p.GetBuildTool() == gradle { + p.log.V(2).Info("gradle found - retrieving dependencies") + m := map[uri.URI][]*provider.Dep{} + deps, err := p.getDependenciesForGradle(ctx) + for f, ds := range deps { + deps := []*provider.Dep{} + for _, dep := range ds { + d := dep.Dep + deps = append(deps, &d) + deps = append(deps, provider.ConvertDagItemsToList(dep.AddedDeps)...) + } + m[f] = deps + } + return m, err + } + p.depsMutex.RLock() val := p.depsCache p.depsMutex.RUnlock() @@ -226,6 +277,17 @@ func pomCoordinate(value *string) string { } func (p *javaServiceClient) GetDependenciesDAG(ctx context.Context) (map[uri.URI][]provider.DepDAGItem, error) { + switch p.GetBuildTool() { + case maven: + return p.getDependenciesForMaven(ctx) + case gradle: + return p.getDependenciesForGradle(ctx) + default: + return nil, fmt.Errorf("no build tool found") + } +} + +func (p *javaServiceClient) getDependenciesForMaven(ctx context.Context) (map[uri.URI][]provider.DepDAGItem, error) { localRepoPath := getMavenLocalRepoPath(p.mvnSettingsFile) path := p.findPom() @@ -274,6 +336,171 @@ func (p *javaServiceClient) GetDependenciesDAG(ctx context.Context) (map[uri.URI return m, nil } +// getDependenciesForGradle invokes the Gradle wrapper to get the dependency tree and returns all project dependencies +// TODO: what if no wrapper? +func (p *javaServiceClient) getDependenciesForGradle(ctx context.Context) (map[uri.URI][]provider.DepDAGItem, error) { + subprojects, err := p.getGradleSubprojects() + if err != nil { + return nil, err + } + + // command syntax: ./gradlew subproject1:dependencies subproject2:dependencies ... + args := []string{} + if len(subprojects) > 0 { + for _, sp := range subprojects { + args = append(args, fmt.Sprintf("%s:dependencies", sp)) + } + } else { + args = append(args, "dependencies") + } + + // get the graph output + exe, err := filepath.Abs(filepath.Join(p.config.Location, "gradlew")) + if err != nil { + return nil, fmt.Errorf("error calculating gradle wrapper path") + } + if _, err = os.Stat(exe); errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("a gradle wrapper must be present in the project") + } + cmd := exec.Command(exe, args...) + cmd.Dir = p.config.Location + output, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + + lines := strings.Split(string(output), "\n") + deps := p.parseGradleDependencyOutput(lines) + + // TODO: do we need to separate by submodule somehow? + + path := p.findGradleBuild() + file := uri.File(path) + m := map[uri.URI][]provider.DepDAGItem{} + m[file] = deps + + // TODO: need error? + return m, nil +} + +func (p *javaServiceClient) getGradleSubprojects() ([]string, error) { + args := []string{ + "projects", + } + + // Ideally we'd want to set this in gradle.properties, or as a -Dorg.gradle.java.home arg, + // but it doesn't seem to work in older Gradle versions. This should only affect child processes in any case. + err := os.Setenv("JAVA_HOME", os.Getenv("JAVA8_HOME")) + if err != nil { + return nil, err + } + + exe, err := filepath.Abs(filepath.Join(p.config.Location, "gradlew")) + if err != nil { + return nil, fmt.Errorf("error calculating gradle wrapper path") + } + if _, err = os.Stat(exe); errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("a gradle wrapper must be present in the project") + } + cmd := exec.Command(exe, args...) + cmd.Dir = p.config.Location + output, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + + beginRegex := regexp.MustCompile(`Root project`) + endRegex := regexp.MustCompile(`To see a list of`) + npRegex := regexp.MustCompile(`No sub-projects`) + pRegex := regexp.MustCompile(`.*- Project '(.*)'`) + + subprojects := []string{} + + gather := false + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if npRegex.Find([]byte(line)) != nil { + return []string{}, nil + } + if beginRegex.Find([]byte(line)) != nil { + gather = true + continue + } + if gather { + if endRegex.Find([]byte(line)) != nil { + return subprojects, nil + } + + if p := pRegex.FindStringSubmatch(line); p != nil { + subprojects = append(subprojects, p[1]) + } + } + } + + return subprojects, fmt.Errorf("error parsing gradle dependency output") +} + +// parseGradleDependencyOutput converts the relevant lines from the dependency output into actual dependencies +// See https://regex101.com/r/9Gp7dW/1 for context +func (p *javaServiceClient) parseGradleDependencyOutput(lines []string) []provider.DepDAGItem { + deps := []provider.DepDAGItem{} + + treeDepRegex := regexp.MustCompile(`^([| ]+)?[+\\]--- (.*)`) + + // map of to + // this is so that children can be added to their respective parents + lastFoundWithDepth := make(map[int]*provider.DepDAGItem) + + for _, line := range lines { + match := treeDepRegex.FindStringSubmatch(line) + if match != nil { + dep := parseGradleDependencyString(match[2]) + if reflect.DeepEqual(dep, provider.DepDAGItem{}) { // ignore empty dependency + continue + } else if match[1] != "" { // transitive dependency + dep.Dep.Indirect = true + depth := len(match[1]) / 5 // get the level of anidation of the dependency within the tree + parent := lastFoundWithDepth[depth-1] // find its parent + parent.AddedDeps = append(parent.AddedDeps, dep) // add child to parent + lastFoundWithDepth[depth] = &parent.AddedDeps[len(parent.AddedDeps)-1] // update last found with given depth + } else { // root level (direct) dependency + deps = append(deps, dep) // add root dependency to result list + lastFoundWithDepth[0] = &deps[len(deps)-1] + continue + } + } + } + + return deps +} + +// parseGradleDependencyString parses the lines of the gradle dependency output, for instance: +// org.codehaus.groovy:groovy:3.0.21 +// org.codehaus.groovy:groovy:3.+ -> 3.0.21 +// com.codevineyard:hello-world:{strictly 1.0.1} -> 1.0.1 +// :simple-jar (n) +func parseGradleDependencyString(s string) provider.DepDAGItem { + // (*) - dependencies omitted (listed previously) + // (n) - Not resolved (configuration is not meant to be resolved) + if strings.HasSuffix(s, "(n)") || strings.HasSuffix(s, "(*)") { + return provider.DepDAGItem{} + } + + depRegex := regexp.MustCompile(`(.+):(.+):((.*) -> )?(.*)`) + libRegex := regexp.MustCompile(`:(.*)`) + + dep := provider.Dep{} + match := depRegex.FindStringSubmatch(s) + if match != nil { + dep.Name = match[1] + "." + match[2] + dep.Version = match[5] + } else if match = libRegex.FindStringSubmatch(s); match != nil { + dep.Name = match[1] + } + + return provider.DepDAGItem{Dep: dep, AddedDeps: []provider.DepDAGItem{}} +} + // extractSubmoduleTrees creates an array of lines for each submodule tree found in the mvn dependency:tree output func extractSubmoduleTrees(lines []string) [][]string { submoduleTrees := [][]string{} diff --git a/external-providers/java-external-provider/pkg/java_external_provider/dependency_test.go b/external-providers/java-external-provider/pkg/java_external_provider/dependency_test.go index d605f319..33957093 100644 --- a/external-providers/java-external-provider/pkg/java_external_provider/dependency_test.go +++ b/external-providers/java-external-provider/pkg/java_external_provider/dependency_test.go @@ -560,3 +560,238 @@ func Test_parseMavenDepLines(t *testing.T) { }) } } + +func Test_parseGradleDependencyOutput(t *testing.T) { + gradleOutput := ` +Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details + +> Task :dependencies + +------------------------------------------------------------ +Root project +------------------------------------------------------------ + +annotationProcessor - Annotation processors and their dependencies for source set 'main'. +No dependencies + +api - API dependencies for source set 'main'. (n) +No dependencies + +apiElements - API elements for main. (n) +No dependencies + +archives - Configuration for archive artifacts. (n) +No dependencies + +compileClasspath - Compile classpath for source set 'main'. ++--- org.codehaus.groovy:groovy:3.+ -> 3.0.21 ++--- org.codehaus.groovy:groovy-json:3.+ -> 3.0.21 +| \--- org.codehaus.groovy:groovy:3.0.21 ++--- com.codevineyard:hello-world:{strictly 1.0.1} -> 1.0.1 +\--- :simple-jar + +testRuntimeOnly - Runtime only dependencies for source set 'test'. (n) +No dependencies + +(*) - dependencies omitted (listed previously) + +(n) - Not resolved (configuration is not meant to be resolved) + +A web-based, searchable dependency report is available by adding the --scan option. + +BUILD SUCCESSFUL in 4s +1 actionable task: 1 executed +` + + lines := strings.Split(gradleOutput, "\n") + + p := javaServiceClient{ + log: testr.New(t), + depToLabels: map[string]*depLabelItem{}, + config: provider.InitConfig{ + ProviderSpecificConfig: map[string]interface{}{ + "excludePackages": []string{}, + }, + }, + } + + wantedDeps := []provider.DepDAGItem{ + { + Dep: provider.Dep{ + Name: "org.codehaus.groovy.groovy", + Version: "3.0.21", + Indirect: false, + }, + }, + { + Dep: provider.Dep{ + Name: "org.codehaus.groovy.groovy-json", + Version: "3.0.21", + Indirect: false, + }, + AddedDeps: []provider.DepDAGItem{ + { + Dep: provider.Dep{ + Name: "org.codehaus.groovy.groovy", + Version: "3.0.21", + Indirect: true, + }, + }, + }, + }, + { + Dep: provider.Dep{ + Name: "com.codevineyard.hello-world", + Version: "1.0.1", + Indirect: false, + }, + }, + { + Dep: provider.Dep{ + Name: "simple-jar", + Indirect: false, + }, + }, + } + + deps := p.parseGradleDependencyOutput(lines) + + if len(deps) != len(wantedDeps) { + t.Errorf("different number of dependencies found") + } + + for i := 0; i < len(deps); i++ { + dep := deps[i] + wantedDep := wantedDeps[i] + if dep.Dep.Name != wantedDep.Dep.Name { + t.Errorf("wanted name: %s, found name: %s", wantedDep.Dep.Name, dep.Dep.Name) + } + if dep.Dep.Version != wantedDep.Dep.Version { + t.Errorf("wanted version: %s, found version: %s", wantedDep.Dep.Version, dep.Dep.Version) + } + if len(dep.AddedDeps) != len(wantedDep.AddedDeps) { + t.Errorf("wanted %d child deps, found %d for dep %s", len(wantedDep.AddedDeps), len(dep.AddedDeps), dep.Dep.Name) + } + + } + +} + +func Test_parseGradleDependencyOutput_withTwoLevelsOfNesting(t *testing.T) { + gradleOutput := ` +Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details + +> Task :dependencies + +------------------------------------------------------------ +Root project +------------------------------------------------------------ + +annotationProcessor - Annotation processors and their dependencies for source set 'main'. +No dependencies + +api - API dependencies for source set 'main'. (n) +No dependencies + +apiElements - API elements for main. (n) +No dependencies + +archives - Configuration for archive artifacts. (n) +No dependencies + +compileClasspath - Compile classpath for source set 'main'. ++--- net.sourceforge.pmd:pmd-java:5.6.1 + +--- net.sourceforge.pmd:pmd-core:5.6.1 + | \--- com.google.code.gson:gson:2.5 + \--- net.sourceforge.saxon:saxon:9.1.0.8 ++--- org.apache.logging.log4j:log4j-api:2.9.1 + +testRuntimeOnly - Runtime only dependencies for source set 'test'. (n) +No dependencies + +(*) - dependencies omitted (listed previously) + +(n) - Not resolved (configuration is not meant to be resolved) + +A web-based, searchable dependency report is available by adding the --scan option. + +BUILD SUCCESSFUL in 4s +1 actionable task: 1 executed +` + + lines := strings.Split(gradleOutput, "\n") + + p := javaServiceClient{ + log: testr.New(t), + depToLabels: map[string]*depLabelItem{}, + config: provider.InitConfig{ + ProviderSpecificConfig: map[string]interface{}{ + "excludePackages": []string{}, + }, + }, + } + + wantedDeps := []provider.DepDAGItem{ + { + Dep: provider.Dep{ + Name: "net.sourceforge.pmd.pmd-java", + Version: "5.6.1", + Indirect: false, + }, + AddedDeps: []provider.DepDAGItem{ + { + Dep: provider.Dep{ + Name: "net.sourceforge.pmd.pmd-core", + Version: "5.6.1", + Indirect: true, + }, + AddedDeps: []provider.DepDAGItem{ + { + Dep: provider.Dep{ + Name: "com.google.code.gson.gson", + Version: "2.5", + Indirect: true, + }, + }, + }, + }, + { + Dep: provider.Dep{ + Name: "net.sourceforge.saxon.saxon", + Version: "9.1.0.8", + Indirect: true, + }, + }, + }, + }, + { + Dep: provider.Dep{ + Name: "org.apache.logging.log4j.log4j-api", + Version: "2.9.1", + Indirect: false, + }, + }, + } + + deps := p.parseGradleDependencyOutput(lines) + + if len(deps) != len(wantedDeps) { + t.Errorf("different number of dependencies found") + } + + for i := 0; i < len(deps); i++ { + dep := deps[i] + wantedDep := wantedDeps[i] + if dep.Dep.Name != wantedDep.Dep.Name { + t.Errorf("wanted name: %s, found name: %s", wantedDep.Dep.Name, dep.Dep.Name) + } + if dep.Dep.Version != wantedDep.Dep.Version { + t.Errorf("wanted version: %s, found version: %s", wantedDep.Dep.Version, dep.Dep.Version) + } + if len(dep.AddedDeps) != len(wantedDep.AddedDeps) { + t.Errorf("wanted %d child deps, found %d for dep %s", len(wantedDep.AddedDeps), len(dep.AddedDeps), dep.Dep.Name) + } + + } + +} diff --git a/external-providers/java-external-provider/pkg/java_external_provider/filter.go b/external-providers/java-external-provider/pkg/java_external_provider/filter.go index fd6d85b0..3e33d14d 100644 --- a/external-providers/java-external-provider/pkg/java_external_provider/filter.go +++ b/external-providers/java-external-provider/pkg/java_external_provider/filter.go @@ -263,7 +263,7 @@ func (p *javaServiceClient) getURI(refURI string) (string, uri.URI, error) { var jarPath string if sourceRange { - // If there is a source range, we know we know there is a sources jar + // If there is a source range, we know there is a sources jar jarName := filepath.Base(u.Path) s := strings.TrimSuffix(jarName, ".jar") s = fmt.Sprintf("%v-sources.jar", s) @@ -272,26 +272,61 @@ func (p *javaServiceClient) getURI(refURI string) (string, uri.URI, error) { jarName := filepath.Base(u.Path) jarPath = filepath.Join(filepath.Dir(u.Path), jarName) } - path := filepath.Join(strings.Split(strings.TrimSuffix(packageName, ".class"), ".")...) + + path := filepath.Join(strings.Split(strings.TrimSuffix(packageName, ".class"), ".")...) // path: org/apache/logging/log4j/core/appender/FileManager javaFileName := fmt.Sprintf("%s.java", filepath.Base(path)) if i := strings.Index(javaFileName, "$"); i > 0 { javaFileName = fmt.Sprintf("%v.java", javaFileName[0:i]) } - javaFileAbsolutePath := filepath.Join(filepath.Dir(jarPath), filepath.Dir(path), javaFileName) + javaFileAbsolutePath := "" + if p.GetBuildTool() == maven { + javaFileAbsolutePath = filepath.Join(filepath.Dir(jarPath), filepath.Dir(path), javaFileName) - // attempt to decompile when directory for the expected java file doesn't exist - // if directory exists, assume .java file is present within, this avoids decompiling every Jar - if _, err := os.Stat(filepath.Dir(javaFileAbsolutePath)); err != nil { - cmd := exec.Command("jar", "xf", filepath.Base(jarPath)) - cmd.Dir = filepath.Dir(jarPath) - err := cmd.Run() + // attempt to decompile when directory for the expected java file doesn't exist + // if directory exists, assume .java file is present within, this avoids decompiling every Jar + if _, err := os.Stat(filepath.Dir(javaFileAbsolutePath)); err != nil { + cmd := exec.Command("jar", "xf", filepath.Base(jarPath)) + cmd.Dir = filepath.Dir(jarPath) + err := cmd.Run() + if err != nil { + fmt.Printf("\n java error%v", err) + return "", "", err + } + } + } else if p.GetBuildTool() == gradle { + sourcesFile := "" + jarFile := filepath.Base(jarPath) + walker := func(path string, d os.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("found error traversing files: %w", err) + } + if !d.IsDir() && d.Name() == jarFile { + sourcesFile = path + if err != nil { + return fmt.Errorf("found error traversing files: %w", err) + } + return nil + } + return nil + } + root := filepath.Join(jarPath, "..", "..") + err := filepath.WalkDir(root, walker) + if err != nil { + return "", "", err + } + javaFileAbsolutePath = filepath.Join(filepath.Dir(sourcesFile), filepath.Dir(path), javaFileName) + + cmd := exec.Command("jar", "xf", filepath.Base(sourcesFile)) + cmd.Dir = filepath.Dir(sourcesFile) + err = cmd.Run() if err != nil { fmt.Printf("\n java error%v", err) return "", "", err } } + ui := uri.New(javaFileAbsolutePath) file, err := os.Open(ui.Filename()) defer file.Close() diff --git a/external-providers/java-external-provider/pkg/java_external_provider/provider.go b/external-providers/java-external-provider/pkg/java_external_provider/provider.go index 1bedb85b..b6bbe975 100644 --- a/external-providers/java-external-provider/pkg/java_external_provider/provider.go +++ b/external-providers/java-external-provider/pkg/java_external_provider/provider.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "io" "os" @@ -234,16 +235,6 @@ func (p *javaProvider) Init(ctx context.Context, log logr.Logger, config provide isBinary = true } - if mode == provider.FullAnalysisMode { - // we attempt to decompile JARs of dependencies that don't have a sources JAR attached - // we need to do this for jdtls to correctly recognize source attachment for dep - err := resolveSourcesJars(ctx, log, config.Location, mavenSettingsFile) - if err != nil { - // TODO (pgaikwad): should we ignore this failure? - log.Error(err, "failed to resolve sources jar for location", "location", config.Location) - } - } - // handle proxy settings for k, v := range config.Proxy.ToEnvVars() { os.Setenv(k, v) @@ -311,6 +302,25 @@ func (p *javaProvider) Init(ctx context.Context, log logr.Logger, config provide includedPaths: provider.GetIncludedPathsFromConfig(config, false), } + if mode == provider.FullAnalysisMode { + // we attempt to decompile JARs of dependencies that don't have a sources JAR attached + // we need to do this for jdtls to correctly recognize source attachment for dep + switch svcClient.GetBuildTool() { + case maven: + err := resolveSourcesJarsForMaven(ctx, log, config.Location, mavenSettingsFile) + if err != nil { + // TODO (pgaikwad): should we ignore this failure? + log.Error(err, "failed to resolve maven sources jar for location", "location", config.Location) + } + case gradle: + err = resolveSourcesJarsForGradle(ctx, log, config.Location, mavenSettingsFile, &svcClient) + if err != nil { + log.Error(err, "failed to resolve gradle sources jar for location", "location", config.Location) + } + } + + } + svcClient.initialization(ctx) err = svcClient.depInit() if err != nil { @@ -319,6 +329,168 @@ func (p *javaProvider) Init(ctx context.Context, log logr.Logger, config provide return &svcClient, returnErr } +func resolveSourcesJarsForGradle(ctx context.Context, log logr.Logger, location string, mvnSettings string, svc *javaServiceClient) error { + ctx, span := tracing.StartNewSpan(ctx, "resolve-sources") + defer span.End() + + log.V(5).Info("resolving dependency sources for gradle") + + gb := svc.findGradleBuild() + if gb == "" { + return fmt.Errorf("could not find gradle build file for project") + } + + // create a temporary build file to append the task for downloading sources + taskgb := filepath.Join(filepath.Dir(gb), "tmp.gradle") + err := CopyFile(gb, taskgb) + if err != nil { + return fmt.Errorf("error copying file %s to %s", gb, taskgb) + } + defer os.Remove(taskgb) + + // append downloader task + taskfile := "/root/.gradle/task.gradle" + err = AppendToFile(taskfile, taskgb) + if err != nil { + return fmt.Errorf("error appending file %s to %s", taskfile, taskgb) + } + + tmpgbname := filepath.Join(location, "toberenamed.gradle") + err = os.Rename(gb, tmpgbname) + if err != nil { + return fmt.Errorf("error renaming file %s to %s", gb, "toberenamed.gradle") + } + defer os.Rename(tmpgbname, gb) + + err = os.Rename(taskgb, gb) + if err != nil { + return fmt.Errorf("error renaming file %s to %s", gb, "toberenamed.gradle") + } + defer os.Remove(gb) + + // run gradle wrapper with tmp build file + exe, err := filepath.Abs(filepath.Join(svc.config.Location, "gradlew")) + if err != nil { + return fmt.Errorf("error calculating gradle wrapper path") + } + if _, err = os.Stat(exe); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("a gradle wrapper must be present in the project") + } + + // gradle must run with java 8 (see compatibility matrix) + java8home := os.Getenv("JAVA8_HOME") + if java8home == "" { + return fmt.Errorf("") + } + + args := []string{ + "konveyorDownloadSources", + } + cmd := exec.CommandContext(ctx, exe, args...) + cmd.Env = append(cmd.Env, fmt.Sprintf("JAVA_HOME=%s", java8home)) + cmd.Dir = location + output, err := cmd.CombinedOutput() + if err != nil { + return err + } + + log.V(8).WithValues("output", output).Info("got gradle output") + + // TODO: what if all sources available + reader := bytes.NewReader(output) + unresolvedSources, err := parseUnresolvedSourcesForGradle(reader) + if err != nil { + return err + } + + fmt.Printf("%d", len(unresolvedSources)) + + decompileJobs := []decompileJob{} + if len(unresolvedSources) > 1 { + // Gradle cache dir structure changes over time - we need to find where the actual dependencies are stored + cache, err := findGradleCache(unresolvedSources[0].GroupId) + if err != nil { + return err + } + + for _, artifact := range unresolvedSources { + log.V(5).WithValues("artifact", artifact).Info("sources for artifact not found, decompiling...") + + artifactDir := filepath.Join(cache, artifact.GroupId, artifact.ArtifactId) + jarName := fmt.Sprintf("%s-%s.jar", artifact.ArtifactId, artifact.Version) + artifactPath, err := findGradleArtifact(artifactDir, jarName) + if err != nil { + return err + } + decompileJobs = append(decompileJobs, decompileJob{ + artifact: artifact, + inputPath: artifactPath, + outputPath: filepath.Join(filepath.Dir(artifactPath), "decompiled", jarName), + }) + } + err = decompile(ctx, log, alwaysDecompileFilter(true), 10, decompileJobs, "") + if err != nil { + return err + } + // move decompiled files to base location of the jar + for _, decompileJob := range decompileJobs { + jarName := strings.TrimSuffix(filepath.Base(decompileJob.inputPath), ".jar") + err = moveFile(decompileJob.outputPath, + filepath.Join(filepath.Dir(decompileJob.inputPath), + fmt.Sprintf("%s-sources.jar", jarName))) + if err != nil { + log.V(5).Error(err, "failed to move decompiled file", "file", decompileJob.outputPath) + } + } + + } + return nil +} + +// findGradleCache looks for the folder within the Gradle cache where the actual dependencies are stored +// by walking the cache directory looking for a directory equal to the given sample group id +func findGradleCache(sampleGroupId string) (string, error) { + // TODO(jmle): atm taking for granted that the cache is going to be here + root := "/root/.gradle/caches" + cache := "" + walker := func(path string, d os.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("found error looking for cache directory: %w", err) + } + if d.IsDir() && d.Name() == sampleGroupId { + cache = path + return filepath.SkipAll + } + return nil + } + err := filepath.WalkDir(root, walker) + if err != nil { + return "", err + } + cache = filepath.Dir(cache) // return the parent of the found directory + return cache, nil +} + +// findGradleArtifact looks for a given artifact jar within the given root dir +func findGradleArtifact(root string, artifactId string) (string, error) { + artifactPath := "" + walker := func(path string, d os.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("found error looking for artifact: %w", err) + } + if !d.IsDir() && d.Name() == artifactId { + artifactPath = path + return filepath.SkipAll + } + return nil + } + err := filepath.WalkDir(root, walker) + if err != nil { + return "", err + } + return artifactPath, nil +} + // GetLocation given a dep, attempts to find line number, caches the line number for a given dep func (j *javaProvider) GetLocation(ctx context.Context, dep konveyor.Dep, file string) (engine.Location, error) { location := engine.Location{StartPosition: engine.Position{}, EndPosition: engine.Position{}} @@ -385,9 +557,9 @@ func (j *javaProvider) GetLocation(ctx context.Context, dep konveyor.Dep, file s return location, nil } -// resolveSourcesJars for a given source code location, runs maven to find +// resolveSourcesJarsForMaven for a given source code location, runs maven to find // deps that don't have sources attached and decompiles them -func resolveSourcesJars(ctx context.Context, log logr.Logger, location, mavenSettings string) error { +func resolveSourcesJarsForMaven(ctx context.Context, log logr.Logger, location, mavenSettings string) error { // TODO (pgaikwad): when we move to external provider, inherit context from parent ctx, span := tracing.StartNewSpan(ctx, "resolve-sources") defer span.End() @@ -455,6 +627,50 @@ func resolveSourcesJars(ctx context.Context, log logr.Logger, location, mavenSet return nil } +// parseUnresolvedSources takes the output from the download sources gradle task and returns the artifacts whose sources +// could not be found. Sample gradle output: +// Found 0 sources for :simple-jar: +// Found 1 sources for com.codevineyard:hello-world:1.0.1 +// Found 1 sources for org.codehaus.groovy:groovy:3.0.21 +func parseUnresolvedSourcesForGradle(output io.Reader) ([]javaArtifact, error) { + unresolvedSources := []javaArtifact{} + unresolvedRegex := regexp.MustCompile(`Found 0 sources for (.*)`) + artifactRegex := regexp.MustCompile(`(.+):(.+):(.+)|:(.+):`) + + scanner := bufio.NewScanner(output) + for scanner.Scan() { + line := scanner.Text() + + if match := unresolvedRegex.FindStringSubmatch(line); len(match) != 0 { + gav := artifactRegex.FindStringSubmatch(match[1]) + if gav[4] != "" { // internal library, unknown group/version + artifact := javaArtifact{ + ArtifactId: match[4], + } + unresolvedSources = append(unresolvedSources, artifact) + } else { // external dependency + artifact := javaArtifact{ + GroupId: gav[1], + ArtifactId: gav[2], + Version: gav[3], + } + unresolvedSources = append(unresolvedSources, artifact) + } + } + } + + // dedup artifacts + result := []javaArtifact{} + for _, artifact := range unresolvedSources { + if contains(result, artifact) { + continue + } + result = append(result, artifact) + } + + return result, scanner.Err() +} + // parseUnresolvedSources takes the output from the go-offline maven plugin and returns the artifacts whose sources // could not be found. func parseUnresolvedSources(output io.Reader) ([]javaArtifact, error) { diff --git a/external-providers/java-external-provider/pkg/java_external_provider/service_client.go b/external-providers/java-external-provider/pkg/java_external_provider/service_client.go index 1749b7cb..ccb83704 100644 --- a/external-providers/java-external-provider/pkg/java_external_provider/service_client.go +++ b/external-providers/java-external-provider/pkg/java_external_provider/service_client.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "os" "os/exec" "path/filepath" "regexp" @@ -205,6 +206,8 @@ func (p *javaServiceClient) initialization(ctx context.Context) { params.ExtendedClientCapilities = map[string]interface{}{ "classFileContentsSupport": true, } + // See https://github.com/eclipse-jdtls/eclipse.jdt.ls/blob/1a3dd9323756113bf39cfab82746d57a2fd19474/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java + java8home := os.Getenv("JAVA8_HOME") params.InitializationOptions = map[string]interface{}{ "bundles": absBundles, "workspaceFolders": []string{fmt.Sprintf("file://%v", absLocation)}, @@ -221,6 +224,13 @@ func (p *javaServiceClient) initialization(ctx context.Context) { "maven": map[string]interface{}{ "downloadSources": downloadSources, }, + "import": map[string]interface{}{ + "gradle": map[string]interface{}{ + "java": map[string]interface{}{ + "home": java8home, + }, + }, + }, }, }, } diff --git a/external-providers/java-external-provider/pkg/java_external_provider/util.go b/external-providers/java-external-provider/pkg/java_external_provider/util.go index d8bb0f75..1952e824 100644 --- a/external-providers/java-external-provider/pkg/java_external_provider/util.go +++ b/external-providers/java-external-provider/pkg/java_external_provider/util.go @@ -131,9 +131,11 @@ func decompile(ctx context.Context, log logr.Logger, filter decompileFilter, wor "failed to create directories for decompiled file", "path", outputPathDir) continue } + // multiple java versions may be installed - chose $JAVA_HOME one + java := filepath.Join(os.Getenv("JAVA_HOME"), "bin", "java") // -mpm (max processing method) is required to keep decomp time low cmd := exec.CommandContext( - jobCtx, "java", "-jar", "/bin/fernflower.jar", "-mpm=30", job.inputPath, outputPathDir) + jobCtx, java, "-jar", "/bin/fernflower.jar", "-mpm=30", job.inputPath, outputPathDir) err := cmd.Run() if err != nil { log.V(5).Error(err, "failed to decompile file", "file", job.inputPath, job.outputPath) @@ -385,7 +387,7 @@ func explode(ctx context.Context, log logr.Logger, archivePath, projectPath stri artifactPath := filepath.Join(strings.Split(dep.ArtifactId, ".")...) destPath := filepath.Join(m2Repo, groupPath, artifactPath, dep.Version, filepath.Base(filePath)) - if err := copyFile(filePath, destPath); err != nil { + if err := CopyFile(filePath, destPath); err != nil { log.V(8).Error(err, "failed copying jar to m2 local repo") } else { log.V(8).Info("copied jar file", "src", filePath, "dest", destPath) @@ -433,7 +435,7 @@ func createJavaProject(ctx context.Context, dir string, dependencies []javaArtif } func moveFile(srcPath string, destPath string) error { - err := copyFile(srcPath, destPath) + err := CopyFile(srcPath, destPath) if err != nil { return err } @@ -444,7 +446,7 @@ func moveFile(srcPath string, destPath string) error { return nil } -func copyFile(srcPath string, destPath string) error { +func CopyFile(srcPath string, destPath string) error { if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil { return err } @@ -465,6 +467,29 @@ func copyFile(srcPath string, destPath string) error { return nil } +func AppendToFile(src string, dst string) error { + // Read the contents of the source file + content, err := os.ReadFile(src) + if err != nil { + return fmt.Errorf("error reading source file: %s", err) + } + + // Open the destination file in append mode + destFile, err := os.OpenFile(dst, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("error opening destination file: %s", err) + } + defer destFile.Close() + + // Append the content to the destination file + _, err = destFile.Write(content) + if err != nil { + return fmt.Errorf("error apending to destination file: %s", err) + } + + return nil +} + // toDependency returns javaArtifact constructed for a jar func toDependency(ctx context.Context, jarFile string) (javaArtifact, error) { // attempt to lookup java artifact in maven diff --git a/provider_container_settings.json b/provider_container_settings.json index 169a3b09..56c63780 100644 --- a/provider_container_settings.json +++ b/provider_container_settings.json @@ -101,6 +101,16 @@ "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" }, "analysisMode": "source-only" + }, + { + "location": "/analyzer-lsp/examples/gradle-multi-project-example", + "providerSpecificConfig": { + "lspServerName": "java", + "lspServerPath": "/jdtls/bin/jdtls", + "depOpenSourceLabelsFile": "/usr/local/etc/maven.default.index", + "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" + }, + "analysisMode": "source-only" } ] }, diff --git a/provider_local_external_images.json b/provider_local_external_images.json index e21c2f92..92a5129c 100644 --- a/provider_local_external_images.json +++ b/provider_local_external_images.json @@ -101,6 +101,16 @@ "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" }, "analysisMode": "source-only" + }, + { + "location": "examples/gradle-multi-project-example", + "providerSpecificConfig": { + "lspServerName": "java", + "lspServerPath": "/jdtls/bin/jdtls", + "depOpenSourceLabelsFile": "/usr/local/etc/maven.default.index", + "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" + }, + "analysisMode": "source-only" } ] }, diff --git a/provider_pod_local_settings.json b/provider_pod_local_settings.json index d62198a4..141257da 100644 --- a/provider_pod_local_settings.json +++ b/provider_pod_local_settings.json @@ -101,6 +101,16 @@ "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" }, "analysisMode": "source-only" + }, + { + "location": "/analyzer-lsp/examples/gradle-multi-project-example", + "providerSpecificConfig": { + "lspServerName": "java", + "lspServerPath": "/jdtls/bin/jdtls", + "depOpenSourceLabelsFile": "/usr/local/etc/maven.default.index", + "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" + }, + "analysisMode": "source-only" } ] }, diff --git a/rule-example.yaml b/rule-example.yaml index 4d614909..929d10ff 100644 --- a/rule-example.yaml +++ b/rule-example.yaml @@ -291,4 +291,12 @@ pattern: inclusion-test.xml - builtin.filecontent: pattern: "" - \ No newline at end of file +- category: mandatory + description: | + This rule looks for a class only present in the gradle project + effort: 3 + message: Only incidents in gradle project should appear + ruleID: java-gradle-project + when: + java.referenced: + pattern: com.sun.net.httpserver.HttpExchange