feat(gradle): add gradle init generator (#22245)

This commit is contained in:
Emily Xiong 2024-03-19 14:38:15 -04:00 committed by GitHub
parent 45e1d78a21
commit 6d83dd7ff0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 358 additions and 88 deletions

View File

@ -8,80 +8,89 @@ import {
runCommand,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
import { execSync } from 'child_process';
describe('Gradle', () => {
let gradleProjectName = uniq('my-gradle-project');
describe.each([{ type: 'kotlin' }, { type: 'groovy' }])(
'$type',
({ type }: { type: 'kotlin' | 'groovy' }) => {
let gradleProjectName = uniq('my-gradle-project');
beforeAll(() => {
newProject();
createGradleProject(gradleProjectName, type);
});
afterAll(() => cleanupProject());
beforeAll(() => {
newProject();
createGradleProject(gradleProjectName);
});
afterAll(() => cleanupProject());
it('should build', () => {
const projects = runCLI(`show projects`);
expect(projects).toContain('app');
expect(projects).toContain('list');
expect(projects).toContain('utilities');
expect(projects).toContain(gradleProjectName);
it('should build', () => {
const projects = runCLI(`show projects`);
expect(projects).toContain('app');
expect(projects).toContain('list');
expect(projects).toContain('utilities');
expect(projects).toContain(gradleProjectName);
const buildOutput = runCLI('build app', { verbose: true });
// app depends on list and utilities
expect(buildOutput).toContain('nx run list:build');
expect(buildOutput).toContain('nx run utilities:build');
const buildOutput = runCLI('build app', { verbose: true });
// app depends on list and utilities
expect(buildOutput).toContain('nx run list:build');
expect(buildOutput).toContain('nx run utilities:build');
checkFilesExist(
`app/build/libs/app.jar`,
`list/build/libs/list.jar`,
`utilities/build/libs/utilities.jar`
);
});
checkFilesExist(
`app/build/libs/app.jar`,
`list/build/libs/list.jar`,
`utilities/build/libs/utilities.jar`
);
});
it('should track dependencies for new app', () => {
if (type === 'groovy') {
createFile(
`app2/build.gradle`,
`plugins {
id 'gradleProject.groovy-application-conventions'
}
it('should track dependencies for new app', () => {
createFile(
'app2/build.gradle.kts',
`
plugins {
id("gradleProject.kotlin-application-conventions")
dependencies {
implementation project(':app')
}`
);
} else {
createFile(
`app2/build.gradle.kts`,
`plugins {
id("gradleProject.kotlin-library-conventions")
}
dependencies {
implementation(project(":app"))
}`
);
}
updateFile(
`settings.gradle${type === 'kotlin' ? '.kts' : ''}`,
(content) => {
content += `\r\ninclude("app2")`;
return content;
}
);
const buildOutput = runCLI('build app2', { verbose: true });
// app2 depends on app
expect(buildOutput).toContain('nx run app:build');
});
}
dependencies {
implementation(project(":app"))
}
`
);
updateFile(`settings.gradle.kts`, (content) => {
content += `\r\ninclude("app2")`;
return content;
});
const buildOutput = runCLI('build app2', { verbose: true });
// app2 depends on app
expect(buildOutput).toContain('nx run app:build');
});
);
});
function createGradleProject(projectName: string) {
function createGradleProject(
projectName: string,
type: 'kotlin' | 'groovy' = 'kotlin'
) {
e2eConsoleLogger(`Using java version: ${execSync('java --version')}`);
e2eConsoleLogger(`Using gradle version: ${execSync('gradle --version')}`);
e2eConsoleLogger(execSync(`gradle help --task :init`).toString());
e2eConsoleLogger(
runCommand(
`gradle init --type kotlin-application --dsl kotlin --project-name ${projectName} --package gradleProject --no-incubating --split-project`
`gradle init --type ${type}-application --dsl ${type} --project-name ${projectName} --package gradleProject --no-incubating --split-project`
)
);
updateJson('nx.json', (nxJson) => {
nxJson.plugins = ['@nx/gradle'];
return nxJson;
});
createFile(
'build.gradle.kts',
`allprojects {
apply {
plugin("project-report")
}
}`
);
runCLI(`add @nx/gradle`);
}

View File

@ -0,0 +1,11 @@
{
"name": "Nx Gradle",
"version": "0.1",
"generators": {
"init": {
"factory": "./src/generators/init/init#initGenerator",
"schema": "./src/generators/init/schema.json",
"description": "Initializes a Gradle project in the current workspace"
}
}
}

View File

@ -1 +1,2 @@
export * from './plugin';
export { initGenerator } from './src/generators/init/init';

View File

@ -22,6 +22,14 @@
"url": "https://github.com/nrwl/nx/issues"
},
"homepage": "https://nx.dev",
"generators": "./generators.json",
"exports": {
".": "./index.js",
"./package.json": "./package.json",
"./migrations.json": "./migrations.json",
"./generators.json": "./generators.json",
"./plugin": "./plugin.js"
},
"nx-migrate": {
"migrations": "./migrations.json"
},

View File

@ -0,0 +1,73 @@
import { readNxJson, Tree, updateNxJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { initGenerator } from './init';
describe('@nx/gradle:init', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
tree.write('settings.gradle', '');
});
it('should add the plugin', async () => {
await initGenerator(tree, {
skipFormat: true,
skipPackageJson: false,
});
const nxJson = readNxJson(tree);
expect(nxJson.plugins).toMatchInlineSnapshot(`
[
{
"options": {
"buildTargetName": "build",
"classesTargetName": "classes",
"testTargetName": "test",
},
"plugin": "@nx/gradle/plugin",
},
]
`);
});
it('should not overwrite existing plugins', async () => {
updateNxJson(tree, {
plugins: ['foo'],
});
await initGenerator(tree, {
skipFormat: true,
skipPackageJson: false,
});
const nxJson = readNxJson(tree);
expect(nxJson.plugins).toMatchInlineSnapshot(`
[
"foo",
{
"options": {
"buildTargetName": "build",
"classesTargetName": "classes",
"testTargetName": "test",
},
"plugin": "@nx/gradle/plugin",
},
]
`);
});
it('should not add plugin if already in array', async () => {
updateNxJson(tree, {
plugins: ['@nx/gradle/plugin'],
});
await initGenerator(tree, {
skipFormat: true,
skipPackageJson: false,
});
const nxJson = readNxJson(tree);
expect(nxJson.plugins).toMatchInlineSnapshot(`
[
"@nx/gradle/plugin",
]
`);
});
});

View File

@ -0,0 +1,102 @@
import {
addDependenciesToPackageJson,
formatFiles,
GeneratorCallback,
logger,
readNxJson,
runTasksInSerial,
Tree,
updateNxJson,
} from '@nx/devkit';
import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts';
import { createNodes } from '../../plugin/nodes';
import { nxVersion } from '../../utils/versions';
import { InitGeneratorSchema } from './schema';
import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
export async function initGenerator(tree: Tree, options: InitGeneratorSchema) {
const tasks: GeneratorCallback[] = [];
if (!options.skipPackageJson && tree.exists('package.json')) {
tasks.push(
addDependenciesToPackageJson(
tree,
{},
{
'@nx/gradle': nxVersion,
},
undefined,
options.keepExistingVersions
)
);
}
addPlugin(tree);
addProjectReportToBuildGradle(tree);
if (options.updatePackageScripts && tree.exists('package.json')) {
await updatePackageScripts(tree, createNodes);
}
if (!options.skipFormat) {
await formatFiles(tree);
}
return runTasksInSerial(...tasks);
}
function addPlugin(tree: Tree) {
const nxJson = readNxJson(tree);
if (!hasGradlePlugin(tree)) {
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/gradle/plugin',
options: {
testTargetName: 'test',
classesTargetName: 'classes',
buildTargetName: 'build',
},
});
updateNxJson(tree, nxJson);
}
}
/**
* This function adds the project-report plugin to the build.gradle or build.gradle.kts file
*/
function addProjectReportToBuildGradle(tree: Tree) {
let buildGradleFile: string;
if (tree.exists('settings.gradle.kts')) {
buildGradleFile = 'build.gradle.kts';
} else if (tree.exists('settings.gradle')) {
buildGradleFile = 'build.gradle';
} else {
throw new Error(
'Could not find settings.gradle or settings.gradle.kts file in your gradle workspace.'
);
}
let buildGradleContent = '';
if (tree.exists(buildGradleFile)) {
buildGradleContent = tree.read(buildGradleFile).toString();
}
if (buildGradleContent.includes('allprojects')) {
if (!buildGradleContent.includes('"project-report')) {
logger.warn(`Please add the project-report plugin to your ${buildGradleFile}:
allprojects {
apply {
plugin("project-report")
}
}`);
}
} else {
buildGradleContent += `\n\rallprojects {
apply {
plugin("project-report")
}
}`;
tree.write(buildGradleFile, buildGradleContent);
}
}
export default initGenerator;

View File

@ -0,0 +1,6 @@
export interface InitGeneratorSchema {
skipFormat?: boolean;
skipPackageJson?: boolean;
keepExistingVersions?: boolean;
updatePackageScripts?: boolean;
}

View File

@ -0,0 +1,34 @@
{
"$schema": "https://json-schema.org/schema",
"$id": "NxGradleInitSchema",
"title": "Gradle Init Generator",
"description": "Initializes a Gradle project in the current workspace.",
"type": "object",
"properties": {
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
},
"skipPackageJson": {
"type": "boolean",
"default": false,
"description": "Do not add dependencies to `package.json`.",
"x-priority": "internal"
},
"keepExistingVersions": {
"type": "boolean",
"x-priority": "internal",
"description": "Keep existing dependencies versions",
"default": false
},
"updatePackageScripts": {
"type": "boolean",
"x-priority": "internal",
"description": "Update `package.json` scripts with inferred targets",
"default": false
}
},
"required": []
}

View File

@ -39,12 +39,14 @@ export const createDependencies: CreateDependencies = async (
if (projectName && depsFile) {
dependencies = dependencies.concat(
processGradleDependencies(
depsFile,
gradleProjectToProjectName,
projectName,
gradleFile,
context
Array.from(
processGradleDependencies(
depsFile,
gradleProjectToProjectName,
projectName,
gradleFile,
context
)
)
);
}
@ -85,12 +87,15 @@ function processGradleDependencies(
sourceProjectName: string,
gradleFile: string,
context: CreateDependenciesContext
) {
const dependencies: RawProjectGraphDependency[] = [];
): Set<RawProjectGraphDependency> {
const dependencies: Set<RawProjectGraphDependency> = new Set();
const lines = readFileSync(depsFile).toString().split('\n');
let inDeps = false;
for (const line of lines) {
if (line.startsWith('implementationDependenciesMetadata')) {
if (
line.startsWith('implementationDependenciesMetadata') ||
line.startsWith('compileClasspath')
) {
inDeps = true;
continue;
}
@ -116,7 +121,7 @@ function processGradleDependencies(
sourceFile: gradleFile,
};
validateDependency(dependency, context);
dependencies.push(dependency);
dependencies.add(dependency);
}
}
}

View File

@ -0,0 +1,10 @@
import { readNxJson, Tree } from '@nx/devkit';
export function hasGradlePlugin(tree: Tree): boolean {
const nxJson = readNxJson(tree);
return !!nxJson.plugins?.some((p) =>
typeof p === 'string'
? p === '@nx/gradle/plugin'
: p.plugin === '@nx/gradle/plugin'
);
}

View File

@ -0,0 +1 @@
export const nxVersion = require('../../package.json').version;

View File

@ -63,7 +63,7 @@ async function installPackage(pkgName: string, version: string): Promise<void> {
writeJsonFile('nx.json', nxJson);
try {
await runNxAsync('');
await runNxAsync('--help');
} catch (e) {
// revert adding the plugin to nx.json
nxJson.installation.plugins[pkgName] = undefined;

View File

@ -49,6 +49,13 @@ export async function initHandler(options: InitArgs): Promise<void> {
);
}
generateDotNxSetup(version);
const { plugins } = await detectPlugins();
plugins.forEach((plugin) => {
execSync(`./nx add ${plugin}`, {
stdio: 'inherit',
});
});
// invokes the wrapper, thus invoking the initial installation process
runNxSync('--version', { stdio: 'ignore' });
return;
@ -190,6 +197,9 @@ async function detectPlugins(): Promise<
}
}
}
if (existsSync('gradlew') || existsSync('gradlew.bat')) {
detectedPlugins.add('@nx/gradle');
}
const plugins = Array.from(detectedPlugins);
@ -214,25 +224,24 @@ async function detectPlugins(): Promise<
if (pluginsToInstall?.length === 0) return undefined;
const updatePackageScripts = await prompt<{ updatePackageScripts: string }>([
{
name: 'updatePackageScripts',
type: 'autocomplete',
message: `Do you want to start using Nx in your package.json scripts?`,
choices: [
{
name: 'Yes',
},
{
name: 'No',
},
],
initial: 0,
},
]).then((r) => r.updatePackageScripts === 'Yes');
const updatePackageScripts =
existsSync('package.json') &&
(await prompt<{ updatePackageScripts: string }>([
{
name: 'updatePackageScripts',
type: 'autocomplete',
message: `Do you want to start using Nx in your package.json scripts?`,
choices: [
{
name: 'Yes',
},
{
name: 'No',
},
],
initial: 0,
},
]).then((r) => r.updatePackageScripts === 'Yes'));
return {
plugins: pluginsToInstall,
updatePackageScripts,
};
return { plugins: pluginsToInstall, updatePackageScripts };
}

View File

@ -29,6 +29,7 @@ const scopes = [
{ value: 'vue', name: 'vue: anything Vue specific' },
{ value: 'web', name: 'web: anything Web specific' },
{ value: 'webpack', name: 'webpack: anything Webpack specific' },
{ value: 'gradle', name: 'gradle: anything Gradle specific'},
{value: 'module-federation', name: 'module-federation: anything Module Federation specific'},
];