-
Notifications
You must be signed in to change notification settings - Fork 80
fix(gradle): Refactored init-script-gradle.ftl to add support for configuration cache in gradle projects. (IDETECT-4812) #1531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f1528f2
84672c7
faa7342
9ff2d45
94725ad
3678d0c
a40e26b
4614667
aeba3a2
dac370b
457a8a9
1f07694
52511bb
fed5e80
3d29cdb
4982f32
6816b4e
3fd97a9
cec5310
8b029cb
b247c92
7c3ef46
32f78b5
f829dbd
0a4b538
12736f5
94913e1
4efddb7
1b0851f
3e637d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
} | ||
|
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
@@ -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') | ||
} | ||
|
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.