feat(core): able to import gradle project (#27645)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Emily Xiong 2024-09-13 14:28:59 -04:00 committed by GitHub
parent 5bbaffbda8
commit 61b3503619
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 876 additions and 164 deletions

View File

@ -0,0 +1,175 @@
import {
checkFilesExist,
cleanupProject,
getSelectedPackageManager,
newProject,
runCLI,
updateJson,
updateFile,
e2eCwd,
readJson,
} from '@nx/e2e/utils';
import { mkdirSync, rmdirSync, writeFileSync } from 'fs';
import { execSync } from 'node:child_process';
import { join } from 'path';
import { createGradleProject } from './utils/create-gradle-project';
import { createFileSync } from 'fs-extra';
describe('Nx Import Gradle', () => {
let proj: string;
const tempImportE2ERoot = join(e2eCwd, 'nx-import');
beforeAll(() => {
proj = newProject({
packages: ['@nx/js'],
unsetProjectNameAndRootFormat: false,
});
if (getSelectedPackageManager() === 'pnpm') {
updateFile(
'pnpm-workspace.yaml',
`packages:
- 'projects/*'
`
);
} else {
updateJson('package.json', (json) => {
json.workspaces = ['projects/*'];
return json;
});
}
try {
rmdirSync(tempImportE2ERoot);
} catch {}
mkdirSync(tempImportE2ERoot, { recursive: true });
});
afterAll(() => cleanupProject());
it('should be able to import a kotlin gradle app', () => {
const tempGradleProjectName = 'created-gradle-app-kotlin';
const tempGraldeProjectPath = join(
tempImportE2ERoot,
tempGradleProjectName
);
try {
rmdirSync(tempGraldeProjectPath);
} catch {}
mkdirSync(tempGraldeProjectPath, { recursive: true });
createGradleProject(
tempGradleProjectName,
'kotlin',
tempGraldeProjectPath,
'gradleProjectKotlin',
'kotlin-'
);
// Add project.json files to the gradle project to avoid duplicate project names
createFileSync(join(tempGraldeProjectPath, 'project.json'));
writeFileSync(
join(tempGraldeProjectPath, 'project.json'),
`{"name": "${tempGradleProjectName}"}`
);
execSync(`git init`, {
cwd: tempGraldeProjectPath,
});
execSync(`git add .`, {
cwd: tempGraldeProjectPath,
});
execSync(`git commit -am "initial commit"`, {
cwd: tempGraldeProjectPath,
});
execSync(`git checkout -b main`, {
cwd: tempGraldeProjectPath,
});
const remote = tempGraldeProjectPath;
const ref = 'main';
const source = '.';
const directory = 'projects/gradle-app-kotlin';
runCLI(
`import ${remote} ${directory} --ref ${ref} --source ${source} --no-interactive`,
{
verbose: true,
}
);
checkFilesExist(
`${directory}/settings.gradle.kts`,
`${directory}/build.gradle.kts`,
`${directory}/gradlew`,
`${directory}/gradlew.bat`
);
const nxJson = readJson('nx.json');
const gradlePlugin = nxJson.plugins.find(
(plugin) => plugin.plugin === '@nx/gradle'
);
expect(gradlePlugin).toBeDefined();
expect(() => {
runCLI(`show projects`);
runCLI('build kotlin-app');
}).not.toThrow();
});
it('should be able to import a groovy gradle app', () => {
const tempGradleProjectName = 'created-gradle-app-groovy';
const tempGraldeProjectPath = join(
tempImportE2ERoot,
tempGradleProjectName
);
try {
rmdirSync(tempGraldeProjectPath);
} catch {}
mkdirSync(tempGraldeProjectPath, { recursive: true });
createGradleProject(
tempGradleProjectName,
'groovy',
tempGraldeProjectPath,
'gradleProjectGroovy',
'groovy-'
);
// Add project.json files to the gradle project to avoid duplicate project names
createFileSync(join(tempGraldeProjectPath, 'project.json'));
writeFileSync(
join(tempGraldeProjectPath, 'project.json'),
`{"name": "${tempGradleProjectName}"}`
);
execSync(`git init`, {
cwd: tempGraldeProjectPath,
});
execSync(`git add .`, {
cwd: tempGraldeProjectPath,
});
execSync(`git commit -am "initial commit"`, {
cwd: tempGraldeProjectPath,
});
execSync(`git checkout -b main`, {
cwd: tempGraldeProjectPath,
});
const remote = tempGraldeProjectPath;
const ref = 'main';
const source = '.';
const directory = 'projects/gradle-app-groovy';
runCLI(
`import ${remote} ${directory} --ref ${ref} --source ${source} --no-interactive`,
{
verbose: true,
}
);
runCLI(`g @nx/gradle:init --no-interactive`);
checkFilesExist(
`${directory}/build.gradle`,
`${directory}/settings.gradle`,
`${directory}/gradlew`,
`${directory}/gradlew.bat`
);
expect(() => {
runCLI(`show projects`);
runCLI('build groovy-app');
}).not.toThrow();
});
});

View File

@ -2,17 +2,13 @@ import {
checkFilesExist, checkFilesExist,
cleanupProject, cleanupProject,
createFile, createFile,
e2eConsoleLogger,
isWindows,
newProject, newProject,
runCLI, runCLI,
runCommand,
tmpProjPath,
uniq, uniq,
updateFile, updateFile,
} from '@nx/e2e/utils'; } from '@nx/e2e/utils';
import { execSync } from 'child_process';
import { resolve } from 'path'; import { createGradleProject } from './utils/create-gradle-project';
describe('Gradle', () => { describe('Gradle', () => {
describe.each([{ type: 'kotlin' }, { type: 'groovy' }])( describe.each([{ type: 'kotlin' }, { type: 'groovy' }])(
@ -22,6 +18,7 @@ describe('Gradle', () => {
beforeAll(() => { beforeAll(() => {
newProject(); newProject();
createGradleProject(gradleProjectName, type); createGradleProject(gradleProjectName, type);
runCLI(`add @nx/gradle`);
}); });
afterAll(() => cleanupProject()); afterAll(() => cleanupProject());
@ -101,33 +98,3 @@ dependencies {
} }
); );
}); });
function createGradleProject(
projectName: string,
type: 'kotlin' | 'groovy' = 'kotlin'
) {
e2eConsoleLogger(`Using java version: ${execSync('java -version')}`);
const gradleCommand = isWindows()
? resolve(`${__dirname}/../gradlew.bat`)
: resolve(`${__dirname}/../gradlew`);
e2eConsoleLogger(
'Using gradle version: ' +
execSync(`${gradleCommand} --version`, {
cwd: tmpProjPath(),
})
);
e2eConsoleLogger(
execSync(`${gradleCommand} help --task :init`, {
cwd: tmpProjPath(),
}).toString()
);
e2eConsoleLogger(
runCommand(
`${gradleCommand} init --type ${type}-application --dsl ${type} --project-name ${projectName} --package gradleProject --no-incubating --split-project`,
{
cwd: tmpProjPath(),
}
)
);
runCLI(`add @nx/gradle`);
}

View File

@ -0,0 +1,59 @@
import {
e2eConsoleLogger,
isWindows,
runCommand,
tmpProjPath,
} from '@nx/e2e/utils';
import { execSync } from 'child_process';
import { createFileSync, writeFileSync } from 'fs-extra';
import { join, resolve } from 'path';
export function createGradleProject(
projectName: string,
type: 'kotlin' | 'groovy' = 'kotlin',
cwd: string = tmpProjPath(),
packageName: string = 'gradleProject',
addProjectJsonNamePrefix: string = ''
) {
e2eConsoleLogger(`Using java version: ${execSync('java -version')}`);
const gradleCommand = isWindows()
? resolve(`${__dirname}/../../gradlew.bat`)
: resolve(`${__dirname}/../../gradlew`);
e2eConsoleLogger(
'Using gradle version: ' +
execSync(`${gradleCommand} --version`, {
cwd,
})
);
e2eConsoleLogger(
execSync(`${gradleCommand} help --task :init`, {
cwd,
}).toString()
);
e2eConsoleLogger(
runCommand(
`${gradleCommand} init --type ${type}-application --dsl ${type} --project-name ${projectName} --package ${packageName} --no-incubating --split-project`,
{
cwd,
}
)
);
if (addProjectJsonNamePrefix) {
createFileSync(join(cwd, 'app/project.json'));
writeFileSync(
join(cwd, 'app/project.json'),
`{"name": "${addProjectJsonNamePrefix}app"}`
);
createFileSync(join(cwd, 'list/project.json'));
writeFileSync(
join(cwd, 'list/project.json'),
`{"name": "${addProjectJsonNamePrefix}list"}`
);
createFileSync(join(cwd, 'utilities/project.json'));
writeFileSync(
join(cwd, 'utilities/project.json'),
`{"name": "${addProjectJsonNamePrefix}utilities"}`
);
}
}

View File

@ -1,7 +1,7 @@
{ {
"name": "e2e-nx", "name": "e2e-nx",
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "e2e/nx-misc", "sourceRoot": "e2e/nx",
"projectType": "application", "projectType": "application",
"implicitDependencies": ["nx", "js"], "implicitDependencies": ["nx", "js"],
"// targets": "to see all targets run: nx show project e2e-nx --web", "// targets": "to see all targets run: nx show project e2e-nx --web",

View File

@ -9,7 +9,6 @@ import {
Tree, Tree,
updateNxJson, updateNxJson,
} from '@nx/devkit'; } from '@nx/devkit';
import { execSync } from 'child_process';
import { nxVersion } from '../../utils/versions'; import { nxVersion } from '../../utils/versions';
import { InitGeneratorSchema } from './schema'; import { InitGeneratorSchema } from './schema';
import { hasGradlePlugin } from '../../utils/has-gradle-plugin'; import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
@ -18,13 +17,6 @@ import { dirname, join, basename } from 'path';
export async function initGenerator(tree: Tree, options: InitGeneratorSchema) { export async function initGenerator(tree: Tree, options: InitGeneratorSchema) {
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
if (!tree.exists('settings.gradle') && !tree.exists('settings.gradle.kts')) {
logger.warn(`Could not find 'settings.gradle' or 'settings.gradle.kts' file in your gradle workspace.
A Gradle build should contain a 'settings.gradle' or 'settings.gradle.kts' file in its root directory. It may also contain a 'build.gradle' or 'build.gradle.kts' file.
Running 'gradle init':`);
execSync('gradle init', { stdio: 'inherit' });
}
if (!options.skipPackageJson && tree.exists('package.json')) { if (!options.skipPackageJson && tree.exists('package.json')) {
tasks.push( tasks.push(
addDependenciesToPackageJson( addDependenciesToPackageJson(

View File

@ -9,11 +9,9 @@ import {
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { basename, dirname } from 'node:path'; import { basename, dirname } from 'node:path';
import { import { getCurrentGradleReport } from '../utils/get-gradle-report';
GRADLE_BUILD_FILES, import { GRADLE_BUILD_FILES } from '../utils/split-config-files';
getCurrentGradleReport, import { newLineSeparator } from '../utils/get-project-report-lines';
newLineSeparator,
} from '../utils/get-gradle-report';
export const createDependencies: CreateDependencies = async ( export const createDependencies: CreateDependencies = async (
_, _,

View File

@ -116,6 +116,9 @@ describe('@nx/gradle/plugin', () => {
"gradle", "gradle",
], ],
}, },
"options": {
"cwd": ".",
},
}, },
}, },
}, },
@ -200,6 +203,9 @@ describe('@nx/gradle/plugin', () => {
"gradle", "gradle",
], ],
}, },
"options": {
"cwd": ".",
},
}, },
}, },
}, },
@ -310,6 +316,9 @@ describe('@nx/gradle/plugin', () => {
"gradle", "gradle",
], ],
}, },
"options": {
"cwd": ".",
},
}, },
"test-ci": { "test-ci": {
"cache": true, "cache": true,
@ -379,6 +388,9 @@ describe('@nx/gradle/plugin', () => {
"gradle", "gradle",
], ],
}, },
"options": {
"cwd": ".",
},
}, },
"test-ci--bTest": { "test-ci--bTest": {
"cache": true, "cache": true,
@ -406,6 +418,9 @@ describe('@nx/gradle/plugin', () => {
"gradle", "gradle",
], ],
}, },
"options": {
"cwd": ".",
},
}, },
"test-ci--cTests": { "test-ci--cTests": {
"cache": true, "cache": true,
@ -433,6 +448,9 @@ describe('@nx/gradle/plugin', () => {
"gradle", "gradle",
], ],
}, },
"options": {
"cwd": ".",
},
}, },
}, },
}, },

View File

@ -16,16 +16,18 @@ import { basename, dirname, join } from 'node:path';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { findProjectForPath } from 'nx/src/devkit-internals'; import { findProjectForPath } from 'nx/src/devkit-internals';
import { getGradleExecFile } from '../utils/exec-gradle';
import { import {
populateGradleReport, populateGradleReport,
getCurrentGradleReport, getCurrentGradleReport,
GradleReport, GradleReport,
gradleConfigGlob,
GRADLE_BUILD_FILES,
gradleConfigAndTestGlob,
} from '../utils/get-gradle-report'; } from '../utils/get-gradle-report';
import { hashObject } from 'nx/src/hasher/file-hasher'; import { hashObject } from 'nx/src/hasher/file-hasher';
import {
gradleConfigAndTestGlob,
gradleConfigGlob,
splitConfigFiles,
} from '../utils/split-config-files';
import { getGradleExecFile, findGraldewFile } from '../utils/exec-gradle';
const cacheableTaskType = new Set(['Build', 'Verification']); const cacheableTaskType = new Set(['Build', 'Verification']);
const dependsOnMap = { const dependsOnMap = {
@ -76,7 +78,8 @@ export function writeTargetsToCache(cachePath: string, results: GradleTargets) {
export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [ export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [
gradleConfigAndTestGlob, gradleConfigAndTestGlob,
async (files, options, context) => { async (files, options, context) => {
const { configFiles, projectRoots, testFiles } = splitConfigFiles(files); const { buildFiles, projectRoots, gradlewFiles, testFiles } =
splitConfigFiles(files);
const optionsHash = hashObject(options); const optionsHash = hashObject(options);
const cachePath = join( const cachePath = join(
workspaceDataDirectory, workspaceDataDirectory,
@ -84,7 +87,10 @@ export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [
); );
const targetsCache = readTargetsCache(cachePath); const targetsCache = readTargetsCache(cachePath);
await populateGradleReport(context.workspaceRoot); await populateGradleReport(
context.workspaceRoot,
gradlewFiles.map((f) => join(context.workspaceRoot, f))
);
const gradleReport = getCurrentGradleReport(); const gradleReport = getCurrentGradleReport();
const gradleProjectRootToTestFilesMap = getGradleProjectRootToTestFilesMap( const gradleProjectRootToTestFilesMap = getGradleProjectRootToTestFilesMap(
testFiles, testFiles,
@ -98,7 +104,7 @@ export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [
targetsCache, targetsCache,
gradleProjectRootToTestFilesMap gradleProjectRootToTestFilesMap
), ),
configFiles, buildFiles,
options, options,
context context
); );
@ -151,15 +157,16 @@ export const makeCreateNodesForGradleConfigFile =
*/ */
export const createNodes: CreateNodes<GradlePluginOptions> = [ export const createNodes: CreateNodes<GradlePluginOptions> = [
gradleConfigGlob, gradleConfigGlob,
async (configFile, options, context) => { async (buildFile, options, context) => {
logger.warn( logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.' '`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
); );
await populateGradleReport(context.workspaceRoot); const { gradlewFiles } = splitConfigFiles(context.configFiles);
await populateGradleReport(context.workspaceRoot, gradlewFiles);
const gradleReport = getCurrentGradleReport(); const gradleReport = getCurrentGradleReport();
const internalCreateNodes = const internalCreateNodes =
makeCreateNodesForGradleConfigFile(gradleReport); makeCreateNodesForGradleConfigFile(gradleReport);
return await internalCreateNodes(configFile, options, context); return await internalCreateNodes(buildFile, options, context);
}, },
]; ];
@ -234,13 +241,16 @@ async function createGradleTargets(
context: CreateNodesContext, context: CreateNodesContext,
outputDirs: Map<string, string>, outputDirs: Map<string, string>,
gradleProject: string, gradleProject: string,
gradleFilePath: string, gradleBuildFilePath: string,
testFiles: string[] = [] testFiles: string[] = []
): Promise<{ ): Promise<{
targetGroups: Record<string, string[]>; targetGroups: Record<string, string[]>;
targets: Record<string, TargetConfiguration>; targets: Record<string, TargetConfiguration>;
}> { }> {
const inputsMap = createInputsMap(context); const inputsMap = createInputsMap(context);
const gradlewFileDirectory = dirname(
findGraldewFile(gradleBuildFilePath, context.workspaceRoot)
);
const targets: Record<string, TargetConfiguration> = {}; const targets: Record<string, TargetConfiguration> = {};
const targetGroups: Record<string, string[]> = {}; const targetGroups: Record<string, string[]> = {};
@ -262,15 +272,20 @@ async function createGradleTargets(
outputs, outputs,
task.type, task.type,
targets, targets,
targetGroups targetGroups,
gradlewFileDirectory
); );
} }
const taskCommandToRun = `${gradleProject ? gradleProject + ':' : ''}${ const taskCommandToRun = `${gradleProject ? gradleProject + ':' : ''}${
task.name task.name
}`; }`;
targets[targetName] = { targets[targetName] = {
command: `${getGradleExecFile()} ${taskCommandToRun}`, command: `${getGradleExecFile()} ${taskCommandToRun}`,
options: {
cwd: gradlewFileDirectory,
},
cache: cacheableTaskType.has(task.type), cache: cacheableTaskType.has(task.type),
inputs: inputsMap[task.name], inputs: inputsMap[task.name],
dependsOn: dependsOnMap[task.name], dependsOn: dependsOnMap[task.name],
@ -320,7 +335,8 @@ function getTestCiTargets(
outputs: string[], outputs: string[],
targetGroupName: string, targetGroupName: string,
targets: Record<string, TargetConfiguration>, targets: Record<string, TargetConfiguration>,
targetGroups: Record<string, string[]> targetGroups: Record<string, string[]>,
gradlewFileDirectory: string
): void { ): void {
if (!testFiles || testFiles.length === 0 || !ciTargetName) { if (!testFiles || testFiles.length === 0 || !ciTargetName) {
return; return;
@ -338,6 +354,9 @@ function getTestCiTargets(
targets[targetName] = { targets[targetName] = {
command: `${getGradleExecFile()} ${taskCommandToRun} --tests ${testName}`, command: `${getGradleExecFile()} ${taskCommandToRun} --tests ${testName}`,
options: {
cwd: gradlewFileDirectory,
},
cache: true, cache: true,
inputs, inputs,
dependsOn: dependsOnMap['test'], dependsOn: dependsOnMap['test'],
@ -386,26 +405,6 @@ function getTestCiTargets(
targetGroups[targetGroupName].push(ciTargetName); targetGroups[targetGroupName].push(ciTargetName);
} }
function splitConfigFiles(files: readonly string[]): {
configFiles: string[];
testFiles: string[];
projectRoots: string[];
} {
const configFiles = [];
const testFiles = [];
const projectRoots = new Set<string>();
files.forEach((file) => {
if (GRADLE_BUILD_FILES.has(basename(file))) {
configFiles.push(file);
projectRoots.add(dirname(file));
} else {
testFiles.push(file);
}
});
return { configFiles, testFiles, projectRoots: Array.from(projectRoots) };
}
function getGradleProjectRootToTestFilesMap( function getGradleProjectRootToTestFilesMap(
testFiles: string[], testFiles: string[],
projectRoots: string[] projectRoots: string[]

View File

@ -0,0 +1,160 @@
------------------------------------------------------------
Project ':app'
------------------------------------------------------------
allprojects: [project ':app']
ant: org.gradle.api.internal.project.DefaultAntBuilder@505598eb
antBuilderFactory: org.gradle.api.internal.project.DefaultAntBuilderFactory@259d836f
application: extension 'application'
applicationDefaultJvmArgs: []
applicationDistribution: org.gradle.api.internal.file.copy.DefaultCopySpec_Decorated@3978059a
applicationName: app
archivesBaseName: app
artifacts: org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler_Decorated@32167ebb
asDynamicObject: DynamicObject for project ':app'
assemble: task ':app:assemble'
assembleDist: task ':app:assembleDist'
autoTargetJvmDisabled: false
base: extension 'base'
baseClassLoaderScope: org.gradle.api.internal.initialization.DefaultClassLoaderScope@3c4c27d8
build: task ':app:build'
buildDependents: task ':app:buildDependents'
buildDir: /private/tmp/nx-e2e/nx/proj9912426/app/build
buildEnvironment: task ':app:buildEnvironment'
buildFile: /private/tmp/nx-e2e/nx/proj9912426/app/build.gradle
buildNeeded: task ':app:buildNeeded'
buildPath: :
buildScriptSource: org.gradle.groovy.scripts.TextResourceScriptSource@4ef82ad2
buildTreePath: :app
buildscript: org.gradle.api.internal.initialization.DefaultScriptHandler_Decorated@7b75e99
check: task ':app:check'
childProjects: {}
childProjectsUnchecked: {}
class: class org.gradle.api.internal.project.DefaultProject_Decorated
classLoaderScope: org.gradle.api.internal.initialization.DefaultClassLoaderScope@7e1acf20
classes: task ':app:classes'
clean: task ':app:clean'
compileGroovy: task ':app:compileGroovy'
compileJava: task ':app:compileJava'
compileTestGroovy: task ':app:compileTestGroovy'
compileTestJava: task ':app:compileTestJava'
components: SoftwareComponent container
configurationActions: org.gradle.configuration.project.DefaultProjectConfigurationActionContainer@7f7885fe
configurationTargetIdentifier: org.gradle.configuration.ConfigurationTargetIdentifier$1@70598e79
configurations: configuration container
convention: org.gradle.internal.extensibility.DefaultConvention@4d27557d
conventionMapping: org.gradle.internal.extensibility.ConventionAwareHelper@65ecf036
crossProjectModelAccess: org.gradle.api.internal.project.DefaultCrossProjectModelAccess@7665f252
defaultArtifacts: extension 'defaultArtifacts'
defaultTasks: []
deferredProjectConfiguration: org.gradle.api.internal.project.DeferredProjectConfiguration@5e80ce00
dependencies: org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler_Decorated@6216b9ef
dependencyFactory: org.gradle.api.internal.artifacts.DefaultDependencyFactory@15ed846d
dependencyInsight: task ':app:dependencyInsight'
dependencyLocking: org.gradle.internal.locking.DefaultDependencyLockingHandler_Decorated@1702aba6
dependencyMetaDataProvider: org.gradle.internal.service.scopes.ProjectScopeServices$ProjectBackedModuleMetaDataProvider@4f2a8788
dependencyReport: task ':app:dependencyReport'
dependentComponents: task ':app:dependentComponents'
depth: 1
description: null
detachedState: false
displayName: project ':app'
distTar: task ':app:distTar'
distZip: task ':app:distZip'
distributions: Distribution container
distsDirName: distributions
distsDirectory: extension 'base' property 'distsDirectory'
docsDir: /private/tmp/nx-e2e/nx/proj9912426/app/build/docs
docsDirName: docs
executableDir: bin
ext: org.gradle.internal.extensibility.DefaultExtraPropertiesExtension@18249643
extensions: org.gradle.internal.extensibility.DefaultConvention@4d27557d
fileOperations: org.gradle.api.internal.file.DefaultFileOperations@6193a7c6
fileResolver: org.gradle.api.internal.file.BaseDirFileResolver@634e7fc9
gradle: build 'my-gradle-project5165026'
groovyRuntime: extension 'groovyRuntime'
groovydoc: task ':app:groovydoc'
group: my-gradle-project5165026
help: task ':app:help'
htmlDependencyReport: task ':app:htmlDependencyReport'
identityPath: :app
inheritedScope: org.gradle.internal.extensibility.ExtensibleDynamicObject$InheritedDynamicObject@7843a2ef
installDist: task ':app:installDist'
internalStatus: property(java.lang.Object, fixed(class java.lang.String, integration))
jar: task ':app:jar'
java: extension 'java'
javaToolchains: extension 'javaToolchains'
javadoc: task ':app:javadoc'
layout: org.gradle.api.internal.file.DefaultProjectLayout@38b897f6
libsDirName: libs
libsDirectory: extension 'base' property 'libsDirectory'
listenerBuildOperationDecorator: org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator@58c7b14b
logger: org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger@2fb55781
logging: org.gradle.internal.logging.services.DefaultLoggingManager@2818071f
mainClassName: null
model: project ':app'
modelIdentityDisplayName: null
modelRegistry: org.gradle.model.internal.registry.DefaultModelRegistry@4d299c29
name: app
normalization: org.gradle.normalization.internal.DefaultInputNormalizationHandler_Decorated@4460a043
objects: org.gradle.api.internal.model.DefaultObjectFactory@381b881e
outgoingVariants: task ':app:outgoingVariants'
owner: project ':app'
parent: root project 'my-gradle-project5165026'
parentIdentifier: root project 'my-gradle-project5165026'
path: :app
pluginContext: false
pluginManager: org.gradle.api.internal.plugins.DefaultPluginManager_Decorated@505350fd
plugins: [org.gradle.api.plugins.ReportingBasePlugin$Inject@363d344c, org.gradle.api.plugins.ProjectReportsPlugin$Inject@4ecb0bf4, org.gradle.api.plugins.HelpTasksPlugin$Inject@55c1f1c, org.gradle.buildinit.plugins.BuildInitPlugin$Inject@5154243b, org.gradle.buildinit.plugins.WrapperPlugin$Inject@4186a206, org.gradle.language.base.plugins.LifecycleBasePlugin$Inject@63b97a85, org.gradle.api.plugins.BasePlugin$Inject@46c915ee, org.gradle.api.plugins.JvmEcosystemPlugin$Inject@26901290, org.gradle.api.plugins.JvmToolchainsPlugin$Inject@1047a16b, org.gradle.api.plugins.JavaBasePlugin$Inject@219fc174, org.gradle.api.plugins.GroovyBasePlugin$Inject@cc0c00e, org.gradle.testing.base.plugins.TestSuiteBasePlugin$Inject@afcdb52, org.gradle.api.plugins.JvmTestSuitePlugin$Inject@4f2802e1, org.gradle.api.plugins.JavaPlugin$Inject@457c77b1, org.gradle.api.plugins.GroovyPlugin$Inject@6bea0b65, BuildlogicGroovyCommonConventionsPlugin@340f0f12, org.gradle.api.distribution.plugins.DistributionPlugin$Inject@41c5c0a5, org.gradle.api.plugins.ApplicationPlugin$Inject@4e797fdb, BuildlogicGroovyApplicationConventionsPlugin@502595f2]
processOperations: org.gradle.process.internal.DefaultExecActionFactory$DecoratingExecActionFactory@511a460a
processResources: task ':app:processResources'
processTestResources: task ':app:processTestResources'
project: project ':app'
projectConfigurator: org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator@21331883
projectDir: /private/tmp/nx-e2e/nx/proj9912426/app
projectEvaluationBroadcaster: ProjectEvaluationListener broadcast
projectEvaluator: org.gradle.configuration.project.LifecycleProjectEvaluator@f6c3829
projectPath: :app
projectReport: task ':app:projectReport'
projectReportDir: /private/tmp/nx-e2e/nx/proj9912426/app/build/reports/project
projectReportDirName: project
projects: [project ':app']
properties: {...}
propertyReport: task ':app:propertyReport'
providers: org.gradle.api.internal.provider.DefaultProviderFactory_Decorated@1eb7e486
publicType: org.gradle.api.plugins.ProjectReportsPluginConvention
reporting: extension 'reporting'
repositories: repository container
resolvableConfigurations: task ':app:resolvableConfigurations'
resources: org.gradle.api.internal.resources.DefaultResourceHandler@103986d2
rootDir: /private/tmp/nx-e2e/nx/proj9912426
rootProject: root project 'my-gradle-project5165026'
rootScript: false
run: task ':app:run'
script: false
scriptHandlerFactory: org.gradle.api.internal.initialization.DefaultScriptHandlerFactory@191cd667
scriptPluginFactory: org.gradle.configuration.ScriptPluginFactorySelector@775f081d
serviceRegistryFactory: org.gradle.internal.service.scopes.BuildScopeServiceRegistryFactory@566d51a4
services: Project services
sourceCompatibility: 21
sourceSets: SourceSet container
standardOutputCapture: org.gradle.internal.logging.services.DefaultLoggingManager@2818071f
startScripts: task ':app:startScripts'
state: project state 'EXECUTED'
status: integration
subprojects: []
targetCompatibility: 21
taskDependencyFactory: org.gradle.api.internal.tasks.DefaultTaskDependencyFactory@7486b7b3
taskReport: task ':app:taskReport'
taskThatOwnsThisObject: null
tasks: task set
test: task ':app:test'
testClasses: task ':app:testClasses'
testReportDir: /private/tmp/nx-e2e/nx/proj9912426/app/build/reports/tests
testReportDirName: tests
testResultsDir: /private/tmp/nx-e2e/nx/proj9912426/app/build/test-results
testResultsDirName: test-results
testing: extension 'testing'
version: unspecified
versionCatalogs: extension 'versionCatalogs'

View File

@ -0,0 +1,87 @@
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { findGraldewFile } from './exec-gradle';
describe('exec gradle', () => {
describe('findGraldewFile', () => {
let tempFs: TempFs;
let cwd: string;
beforeEach(async () => {
tempFs = new TempFs('test');
cwd = process.cwd();
process.chdir(tempFs.tempDir);
});
afterEach(() => {
jest.resetModules();
process.chdir(cwd);
});
it('should find gradlew with one gradlew file at root', async () => {
await tempFs.createFiles({
'proj/build.gradle': ``,
gradlew: '',
'nested/nested/proj/build.gradle': ``,
'nested/nested/proj/settings.gradle': ``,
'nested/nested/proj/src/test/java/test/rootTest.java': ``,
'nested/nested/proj/src/test/java/test/aTest.java': ``,
'nested/nested/proj/src/test/java/test/bTest.java': ``,
});
let gradlewFile = findGraldewFile('proj/build.gradle', tempFs.tempDir);
expect(gradlewFile).toEqual('gradlew');
gradlewFile = findGraldewFile(
'nested/nested/proj/build.gradle',
tempFs.tempDir
);
expect(gradlewFile).toEqual('gradlew');
gradlewFile = findGraldewFile(
'nested/nested/proj/settings.gradle',
tempFs.tempDir
);
expect(gradlewFile).toEqual('gradlew');
});
it('should find gradlew with multiple gradlew files with nested project structure', async () => {
await tempFs.createFiles({
'proj/build.gradle': ``,
'proj/gradlew': '',
'proj/settings.gradle': ``,
'nested/nested/proj/gradlew': '',
'nested/nested/proj/build.gradle': ``,
'nested/nested/proj/settings.gradle': ``,
'nested/nested/proj/src/test/java/test/rootTest.java': ``,
'nested/nested/proj/src/test/java/test/aTest.java': ``,
'nested/nested/proj/src/test/java/test/bTest.java': ``,
});
let gradlewFile = findGraldewFile('proj/build.gradle', tempFs.tempDir);
expect(gradlewFile).toEqual('proj/gradlew');
gradlewFile = findGraldewFile('proj/settings.gradle', tempFs.tempDir);
expect(gradlewFile).toEqual('proj/gradlew');
gradlewFile = findGraldewFile(
'nested/nested/proj/build.gradle',
tempFs.tempDir
);
expect(gradlewFile).toEqual('nested/nested/proj/gradlew');
gradlewFile = findGraldewFile(
'nested/nested/proj/settings.gradle',
tempFs.tempDir
);
expect(gradlewFile).toEqual('nested/nested/proj/gradlew');
});
it('should throw an error if no gradlw in workspace', async () => {
await tempFs.createFiles({
'proj/build.gradle': ``,
'nested/nested/proj/build.gradle': ``,
'nested/nested/proj/settings.gradle': ``,
'nested/nested/proj/src/test/java/test/rootTest.java': ``,
'nested/nested/proj/src/test/java/test/aTest.java': ``,
'nested/nested/proj/src/test/java/test/bTest.java': ``,
});
expect(() =>
findGraldewFile('proj/build.gradle', tempFs.tempDir)
).toThrow();
});
});
});

View File

@ -1,36 +1,35 @@
import { workspaceRoot } from '@nx/devkit'; import { AggregateCreateNodesError, workspaceRoot } from '@nx/devkit';
import { ExecFileOptions, execFile } from 'node:child_process'; import { ExecFileOptions, execFile } from 'node:child_process';
import { existsSync } from 'node:fs'; import { existsSync } from 'node:fs';
import { join } from 'node:path'; import { dirname, join } from 'node:path';
export function getGradleBinaryPath(): string {
const gradleFile = process.platform.startsWith('win')
? 'gradlew.bat'
: 'gradlew';
const gradleBinaryPath = join(workspaceRoot, gradleFile);
if (!existsSync(gradleBinaryPath)) {
throw new Error('Gradle is not setup. Run "gradle init"');
}
return gradleBinaryPath;
}
/**
* For gradle command, it needs to be run from the directory of the gradle binary
* @returns gradle binary file name
*/
export function getGradleExecFile(): string { export function getGradleExecFile(): string {
return process.platform.startsWith('win') ? '.\\gradlew.bat' : './gradlew'; return process.platform.startsWith('win') ? '.\\gradlew.bat' : './gradlew';
} }
/**
* This function executes gradle with the given arguments
* @param gradleBinaryPath absolute path to gradle binary
* @param args args passed to gradle
* @param execOptions exec options
* @returns promise with the stdout buffer
*/
export function execGradleAsync( export function execGradleAsync(
gradleBinaryPath: string,
args: ReadonlyArray<string>, args: ReadonlyArray<string>,
execOptions: ExecFileOptions = {} execOptions: ExecFileOptions = {}
): Promise<Buffer> { ): Promise<Buffer> {
const gradleBinaryPath = getGradleBinaryPath();
return new Promise<Buffer>((res, rej) => { return new Promise<Buffer>((res, rej) => {
const cp = execFile(gradleBinaryPath, args, { const cp = execFile(gradleBinaryPath, args, {
...execOptions, cwd: dirname(gradleBinaryPath),
shell: true, shell: true,
windowsHide: true, windowsHide: true,
env: process.env, env: process.env,
...execOptions,
}); });
let stdout = Buffer.from(''); let stdout = Buffer.from('');
@ -53,3 +52,46 @@ export function execGradleAsync(
}); });
}); });
} }
/**
* This function recursively finds the nearest gradlew file in the workspace
* @param originalFileToSearch the original file to search for
* @param wr workspace root
* @param currentSearchPath the path to start searching for gradlew file
* @returns the relative path of the gradlew file to workspace root, throws an error if gradlew file is not found
* It will return gradlew.bat file on windows and gradlew file on other platforms
*/
export function findGraldewFile(
originalFileToSearch: string,
wr: string = workspaceRoot,
currentSearchPath?: string
): string {
currentSearchPath ??= originalFileToSearch;
const parent = dirname(currentSearchPath);
if (currentSearchPath === parent) {
throw new AggregateCreateNodesError(
[
[
originalFileToSearch,
new Error('No Gradlew file found. Run "gradle init"'),
],
],
[]
);
}
const gradlewPath = join(parent, 'gradlew');
const gradlewBatPath = join(parent, 'gradlew.bat');
if (process.platform.startsWith('win')) {
if (existsSync(join(wr, gradlewBatPath))) {
return gradlewBatPath;
}
} else {
if (existsSync(join(wr, gradlewPath))) {
return gradlewPath;
}
}
return findGraldewFile(originalFileToSearch, wr, parent);
}

View File

@ -36,4 +36,13 @@ describe('processProjectReports', () => {
'mylibrary', 'mylibrary',
]); ]);
}); });
it('should process properties report for projects without child projects', () => {
const report = processProjectReports([
'> Task :propertyReport',
`See the report at: file://${__dirname}/__mocks__/gradle-properties-report-no-child-projects.txt`,
]);
expect(report.gradleProjectToProjectName.get('')).toEqual('app');
expect(report.gradleProjectToChildProjects.get('')).toEqual([]);
});
}); });

View File

@ -3,23 +3,18 @@ import { join, relative } from 'node:path';
import { import {
AggregateCreateNodesError, AggregateCreateNodesError,
logger,
normalizePath, normalizePath,
workspaceRoot, workspaceRoot,
} from '@nx/devkit'; } from '@nx/devkit';
import { combineGlobPatterns } from 'nx/src/utils/globs';
import { execGradleAsync } from './exec-gradle';
import { hashWithWorkspaceContext } from 'nx/src/utils/workspace-context'; import { hashWithWorkspaceContext } from 'nx/src/utils/workspace-context';
import { dirname } from 'path'; import { dirname } from 'path';
import { gradleConfigAndTestGlob } from './split-config-files';
export const fileSeparator = process.platform.startsWith('win') import {
? 'file:///' getProjectReportLines,
: 'file://'; fileSeparator,
newLineSeparator,
export const newLineSeparator = process.platform.startsWith('win') } from './get-project-report-lines';
? '\r\n'
: '\n';
export interface GradleReport { export interface GradleReport {
gradleFileToGradleProjectMap: Map<string, string>; gradleFileToGradleProjectMap: Map<string, string>;
@ -34,37 +29,39 @@ export interface GradleReport {
let gradleReportCache: GradleReport; let gradleReportCache: GradleReport;
let gradleCurrentConfigHash: string; let gradleCurrentConfigHash: string;
export const GRADLE_BUILD_FILES = new Set(['build.gradle', 'build.gradle.kts']);
export const GRADLE_TEST_FILES = [
'**/src/test/java/**/*Test.java',
'**/src/test/kotlin/**/*Test.kt',
'**/src/test/java/**/*Tests.java',
'**/src/test/kotlin/**/*Tests.kt',
];
export const gradleConfigGlob = combineGlobPatterns(
...Array.from(GRADLE_BUILD_FILES).map((file) => `**/${file}`)
);
export const gradleConfigAndTestGlob = combineGlobPatterns(
...Array.from(GRADLE_BUILD_FILES).map((file) => `**/${file}`),
...GRADLE_TEST_FILES
);
export function getCurrentGradleReport() { export function getCurrentGradleReport() {
if (!gradleReportCache) { if (!gradleReportCache) {
throw new Error( throw new AggregateCreateNodesError(
'Expected cached gradle report. Please open an issue at https://github.com/nrwl/nx/issues/new/choose' [
[
null,
new Error(
`Expected cached gradle report. Please open an issue at https://github.com/nrwl/nx/issues/new/choose`
),
],
],
[]
); );
} }
return gradleReportCache; return gradleReportCache;
} }
/**
* This function populates the gradle report cache.
* For each gradlew file, it runs the `projectReportAll` task and processes the output.
* If `projectReportAll` fails, it runs the `projectReport` task instead.
* It will throw an error if both tasks fail.
* It will accumulate the output of all gradlew files.
* @param workspaceRoot
* @param gradlewFiles absolute paths to all gradlew files in the workspace
* @returns Promise<void>
*/
export async function populateGradleReport( export async function populateGradleReport(
workspaceRoot: string workspaceRoot: string,
gradlewFiles: string[]
): Promise<void> { ): Promise<void> {
const gradleConfigHash = await hashWithWorkspaceContext(workspaceRoot, [ const gradleConfigHash = await hashWithWorkspaceContext(workspaceRoot, [
gradleConfigGlob, gradleConfigAndTestGlob,
]); ]);
if (gradleReportCache && gradleConfigHash === gradleCurrentConfigHash) { if (gradleReportCache && gradleConfigHash === gradleCurrentConfigHash) {
return; return;
@ -73,37 +70,18 @@ export async function populateGradleReport(
const gradleProjectReportStart = performance.mark( const gradleProjectReportStart = performance.mark(
'gradleProjectReport:start' 'gradleProjectReport:start'
); );
let projectReportLines;
try { const projectReportLines = await gradlewFiles.reduce(
projectReportLines = await execGradleAsync(['projectReportAll'], { async (
cwd: workspaceRoot, projectReportLines: Promise<string[]>,
}); gradlewFile: string
} catch (e) { ): Promise<string[]> => {
try { const allLines = await projectReportLines;
projectReportLines = await execGradleAsync(['projectReport'], { const currentLines = await getProjectReportLines(gradlewFile);
cwd: workspaceRoot, return [...allLines, ...currentLines];
}); },
logger.warn( Promise.resolve([])
'Could not run `projectReportAll` task. Ran `projectReport` instead. Please run `nx generate @nx/gradle:init` to generate the necessary tasks.' );
);
} catch (e) {
throw new AggregateCreateNodesError(
[
[
null,
new Error(
'Could not run `projectReportAll` or `projectReport` task. Please run `nx generate @nx/gradle:init` to generate the necessary tasks.'
),
],
],
[]
);
}
}
projectReportLines = projectReportLines
.toString()
.split(newLineSeparator)
.filter((line) => line.trim() !== '');
const gradleProjectReportEnd = performance.mark('gradleProjectReport:end'); const gradleProjectReportEnd = performance.mark('gradleProjectReport:end');
performance.measure( performance.measure(
@ -111,6 +89,7 @@ export async function populateGradleReport(
gradleProjectReportStart.name, gradleProjectReportStart.name,
gradleProjectReportEnd.name gradleProjectReportEnd.name
); );
gradleCurrentConfigHash = gradleConfigHash;
gradleReportCache = processProjectReports(projectReportLines); gradleReportCache = processProjectReports(projectReportLines);
} }

View File

@ -0,0 +1,65 @@
import { AggregateCreateNodesError, logger } from '@nx/devkit';
import { execGradleAsync } from './exec-gradle';
import { existsSync } from 'fs';
import { dirname, join } from 'path';
export const fileSeparator = process.platform.startsWith('win')
? 'file:///'
: 'file://';
export const newLineSeparator = process.platform.startsWith('win')
? '\r\n'
: '\n';
/**
* This function executes the gradle projectReportAll task and returns the output as an array of lines.
* @param gradlewFile the absolute path to the gradlew file
* @returns project report lines
*/
export async function getProjectReportLines(
gradlewFile: string
): Promise<string[]> {
let projectReportBuffer: Buffer;
// if there is no build.gradle or build.gradle.kts file, we cannot run the projectReport nor projectReportAll task
if (
!existsSync(join(dirname(gradlewFile), 'build.gradle')) &&
!existsSync(join(dirname(gradlewFile), 'build.gradle.kts'))
) {
logger.warn(
`Could not find build file near ${gradlewFile}. Please run 'nx generate @nx/gradle:init' to generate the necessary tasks.`
);
return [];
}
try {
projectReportBuffer = await execGradleAsync(gradlewFile, [
'projectReportAll',
]);
} catch (e) {
try {
projectReportBuffer = await execGradleAsync(gradlewFile, [
'projectReport',
]);
logger.warn(
`Could not run 'projectReportAll' task. Ran 'projectReport' instead. Please run 'nx generate @nx/gradle:init' to generate the necessary tasks.`
);
} catch (e) {
throw new AggregateCreateNodesError(
[
[
gradlewFile,
new Error(
`Could not run 'projectReportAll' or 'projectReport' task. Please run 'nx generate @nx/gradle:init' to generate the necessary tasks.`
),
],
],
[]
);
}
}
return projectReportBuffer
.toString()
.split(newLineSeparator)
.filter((line) => line.trim() !== '');
}

View File

@ -0,0 +1,88 @@
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { splitConfigFiles } from './split-config-files';
describe('split config files', () => {
let tempFs: TempFs;
let cwd: string;
beforeEach(async () => {
tempFs = new TempFs('test');
cwd = process.cwd();
process.chdir(tempFs.tempDir);
});
afterEach(() => {
jest.resetModules();
process.chdir(cwd);
});
it('should split config files with one gradlew file', async () => {
await tempFs.createFiles({
'proj/build.gradle': ``,
gradlew: '',
'nested/nested/proj/build.gradle': ``,
'nested/nested/proj/settings.gradle': ``,
'nested/nested/proj/src/test/java/test/rootTest.java': ``,
'nested/nested/proj/src/test/java/test/aTest.java': ``,
'nested/nested/proj/src/test/java/test/bTest.java': ``,
});
const { buildFiles, gradlewFiles, testFiles, projectRoots } =
splitConfigFiles([
'proj/build.gradle',
'gradlew',
'nested/nested/proj/build.gradle',
'nested/nested/proj/src/test/java/test/rootTest.java',
'nested/nested/proj/src/test/java/test/aTest.java',
'nested/nested/proj/src/test/java/test/bTest.java',
]);
expect(buildFiles).toEqual([
'proj/build.gradle',
'nested/nested/proj/build.gradle',
]);
expect(gradlewFiles).toEqual(['gradlew']);
expect(testFiles).toEqual([
'nested/nested/proj/src/test/java/test/rootTest.java',
'nested/nested/proj/src/test/java/test/aTest.java',
'nested/nested/proj/src/test/java/test/bTest.java',
]);
expect(projectRoots).toEqual(['proj', 'nested/nested/proj']);
});
it('should split config files with multiple gradlew files', async () => {
await tempFs.createFiles({
'proj/build.gradle': ``,
'proj/gradlew': '',
'proj/settings.gradle': ``,
'nested/nested/proj/gradlew': '',
'nested/nested/proj/build.gradle': ``,
'nested/nested/proj/settings.gradle': ``,
'nested/nested/proj/src/test/java/test/rootTest.java': ``,
'nested/nested/proj/src/test/java/test/aTest.java': ``,
'nested/nested/proj/src/test/java/test/bTest.java': ``,
});
const { buildFiles, gradlewFiles, testFiles, projectRoots } =
splitConfigFiles([
'proj/build.gradle',
'proj/gradlew',
'nested/nested/proj/build.gradle',
'nested/nested/proj/gradlew',
'nested/nested/proj/src/test/java/test/rootTest.java',
'nested/nested/proj/src/test/java/test/aTest.java',
'nested/nested/proj/src/test/java/test/bTest.java',
]);
expect(buildFiles).toEqual([
'proj/build.gradle',
'nested/nested/proj/build.gradle',
]);
expect(gradlewFiles).toEqual([
'proj/gradlew',
'nested/nested/proj/gradlew',
]);
expect(testFiles).toEqual([
'nested/nested/proj/src/test/java/test/rootTest.java',
'nested/nested/proj/src/test/java/test/aTest.java',
'nested/nested/proj/src/test/java/test/bTest.java',
]);
expect(projectRoots).toEqual(['proj', 'nested/nested/proj']);
});
});

View File

@ -0,0 +1,67 @@
import { combineGlobPatterns } from 'nx/src/utils/globs';
import { basename, dirname } from 'node:path';
export const GRADLE_BUILD_FILES = new Set(['build.gradle', 'build.gradle.kts']);
export const GRALDEW_FILES = new Set(['gradlew', 'gradlew.bat']);
export const GRADLE_TEST_FILES = [
'**/src/test/java/**/*Test.java',
'**/src/test/kotlin/**/*Test.kt',
'**/src/test/java/**/*Tests.java',
'**/src/test/kotlin/**/*Tests.kt',
];
export const gradleConfigGlob = combineGlobPatterns(
...Array.from(GRADLE_BUILD_FILES).map((file) => `**/${file}`)
);
export const gradleConfigAndTestGlob = combineGlobPatterns(
...Array.from(GRADLE_BUILD_FILES).map((file) => `**/${file}`),
...Array.from(GRALDEW_FILES).map((file) => `**/${file}`),
...GRADLE_TEST_FILES
);
/**
* This function split config files into build files, settings files, test files and project roots
* @param files list of files to split
* @returns object with buildFiles, gradlewFiles, testFiles and projectRoots
* For gradlewFiles, it will start with settings files and find the nearest gradlew file in the workspace
*/
export function splitConfigFiles(files: readonly string[]): {
buildFiles: string[];
gradlewFiles: string[];
testFiles: string[];
projectRoots: string[];
} {
const buildFiles = [];
const testFiles = [];
const gradlewFiles = [];
const projectRoots = new Set<string>();
files.forEach((file) => {
const filename = basename(file);
const fileDirectory = dirname(file);
if (GRADLE_BUILD_FILES.has(filename)) {
buildFiles.push(file);
projectRoots.add(fileDirectory);
} else if (GRALDEW_FILES.has(filename)) {
if (process.platform.startsWith('win')) {
if (filename === 'gradlew.bat') {
gradlewFiles.push(file);
}
} else {
if (filename === 'gradlew') {
gradlewFiles.push(file);
}
}
} else {
testFiles.push(file);
}
});
return {
buildFiles,
testFiles,
gradlewFiles,
projectRoots: Array.from(projectRoots),
};
}

View File

@ -220,7 +220,14 @@ export async function detectPlugins(
} }
} }
} }
if (existsSync('gradlew') || existsSync('gradlew.bat')) {
let gradlewFiles = ['gradlew', 'gradlew.bat'].concat(
await globWithWorkspaceContext(process.cwd(), [
'**/gradlew',
'**/gradlew.bat',
])
);
if (gradlewFiles.some((f) => existsSync(f))) {
detectedPlugins.add('@nx/gradle'); detectedPlugins.add('@nx/gradle');
} }