Gradle is a polyglot tool mostly used by JVM projects. It's popular with Java and Android projects.
Gradle users generally specify their builds using a build.gradle
file (written in Groovy) or a build.gradle.kts
file (written in Kotlin). These builds are specified as programs that must be dynamically evaluated.
This strategy requires dynamic analysis, which requires a CI integration. |
- Gradle
Most sizable Gradle builds organize their dependencies with two concepts: subprojects and configurations.
Subprojects are used when you have multiple "projects" in a single Gradle build (e.g. having multiple projects in a single repository managed by with single settings.gradle
and one or more build.gradle
files). Gradle calls these "multi-projects". Gradle multi-projects have one root project, and one or more subprojects.
A single subproject roughly corresponds to a single set of outputs. Hence, we treat subprojects as separate analysis targets.
For details, see Creating a multi-project build.
Within a single subproject, Gradle builds can declare dependencies for different "scopes" (i.e. different contexts, such as compilation, test execution, or runtime execution).
Gradle calls these scopes configurations. Examples of configurations include implementation
, testRuntime
, or compileClasspath
.
Different subprojects can have different configurations. Configurations are specific to a subproject, although there are many common configurations (e.g. compileClasspath
) that most subprojects have.
For more details, see What are dependency configurations.
Each pair of (subproject, configuration)
corresponds to one dependency graph.
Ideally, each (subproject, configuration)
pair would be a separate analysis target. For technical reasons, the current implementation treats separate subprojects as analysis targets, and treats each subproject as the union of all of its configurations.
In practice, this should not affect returned dependency results.
Instead of invoking gradle
directly, most Gradle projects use a "Gradle wrapper", which is a shell script vendored in the project that selects and downloads the correct version of Gradle. See The Gradle Wrapper for details.
Because of this, the Gradle analyzer has logic for selecting which gradle
executable to actually use. See Running Gradle for details.
This strategy requires dynamic analysis in its discovery phase (not just in the analysis phase). This is because we need to execute Gradle in order to list subprojects and evaluate build.gradle
files.
When executing Gradle for an analysis target at directory ANALYSIS_TARGET_DIR
, the CLI will prefer (in order):
gradlew
. First looking inANALYSIS_TARGET_DIR
and then recursively searching parent directories untilgradlew
is found.gradlew.bat
. First looking inANALYSIS_TARGET_DIR
and then recursively searching parent directories untilgradlew.bat
is found.gradle
(from$PATH
)
For more details, see Gradle wrappers.
In this documentation below, for brevity, we'll always refer to the selected tool as gradle
.
This strategy discovers analysis targets by looking for files in the folder being analyzed whose names start with build.gradle
. This matches both build.gradle
as well as build.gradle.kts
and Gradle build scripts in other Gradle-supported languages (build.gradle.*
).
It then executes gradle projects
in the directory where the build file is found to get a list of subprojects for this Gradle build. These subprojects are used to create the analysis targets for this Gradle build.
If there are no subprojects, an analysis target is created that analyzes the root project. Otherwise, a set of analysis targets is created: one for each Gradle subproject.
This strategy selects tactics by trying them in preference order and uses the results of the first tactic to succeed.
The order of tactics for this strategy is:
- Gradle build plugin
- Parsing
gradle :dependencies
(not yet implemented)
✔️ | This tactic reports dependencies for all subprojects. |
✔️ | This tactic provides a graph for subproject dependencies. |
This tactic requires dynamic analysis. |
This tactic runs a Gradle init script to output the dependencies in each Gradle subproject. Mechanically, this tactic:
- Unpacks our init script to a temporary directory.
- Invokes the init script with
gradle jsonDeps -Ipath/to/init.gradle
. - Parses the JSON output of the init script.
This init script is implemented here and bundled into the CLI during compilation.
The script works by iterating through configurations, resolving their dependencies, and then serializing those dependencies into JSON.
❌ | This tactic is not yet implemented. |
This tactic requires dynamic analysis. |
This not-yet-implemented tactic will execute gradle $SUBPROJECT:dependencies
for each analysis target, and parse the tool's output.
To determine whether the CLI is properly detecting your Gradle project, run fossa list-targets
. The output of this command is a list of analysis targets, in the format type@path
.
For each of your Gradle subprojects, you should see a gradle@PATH_TO_ROOT_PROJECT:SUBPROJECT
target in the list of analysis targets.
If you don't see this, one of two things is likely happening:
- Your Gradle project does not have a
build.gradle
file. This is an unsupported configuration. gradle projects
is failing to execute. Make sure that a Gradle wrapper is accessible (see Running Gradle), and make suregradle projects
runs successfully.
To manually verify the correctness of the CLI's results, run gradle :dependencies
in your root project, and gradle $SUBPROJECT:dependencies
for each subproject.
The CLI should produce a graph of dependencies that's a union of the dependencies of each subproject.
If your CLI run uploads versions that differ from the output of gradle $SUBPROJECT:dependencies
, check to make sure that the subproject dependency's version is the actual version resolved across the entire Gradle build. Different Gradle subprojects may select different dependency versions when resolved independently, but will select a single resolved version when the build is resolved as a whole.
If you'd like to make a bug report about incorrect dependencies, make sure to include the list of incorrect dependencies, as well as the commands you ran to obtain that list.
The Gradle build plugin is a Gradle init script implemented here.
If this tactic doesn't appear to be working (e.g. is giving you incorrect dependencies or is missing dependencies), you can run the init script directly using:
gradle -I$PATH_TO_SCRIPT jsonDeps
For example, with the script extracted to /tmp/jsondeps.gradle
, you should run (from within the Gradle build script's working directory):
gradle -I/tmp/jsondeps.gradle jsonDeps
Providing this output with a bug report will help us debug issues with the analysis.
If the CLI doesn't natively integrate with your build tool (e.g. if you have a homegrown tool), and your build tool uses Gradle dependencies, you can still manually add Gradle dependencies to an uploaded build. This feature is generally known as manual dependencies
Gradle in particular actually uploads Maven dependencies, since most Gradle builds use Gradle's Maven interoperability to get dependencies from Maven repositories.
An example configuration file looks like:
# fossa-deps.yml
referenced-dependencies:
- type: maven
name: javax.xml.bind:jaxb-api
version: 1.0.0
Notice that the name
field follows Maven conventions: groupId:artifactId
.
For more details, see the manual dependencies documentation.
We classify following configurations are for development:
- compileOnly
and following are for testing:
- testImplementation
- testCompileOnly
- testRuntimeOnly
- testCompileClasspath
- testRuntimeClasspath
We classify following configurations, and any dependencies originating from it as a test environment dependency:
Any dependencies with following prefixes:
- androidTest
- debugAndroidTest
- releaseUnitTest
And any configuration named:
- androidJacocoAnt
- testApiDependenciesMetadata
- testCompileOnlyDependenciesMetadata
- debugUnitTestApiDependenciesMetadata
- debugUnitTestCompileOnlyDependenciesMetadata
- debugUnitTestImplementationDependenciesMetadata
- debugUnitTestIntransitiveDependenciesMetadata
- debugUnitTestRuntimeOnlyDependenciesMetadata
- testDebugApiDependenciesMetadata
- testDebugCompileOnlyDependenciesMetadata
- testDebugImplementationDependenciesMetadata
- testDebugIntransitiveDependenciesMetadata
- testDebugRuntimeOnlyDependenciesMetadata
- testImplementationDependenciesMetadata
- testIntransitiveDependenciesMetadata
- testReleaseApiDependenciesMetadata
- testReleaseCompileOnlyDependenciesMetadata
- testReleaseImplementationDependenciesMetadata
- testReleaseIntransitiveDependenciesMetadata
- testReleaseRuntimeOnlyDependenciesMetadata
- testRuntimeOnlyDependenciesMetadata
- debugUnitTestAnnotationProcessorClasspath
We classify following configurations, and dependencies originating from it as a development (or debug) environment dependency:
- lintChecks
- lintClassPath
- lintPublish
- debugApiDependenciesMetadata
- debugCompileClasspath
- debugCompileOnly
- debugCompileOnlyDependenciesMetadata
- debugImplementationDependenciesMetadata
- debugIntransitiveDependenciesMetadata
- debugReverseMetadataValues
- debugRuntimeClasspath
- debugRuntimeOnlyDependenciesMetadata
- compileOnlyDependenciesMetadata
- releaseCompileOnly
- releaseCompileOnlyDependenciesMetadata
- debugWearBundling
- debugAnnotationProcessorClasspath
- releaseAnnotationProcessorClasspath
- androidJdkImage
- kotlinCompilerClasspath
- kotlinCompilerPluginClasspathDebug
- kotlinCompilerPluginClasspathDebugAndroidTest
- kotlinCompilerPluginClasspathDebugUnitTest
- kotlinCompilerPluginClasspathReleaseUnitTest
- kotlinCompilerPluginClasspathRelease
- kotlinKlibCommonizerClasspath
- kotlinNativeCompilerPluginClasspath
You can use configuration file to provide set of configurations to filter the analysis for. Any configurations not listed will be excluded from analysis. This feature is experimental and may be changed or removed at any time, without warning.
version: 3
experimental:
gradle:
configurations-only:
- example-1-config-to-include
- example-2-config-to-include