Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f1528f2
IDETECT-4812 - refactor gradle init script
bd-samratmuk Sep 1, 2025
84672c7
IDETECT-4812 - refactor gradle init script
bd-samratmuk Sep 2, 2025
faa7342
IDETECT-4812 - refactor gradle init script
bd-samratmuk Sep 2, 2025
9ff2d45
IDETECT-4812 - refactor gradle init script
bd-samratmuk Sep 2, 2025
94725ad
IDETECT-4812 - refactor gradle init script
bd-samratmuk Sep 2, 2025
3678d0c
IDETECT-4812 - refactor gradle init script
bd-samratmuk Sep 3, 2025
a40e26b
IDETECT-4812 - refactor gradle init script
bd-samratmuk Sep 3, 2025
4614667
Prep doc for 11.0.0 and fix nuget note
cpottsbd Aug 27, 2025
aeba3a2
Update nuget.md
cpottsbd Aug 28, 2025
dac370b
remove deprecated properties and values
dterrybd Sep 2, 2025
457a8a9
add release notes
dterrybd Sep 2, 2025
1f07694
Release 11.0.0-SIGQA3
Sep 2, 2025
52511bb
Using the next snapshot post release 11.0.0-SIGQA4-SNAPSHOT
Sep 2, 2025
fed5e80
add deep license project fields
dterrybd Aug 8, 2025
3d29cdb
update to latest blackduck-common
dterrybd Aug 26, 2025
4982f32
deep license for project creation
dterrybd Aug 26, 2025
6816b4e
allow for project updates
dterrybd Aug 29, 2025
3fd97a9
add basic tests
dterrybd Aug 29, 2025
cec5310
use builder pattern
dterrybd Aug 29, 2025
8b029cb
add release note
dterrybd Sep 2, 2025
b247c92
update release notes
dterrybd Sep 2, 2025
7c3ef46
IDETECT-4799: Ignore the toolchain module when putting together the d…
shantyk Sep 4, 2025
32f78b5
Add release note for IDETECT-4799 (#1532)
shantyk Sep 5, 2025
f829dbd
IDETECT-4751: Normalize IAC scan results file to NFD before upload (#…
shantyk Sep 8, 2025
0a4b538
fix(gradle): Refactored init-script-gradle.ftl to add support for con…
bd-samratmuk Sep 9, 2025
12736f5
Merge branch 'master' into IDETECT-4812a
bd-samratmuk Sep 9, 2025
94913e1
Merge branch 'master' into IDETECT-4812a
bd-samratmuk Sep 11, 2025
4efddb7
fix(gradle): Remove redundant try-catch (IDETECT-4812)
bd-samratmuk Sep 12, 2025
1b0851f
fix(gradle): Edit currentreleasenotes.md (IDETECT-4812)
bd-samratmuk Sep 12, 2025
3e637d4
fix(gradle): throw errors in the catch block allowing the script to e…
bd-samratmuk Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion documentation/src/main/markdown/currentreleasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
* (IDETECT-4751) Prevent server-side parsing errors by normalizing IAC Scan `results.json` contents before uploading to Black Duck SCA.
* (IDETECT-4799) When constructing the BDIO, ignore the Go toolchain directive, as it is the Go project's build-time configuration setting and not a module dependency.
* (IDETECT-4813) Fix Gradle Native Inspector to correctly identify projects with only settings.gradle or settings.gradle.kts file in the root directory.
* (IDETECT-4812) Gradle Native Inspector now supports configuration cache (refactored init-detect.gradle to add support for configuration cache in Gradle projects).


### Dependency updates

*
*
326 changes: 194 additions & 132 deletions src/main/resources/init-script-gradle.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -13,174 +13,236 @@ Set<String> projectNameIncludeFilter = convertStringToSet('${includedProjectName
Set<String> projectPathExcludeFilter = convertStringToSet('${excludedProjectPaths}')
Set<String> projectPathIncludeFilter = convertStringToSet('${includedProjectPaths}')
Boolean rootOnly = Boolean.parseBoolean("${rootOnlyOption}")

gradle.allprojects {
// add a new task to each project to start the process of getting the dependencies
task gatherDependencies(type: DefaultTask) {
// Store project name during configuration phase
def projectName = project.name
doLast {
println "Gathering dependencies for " + project.name
println "Gathering dependencies for " + projectName
}
}
afterEvaluate { project ->
// after a project has been evaluated modify the dependencies task for that project to output to a specific file.
project.tasks.getByName('dependencies') {
ext {
excludedProjectNames = '${excludedProjectNames}'
includedProjectNames = '${includedProjectNames}'
excludedConfigurationNames = '${excludedConfigurationNames}'
includedConfigurationNames = '${includedConfigurationNames}'
outputDirectoryPath = System.getProperty('GRADLEEXTRACTIONDIR')

afterEvaluate { currentProject ->
// Capture all needed project properties during configuration
def projectPath = currentProject.path
def projectName = currentProject.name
def isRootProject = isRoot(currentProject)
def extractionDir = System.getProperty('GRADLEEXTRACTIONDIR')
def projectDir = currentProject.projectDir
def projectDirPath = projectDir.canonicalPath

// Root project properties
def rootProject = currentProject.gradle.rootProject
def rootProjectName = rootProject.name
def rootProjectPath = rootProject.path
def rootProjectGroup = rootProject.group.toString()
def rootProjectVersion = rootProject.version.toString()
def rootProjectDir = rootProject.projectDir
def rootProjectDirPath = rootProjectDir.canonicalPath

// Project metadata
def projectGroup = currentProject.group.toString()
def projectVersion = currentProject.version.toString()
def projectParent = currentProject.parent ? currentProject.parent.toString() : "none"

// Prepare configuration names
def configurationNames = getFilteredConfigurationNames(currentProject,
'${excludedConfigurationNames}', '${includedConfigurationNames}')

def selectedConfigs = []
configurationNames.each { name ->
try {
def config = currentProject.configurations.findByName(name)
if (config) {
selectedConfigs.add(config)
}
} catch (Exception e) {
println "Could not process configuration: " + name
throw e
}
doFirst {
generateRootProjectMetaData(project, outputDirectoryPath)
}

if((rootOnly && isRoot(project)) || (!rootOnly && shouldInclude(projectNameExcludeFilter, projectNameIncludeFilter, project.name) && shouldInclude(projectPathExcludeFilter, projectPathIncludeFilter, project.path)) ) {
def dependencyTask = project.tasks.getByName('dependencies')
File projectOutputFile = findProjectOutputFile(project, outputDirectoryPath)
File projectFile = createProjectOutputFile(projectOutputFile)
// Check if the project should be included in results
def shouldIncludeProject = (rootOnly && isRootProject) ||
(!rootOnly && shouldInclude(projectNameExcludeFilter, projectNameIncludeFilter, projectName) &&
shouldInclude(projectPathExcludeFilter, projectPathIncludeFilter, projectPath))

if(dependencyTask.metaClass.respondsTo(dependencyTask, "setConfigurations")) {
println "Updating configurations for task"
// modify the configurations for the dependency task
setConfigurations(filterConfigurations(project, excludedConfigurationNames, includedConfigurationNames))
// Capture output file path during configuration
def projectFilePathConfig = computeProjectFilePath(projectPath, extractionDir)

} else {
println "Could not find method 'setConfigurations'"
}
// Configure the dependencies task during configuration time
def dependenciesTask = currentProject.tasks.getByName('dependencies')

// Set the configurations at configuration time if possible
if (!selectedConfigs.isEmpty()) {
dependenciesTask.configurations = selectedConfigs
Copy link
Preview

Copilot AI Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting task properties directly with Configuration objects can cause serialization issues with configuration cache. Consider using configuration names and resolving configurations during task execution instead.

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

@bd-samratmuk bd-samratmuk Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting configuration object properties during configuration phase is not causing serialization issues as tested with multiple projects. I assume this is what Gradle wants for now.

}

if(dependencyTask.metaClass.respondsTo(dependencyTask,"setOutputFile")) {
println "Updating output file for task to "+projectFile.getAbsolutePath()
// modify the output file
setOutputFile(projectFile)
} else {
println "Could not find method 'setOutputFile'"
// Set the output file at configuration time if possible
if (shouldIncludeProject) {
// Create output file directly during configuration
File projectFile = new File(projectFilePathConfig)
if (projectFile.exists()) {
projectFile.delete()
}
projectFile.createNewFile()
Comment on lines +86 to +90
Copy link
Preview

Copilot AI Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating and deleting files during the configuration phase can cause issues with configuration cache and parallel builds. File operations should be moved to task execution phase (doFirst/doLast) to avoid configuration cache violations and ensure proper build isolation.

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a known issue. Even though creating file should be part of execution phase but then setting configuration objects like outputFile to that created file, when done should be part of configuration phase. I would like to keep this as it is.


// Set the output file during configuration phase
try {
dependenciesTask.outputFile = projectFile
println "Set output file during configuration to " + projectFile.getAbsolutePath()
} catch (Exception e) {
println "Could not set outputFile property during configuration: " + e.message
e.printStackTrace()
throw e
}
}

dependenciesTask.doFirst {
try {
if (extractionDir == null) {
throw new IllegalStateException("GRADLEEXTRACTIONDIR system property is not set")
}

// Create metadata file for root project
if (isRootProject) {
try {
File outputDirectory = new File(extractionDir)
outputDirectory.mkdirs()
File rootOutputFile = new File(outputDirectory, 'rootProjectMetadata.txt')

def rootProjectMetadataPieces = []
rootProjectMetadataPieces.add('DETECT META DATA START')
rootProjectMetadataPieces.add("rootProjectDirectory:" + rootProjectDirPath)
rootProjectMetadataPieces.add("rootProjectPath:" + rootProjectPath)
rootProjectMetadataPieces.add("rootProjectGroup:" + rootProjectGroup)
rootProjectMetadataPieces.add("rootProjectName:" + rootProjectName)
rootProjectMetadataPieces.add("rootProjectVersion:" + rootProjectVersion)
rootProjectMetadataPieces.add('DETECT META DATA END')

rootOutputFile.text = rootProjectMetadataPieces.join('\n')
} catch (Exception e) {
println "ERROR while generating root project metadata: " + e.message
e.printStackTrace()
throw e
}
} else {
println "Excluding from results subproject: " + project.path
}
} catch (Exception e) {
println "ERROR in dependencies doFirst: " + e.message
e.printStackTrace()
throw e
}
}

doLast {
if((rootOnly && isRoot(project)) || (!rootOnly && shouldInclude(projectNameExcludeFilter, projectNameIncludeFilter, project.name) && shouldInclude(projectPathExcludeFilter, projectPathIncludeFilter, project.path))) {
File projectFile = findProjectOutputFile(project, outputDirectoryPath)
appendProjectMetadata(project, projectFile)
dependenciesTask.doLast {
try {
if(shouldIncludeProject) {
File projectFile = new File(projectFilePathConfig)

// Add metadata at the end of the file
def metaDataPieces = []
metaDataPieces.add('')
metaDataPieces.add('DETECT META DATA START')
metaDataPieces.add("rootProjectDirectory:" + rootProjectDirPath)
metaDataPieces.add("rootProjectGroup:" + rootProjectGroup)
metaDataPieces.add("rootProjectPath:" + rootProjectPath)
metaDataPieces.add("rootProjectName:" + rootProjectName)
metaDataPieces.add("rootProjectVersion:" + rootProjectVersion)
metaDataPieces.add("projectDirectory:" + projectDirPath)
metaDataPieces.add("projectGroup:" + projectGroup)
metaDataPieces.add("projectName:" + projectName)
metaDataPieces.add("projectVersion:" + projectVersion)
metaDataPieces.add("projectPath:" + projectPath)
metaDataPieces.add("projectParent:" + projectParent)
metaDataPieces.add('DETECT META DATA END')
metaDataPieces.add('')

// Append to file
projectFile << metaDataPieces.join('\n')
}
} catch (Exception e) {
println "ERROR in dependencies doLast: " + e.message
e.printStackTrace()
throw e
}
}
// this forces the dependencies task to be run which will write the content to the modified output file
project.gatherDependencies.finalizedBy(project.tasks.getByName('dependencies'))
project.gatherDependencies

// This forces the dependencies task to be run
currentProject.gatherDependencies.finalizedBy(currentProject.tasks.getByName('dependencies'))
currentProject.gatherDependencies
}
}

// ## START methods invoked by tasks above
<#-- Do not parse with Freemarker because Groovy variable replacement in template strings is the same as Freemarker template syntax. -->
<#noparse>
def isRoot(Project project) {
Project rootProject = project.gradle.rootProject;
return project.name.equals(rootProject.name)
}

def generateRootProjectMetaData(Project project, String outputDirectoryPath) {
File outputDirectory = createTaskOutputDirectory(outputDirectoryPath)
outputDirectory.mkdirs()

Project rootProject = project.gradle.rootProject;
/* if the current project is the root project then generate the file containing
the meta data for the root project otherwise ignore.
*/
if (project.name.equals(rootProject.name)) {
File rootOutputFile = new File(outputDirectory, 'rootProjectMetadata.txt');
String rootProjectDirectory = rootProject.getProjectDir().getCanonicalPath()
String rootProjectPath = rootProject.path.toString()
String rootProjectGroup = rootProject.group.toString()
String rootProjectName = rootProject.name.toString()
String rootProjectVersionName = rootProject.version.toString()

def rootProjectMetadataPieces = []
rootProjectMetadataPieces.add('DETECT META DATA START')
rootProjectMetadataPieces.add("rootProjectDirectory:${rootProjectDirectory}")
rootProjectMetadataPieces.add("rootProjectPath:${rootProjectPath}")
rootProjectMetadataPieces.add("rootProjectGroup:${rootProjectGroup}")
rootProjectMetadataPieces.add("rootProjectName:${rootProjectName}")
rootProjectMetadataPieces.add("rootProjectVersion:${rootProjectVersionName}")
rootProjectMetadataPieces.add('DETECT META DATA END')
rootOutputFile << rootProjectMetadataPieces.join('\n')
try {
Project rootProject = project.gradle.rootProject;
return project.name.equals(rootProject.name)
} catch (Exception e) {
println "ERROR in isRoot: " + e.message
e.printStackTrace()
throw e
}
}

def findProjectOutputFile(Project project, String outputDirectoryPath) {
File outputDirectory = createTaskOutputDirectory(outputDirectoryPath)
String name = project.toString()

int depthCount = 0
for(char c: name.toCharArray()) {
if (c == ':') {
depthCount++
}
}
String depth = String.valueOf(depthCount)
// Get path for project file
def computeProjectFilePath(String projectPath, String outputDirectoryPath) {
try {
File outputDirectory = createTaskOutputDirectory(outputDirectoryPath)
String name = projectPath ?: ""

String nameForFile = name?.replaceAll(/[^\p{IsAlphabetic}\p{Digit}]/, "_")
File outputFile = new File(outputDirectory, "${nameForFile}_depth${depth}_dependencyGraph.txt")

outputFile
}

def filterConfigurations(Project project, String excludedConfigurationNames, String includedConfigurationNames) {
Set<String> configurationExcludeFilter = convertStringToSet(excludedConfigurationNames)
Set<String> configurationIncludeFilter = convertStringToSet(includedConfigurationNames)
Set<Configuration> filteredConfigurationSet = new TreeSet<Configuration>(new Comparator<Configuration>() {
public int compare(Configuration conf1, Configuration conf2) {
return conf1.getName().compareTo(conf2.getName());
int depthCount = 0
for(char c: name.toCharArray()) {
if (c == ':') {
depthCount++
}
}
})
for (Configuration configuration : project.configurations) {
if (shouldInclude(configurationExcludeFilter, configurationIncludeFilter, configuration.name)) {
filteredConfigurationSet.add(configuration)
String depth = String.valueOf(depthCount)
// Special case for root project
if (projectPath == ":") {
return new File(outputDirectory, "root_project_depth${depth}_dependencyGraph.txt").getAbsolutePath()
}
}
// Replace all non-alphanumeric characters with underscores

filteredConfigurationSet
String nameForFile = name?.replaceAll(/[^\p{IsAlphabetic}\p{Digit}]/, "_")
nameForFile = "project_" + nameForFile
return new File(outputDirectory, "${nameForFile}_depth${depth}_dependencyGraph.txt").getAbsolutePath()
} catch (Exception e) {
println "ERROR in computeProjectFilePath: " + e.message
e.printStackTrace()
throw e
}
}

def appendProjectMetadata(Project project, File projectOutputFile) {
Project rootProject = project.gradle.rootProject;
String rootProjectGroup = rootProject.group.toString()
String rootProjectName = rootProject.name.toString()
String rootProjectVersionName = rootProject.version.toString()
String rootProjectPath = rootProject.path.toString()
String group = project.group.toString()
String name = project.name.toString()
String version = project.version.toString()
String path = project.path.toString()

def metaDataPieces = []
metaDataPieces.add('')
metaDataPieces.add('DETECT META DATA START')
metaDataPieces.add("rootProjectDirectory:${rootProject.getProjectDir().getCanonicalPath()}")
metaDataPieces.add("rootProjectGroup:${rootProjectGroup}")
metaDataPieces.add("rootProjectPath:${rootProjectPath}")
metaDataPieces.add("rootProjectName:${rootProjectName}")
metaDataPieces.add("rootProjectVersion:${rootProjectVersionName}")
metaDataPieces.add("projectDirectory:${project.getProjectDir().getCanonicalPath()}")
metaDataPieces.add("projectGroup:${group}")
metaDataPieces.add("projectName:${name}")
metaDataPieces.add("projectVersion:${version}")
metaDataPieces.add("projectPath:${path}")
metaDataPieces.add("projectParent:${project.parent}")
metaDataPieces.add('DETECT META DATA END')
metaDataPieces.add('')

projectOutputFile << metaDataPieces.join('\n')
}
// Get only configuration names to avoid serialization issues
def getFilteredConfigurationNames(Project project, String excludedConfigurationNames, String includedConfigurationNames) {
try {
Set<String> configurationExcludeFilter = convertStringToSet(excludedConfigurationNames)
Set<String> configurationIncludeFilter = convertStringToSet(includedConfigurationNames)
Set<String> filteredNames = new TreeSet<String>()

def createProjectOutputFile(File projectFile) {
if (projectFile.exists()) {
projectFile.delete()
}
for (def configuration : project.configurations) {
try {
if (shouldInclude(configurationExcludeFilter, configurationIncludeFilter, configuration.name)) {
filteredNames.add(configuration.name)
}
} catch (Exception e) {
println "ERROR processing configuration " + configuration.name + ": " + e.message
throw e
}
}

projectFile.createNewFile()
projectFile
return filteredNames
} catch (Exception e) {
println "ERROR in getFilteredConfigurationNames: " + e.message
e.printStackTrace()
throw e
}
}

def createTaskOutputDirectory(String outputDirectoryPath) {
Expand Down Expand Up @@ -219,7 +281,7 @@ def wildCardTokenToRegexToken(String token) {
if(matcher.group(1) != null) {
matcher.appendReplacement(buffer, '.*')
} else if (matcher.group(2) != null) {
matcher.appendReplacement(buffer, ".");
matcher.appendReplacement(buffer, ".");
} else {
matcher.appendReplacement(buffer, '\\\\Q' + matcher.group(0) + '\\\\E')
}
Expand Down