From 7c45589a71635f6a4200fbbc68b4708cc07f70ab Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Mon, 19 Aug 2024 17:10:27 -0400 Subject: [PATCH] fix(gradle): track childProjects in properties report (#27488) ## Current Behavior ## Expected Behavior ## Related Issue(s) Fixes https://github.com/nrwl/nx/issues/27160 --- packages/gradle/src/plugin/dependencies.ts | 13 +++ packages/gradle/src/plugin/nodes.spec.ts | 3 + ...radle-properties-report-child-projects.txt | 109 ++++++++++++++++++ .../src/utils/get-gradle-report.spec.ts | 12 ++ .../gradle/src/utils/get-gradle-report.ts | 13 +++ 5 files changed, 150 insertions(+) create mode 100644 packages/gradle/src/utils/__mocks__/gradle-properties-report-child-projects.txt diff --git a/packages/gradle/src/plugin/dependencies.ts b/packages/gradle/src/plugin/dependencies.ts index 1513c28d0e8cc..42e0d36d52a8d 100644 --- a/packages/gradle/src/plugin/dependencies.ts +++ b/packages/gradle/src/plugin/dependencies.ts @@ -29,6 +29,7 @@ export const createDependencies: CreateDependencies = async ( gradleFileToGradleProjectMap, gradleProjectToProjectName, buildFileToDepsMap, + gradleProjectToChildProjects, } = getCurrentGradleReport(); const dependencies: Set = new Set(); @@ -47,6 +48,18 @@ export const createDependencies: CreateDependencies = async ( dependencies ); } + gradleProjectToChildProjects.get(gradleProject)?.forEach((childProject) => { + if (childProject) { + const dependency: RawProjectGraphDependency = { + source: projectName as string, + target: childProject, + type: DependencyType.static, + sourceFile: gradleFile, + }; + validateDependency(dependency, context); + dependencies.add(dependency); + } + }); } const gradleDependenciesEnd = performance.mark('gradleDependencies:end'); diff --git a/packages/gradle/src/plugin/nodes.spec.ts b/packages/gradle/src/plugin/nodes.spec.ts index 525f242dd0fe6..60eb322861dd9 100644 --- a/packages/gradle/src/plugin/nodes.spec.ts +++ b/packages/gradle/src/plugin/nodes.spec.ts @@ -34,6 +34,7 @@ describe('@nx/gradle/plugin', () => { ['proj', new Map([['test', 'Verification']])], ]), gradleProjectToProjectName: new Map([['proj', 'proj']]), + gradleProjectToChildProjects: new Map(), }; cwd = process.cwd(); process.chdir(tempFs.tempDir); @@ -135,6 +136,7 @@ describe('@nx/gradle/plugin', () => { ['proj', new Map([['test', 'Verification']])], ]), gradleProjectToProjectName: new Map([['proj', 'proj']]), + gradleProjectToChildProjects: new Map(), }; await tempFs.createFiles({ 'nested/nested/proj/build.gradle': ``, @@ -216,6 +218,7 @@ describe('@nx/gradle/plugin', () => { ['proj', new Map([['test', 'Test']])], ]), gradleProjectToProjectName: new Map([['proj', 'proj']]), + gradleProjectToChildProjects: new Map(), }; await tempFs.createFiles({ 'nested/nested/proj/build.gradle': ``, diff --git a/packages/gradle/src/utils/__mocks__/gradle-properties-report-child-projects.txt b/packages/gradle/src/utils/__mocks__/gradle-properties-report-child-projects.txt new file mode 100644 index 0000000000000..84929bc953765 --- /dev/null +++ b/packages/gradle/src/utils/__mocks__/gradle-properties-report-child-projects.txt @@ -0,0 +1,109 @@ + +------------------------------------------------------------ +Root project 'My Application' +------------------------------------------------------------ + +allprojects: [root project 'My Application', project ':app', project ':mylibrary'] +android.nonTransitiveRClass: true +android.useAndroidX: true +ant: org.gradle.api.internal.project.DefaultAntBuilder@ee42530 +antBuilderFactory: org.gradle.api.internal.project.DefaultAntBuilderFactory@5a5e3c76 +artifacts: org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler_Decorated@3793af5a +asDynamicObject: DynamicObject for root project 'My Application' +baseClassLoaderScope: org.gradle.api.internal.initialization.DefaultClassLoaderScope@4dc8adab +buildDir: /Users/emily/code/tmp/nx-android/build +buildFile: /Users/emily/code/tmp/nx-android/build.gradle +buildPath: : +buildScriptSource: org.gradle.groovy.scripts.TextResourceScriptSource@622acc87 +buildTreePath: : +buildscript: org.gradle.api.internal.initialization.DefaultScriptHandler_Decorated@493cf138 +childProjects: {app=project ':app', mylibrary=project ':mylibrary'} +childProjectsUnchecked: {app=project ':app', mylibrary=project ':mylibrary'} +class: class org.gradle.api.internal.project.DefaultProject_Decorated +classLoaderScope: org.gradle.api.internal.initialization.DefaultClassLoaderScope@72a593e2 +components: SoftwareComponent container +configurationActions: org.gradle.configuration.project.DefaultProjectConfigurationActionContainer@63ff7e3a +configurationTargetIdentifier: org.gradle.configuration.ConfigurationTargetIdentifier$1@6b52321b +configurations: configuration container +convention: org.gradle.internal.extensibility.DefaultConvention@5f289502 +conventionMapping: org.gradle.internal.extensibility.ConventionAwareHelper@71b30aee +crossProjectModelAccess: org.gradle.api.internal.project.DefaultCrossProjectModelAccess@69a5b44c +defaultTasks: [] +deferredProjectConfiguration: org.gradle.api.internal.project.DeferredProjectConfiguration@131d8189 +dependencies: org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler_Decorated@806966d +dependencyFactory: org.gradle.api.internal.artifacts.DefaultDependencyFactory@618b09f9 +dependencyLocking: org.gradle.internal.locking.DefaultDependencyLockingHandler_Decorated@2548d650 +dependencyMetaDataProvider: org.gradle.internal.service.scopes.ProjectScopeServices$ProjectBackedModuleMetaDataProvider@5a261ab9 +dependencyReport: task ':dependencyReport' +depth: 0 +description: null +detachedState: false +displayName: root project 'My Application' +ext: org.gradle.internal.extensibility.DefaultExtraPropertiesExtension@66af4a6e +extensions: org.gradle.internal.extensibility.DefaultConvention@5f289502 +fileOperations: org.gradle.api.internal.file.DefaultFileOperations@5f525383 +fileResolver: org.gradle.api.internal.file.BaseDirFileResolver@3c4be46f +gradle: build 'My Application' +group: +htmlDependencyReport: task ':htmlDependencyReport' +identityPath: : +inheritedScope: org.gradle.internal.extensibility.ExtensibleDynamicObject$InheritedDynamicObject@5652c28d +internalStatus: property(java.lang.Object, fixed(class java.lang.String, release)) +kotlin.code.style: official +kotlin.plugin.loaded.in.projects.1.9.0: :app +layout: org.gradle.api.internal.file.DefaultProjectLayout@750a8619 +libs: extension 'libs' +listenerBuildOperationDecorator: org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator@4233a086 +logger: org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger@27ef32e +logging: org.gradle.internal.logging.services.DefaultLoggingManager@5a33f0dd +model: root project 'My Application' +modelIdentityDisplayName: null +modelRegistry: org.gradle.model.internal.registry.DefaultModelRegistry@7b289fcc +name: My Application +normalization: org.gradle.normalization.internal.DefaultInputNormalizationHandler_Decorated@244027bf +objects: org.gradle.api.internal.model.DefaultObjectFactory@5152579a +org.gradle.jvmargs: -Xmx2048m -Dfile.encoding=UTF-8 +owner: root project 'My Application' +parent: null +parentIdentifier: null +path: : +pluginContext: false +pluginManager: org.gradle.api.internal.plugins.DefaultPluginManager_Decorated@14a2f075 +plugins: [org.gradle.api.plugins.HelpTasksPlugin$Inject@611c939a, org.gradle.buildinit.plugins.BuildInitPlugin$Inject@2cd76269, org.gradle.buildinit.plugins.WrapperPlugin$Inject@2009557, org.gradle.api.plugins.ReportingBasePlugin$Inject@75934caa, org.gradle.api.plugins.ProjectReportsPlugin$Inject@bc7a22e] +processOperations: org.gradle.process.internal.DefaultExecActionFactory$DecoratingExecActionFactory@2bcbdbd2 +project: root project 'My Application' +projectConfigurator: org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator@e136ccb +projectDir: /Users/emily/code/tmp/nx-android +projectEvaluationBroadcaster: ProjectEvaluationListener broadcast +projectEvaluator: org.gradle.configuration.project.LifecycleProjectEvaluator@4d3e0414 +projectPath: : +projectReport: task ':projectReport' +projectReportAll: task ':projectReportAll' +projectReportDir: /Users/emily/code/tmp/nx-android/build/reports/project +projectReportDirName: project +projects: [root project 'My Application'] +properties: {...} +propertyReport: task ':propertyReport' +providers: org.gradle.api.internal.provider.DefaultProviderFactory_Decorated@4e1c649e +publicType: org.gradle.api.plugins.ProjectReportsPluginConvention +reporting: extension 'reporting' +repositories: repository container +resources: org.gradle.api.internal.resources.DefaultResourceHandler@3bfc23c4 +rootDir: /Users/emily/code/tmp/nx-android +rootProject: root project 'My Application' +rootScript: false +script: false +scriptHandlerFactory: org.gradle.api.internal.initialization.DefaultScriptHandlerFactory@2e1c6544 +scriptPluginFactory: org.gradle.configuration.ScriptPluginFactorySelector@133fca37 +serviceRegistryFactory: org.gradle.internal.service.scopes.BuildScopeServiceRegistryFactory@3e2a745a +services: Project services +standardOutputCapture: org.gradle.internal.logging.services.DefaultLoggingManager@5a33f0dd +state: project state 'EXECUTED' +status: release +subprojects: [project ':app', project ':mylibrary'] +taskDependencyFactory: org.gradle.api.internal.tasks.DefaultTaskDependencyFactory@64128258 +taskReport: task ':taskReport' +taskThatOwnsThisObject: null +tasks: task set +version: unspecified +versionCatalogs: extension 'versionCatalogs' diff --git a/packages/gradle/src/utils/get-gradle-report.spec.ts b/packages/gradle/src/utils/get-gradle-report.spec.ts index 4a23efc768df6..04f746ffb0a7a 100644 --- a/packages/gradle/src/utils/get-gradle-report.spec.ts +++ b/packages/gradle/src/utils/get-gradle-report.spec.ts @@ -24,4 +24,16 @@ describe('processProjectReports', () => { Object.keys(Object.fromEntries(report.gradleProjectToTasksTypeMap)) ).toEqual(['', ':app', ':list', ':utilities']); }); + + it('should process properties report with child projects', () => { + const report = processProjectReports([ + '> Task :propertyReport', + `See the report at: file://${__dirname}/__mocks__/gradle-properties-report-child-projects.txt`, + ]); + expect(report.gradleProjectToProjectName.get('')).toEqual('My Application'); + expect(report.gradleProjectToChildProjects.get('')).toEqual([ + 'app', + 'mylibrary', + ]); + }); }); diff --git a/packages/gradle/src/utils/get-gradle-report.ts b/packages/gradle/src/utils/get-gradle-report.ts index 5b9406e6b591c..2dbc0e99d77c2 100644 --- a/packages/gradle/src/utils/get-gradle-report.ts +++ b/packages/gradle/src/utils/get-gradle-report.ts @@ -26,6 +26,7 @@ export interface GradleReport { gradleFileToOutputDirsMap: Map>; gradleProjectToTasksTypeMap: Map>; gradleProjectToProjectName: Map; + gradleProjectToChildProjects: Map; } let gradleReportCache: GradleReport; @@ -133,6 +134,10 @@ export function processProjectReports( * e.g. {build.gradle.kts: { projectReportDir: '' testReportDir: '' }} */ const gradleFileToOutputDirsMap = new Map>(); + /** + * Map of Gradle Project to its child projects + */ + const gradleProjectToChildProjects = new Map(); let index = 0; while (index < projectReportLines.length) { @@ -182,6 +187,13 @@ export function processProjectReports( if (line.startsWith('buildDir: ')) { absBuildDirPath = line.substring('buildDir: '.length); } + if (line.startsWith('childProjects: ')) { + const childProjects = line.substring('childProjects: {'.length); // remove curly braces {} around childProjects + gradleProjectToChildProjects.set( + gradleProject, + childProjects.split(',').map((c) => c.trim().split('=')[0]) // e.g. get project name from text like "app=project ':app', mylibrary=project ':mylibrary'" + ); + } if (line.includes('Dir: ')) { const [dirName, dirPath] = line.split(': '); const taskName = dirName.replace('Dir', ''); @@ -260,5 +272,6 @@ export function processProjectReports( gradleFileToOutputDirsMap, gradleProjectToTasksTypeMap, gradleProjectToProjectName, + gradleProjectToChildProjects, }; }