feat(core): add gradle plugin (#21055)
This commit is contained in:
parent
e687aad0e4
commit
42ad573405
@ -69,6 +69,50 @@ commands:
|
||||
- ~/.pnpm-store
|
||||
- ~/.cache/Cypress
|
||||
- node_modules
|
||||
install-sdkman:
|
||||
description: Install SDKMAN
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore SDKMAN executable and binaries from cache
|
||||
keys:
|
||||
- sdkman-cli-{{ arch }}-v2
|
||||
- run:
|
||||
name: Installing SDKMAN
|
||||
command: |
|
||||
if [ ! -d ~/.sdkman ]
|
||||
then
|
||||
curl -s "https://get.sdkman.io?rcupdate=false" | bash
|
||||
sed -i -e 's/sdkman_auto_answer=false/sdkman_auto_answer=true/g' ~/.sdkman/etc/config
|
||||
fi
|
||||
echo -e '\nsource "/home/circleci/.sdkman/bin/sdkman-init.sh"' >> $BASH_ENV
|
||||
source $BASH_ENV
|
||||
sdk version
|
||||
- save_cache:
|
||||
name: Save SDKMAN executable and binaries to cache
|
||||
key: sdkman-cli-{{ arch }}-v2
|
||||
paths:
|
||||
- ~/.sdkman
|
||||
install-gradle:
|
||||
description: Install gradle
|
||||
parameters:
|
||||
gradle-version:
|
||||
type: string
|
||||
default: ''
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore Gradle binary from cache
|
||||
keys:
|
||||
- gradle-cli-{{ arch }}-v1
|
||||
- run:
|
||||
name: Installing Gradle
|
||||
command: |
|
||||
sdk install gradle << parameters.gradle-version >>
|
||||
gradle --version
|
||||
- save_cache:
|
||||
name: Save Gradle binary to cache
|
||||
key: gradle-cli-{{ arch }}-v1
|
||||
paths:
|
||||
- ~/.sdkman/candidates/gradle/
|
||||
# -------------------------
|
||||
# JOBS
|
||||
# -------------------------
|
||||
@ -96,6 +140,9 @@ jobs:
|
||||
sudo apt-get install -y ca-certificates lsof
|
||||
- browser-tools/install-chrome
|
||||
- browser-tools/install-chromedriver
|
||||
- install-sdkman
|
||||
- install-gradle:
|
||||
gradle-version: '8.5'
|
||||
- run-pnpm-install:
|
||||
os: linux
|
||||
- run:
|
||||
|
||||
4
.github/workflows/e2e-matrix.yml
vendored
4
.github/workflows/e2e-matrix.yml
vendored
@ -165,6 +165,8 @@ jobs:
|
||||
codeowners: 'S04SJ6HHP0X'
|
||||
- project: e2e-expo
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-gradle
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-jest
|
||||
codeowners: 'S04T16BTJJY'
|
||||
- project: e2e-js
|
||||
@ -242,6 +244,8 @@ jobs:
|
||||
project: e2e-esbuild
|
||||
- node_version: 18
|
||||
project: e2e-expo
|
||||
- node_version: 18
|
||||
project: e2e-gradle
|
||||
- node_version: 18
|
||||
project: e2e-jest
|
||||
- node_version: 18
|
||||
|
||||
@ -20,6 +20,8 @@ launch-templates:
|
||||
node_modules
|
||||
~/.cache/Cypress
|
||||
~/.pnpm-store
|
||||
~/.sdkman
|
||||
~/.sdkman/candidates/gradle
|
||||
BASE_BRANCH: 'master'
|
||||
- name: Install e2e deps
|
||||
script: |
|
||||
@ -49,3 +51,21 @@ launch-templates:
|
||||
|
||||
- name: Load Cargo Env
|
||||
script: echo "PATH=$HOME/.cargo/bin:$PATH" >> $NX_CLOUD_ENV
|
||||
|
||||
- name: Install zip and unzip
|
||||
script: sudo apt-get -yqq install zip unzip
|
||||
|
||||
- name: Install SDKMAN and gradle
|
||||
script: |
|
||||
if [ ! -d $HOME/.sdkman ]
|
||||
then
|
||||
curl -s "https://get.sdkman.io" | bash
|
||||
source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||
fi
|
||||
sdk version
|
||||
if [ ! -d $HOME/.sdkman/candidates/gradle/8.5 ]
|
||||
then
|
||||
sdk install gradle 8.5
|
||||
fi
|
||||
gradle --version
|
||||
echo "PATH=$HOME/.sdkman/candidates/gradle/8.5/bin:$PATH" >> $NX_CLOUD_ENV
|
||||
|
||||
@ -144,6 +144,10 @@ rust-toolchain @nrwl/nx-native-reviewers
|
||||
/packages/devkit/public-api.ts @FrozenPandaz @vsavkin
|
||||
/packages/devkit/nx.ts @FrozenPandaz @vsavkin
|
||||
|
||||
# Gradle
|
||||
/packages/gradle/** @FrozenPandaz @xiongemi
|
||||
/e2e/gradle/** @FrozenPandaz @xiongemi
|
||||
|
||||
# Nx-Plugin
|
||||
/docs/generated/packages/plugin/** @nrwl/nx-devkit-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/plugin/** @nrwl/nx-devkit-reviewers @nrwl/nx-docs-reviewers
|
||||
|
||||
14
e2e/gradle/jest.config.ts
Normal file
14
e2e/gradle/jest.config.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||
maxWorkers: 1,
|
||||
globals: {},
|
||||
globalSetup: '../utils/global-setup.ts',
|
||||
globalTeardown: '../utils/global-teardown.ts',
|
||||
displayName: 'e2e-gradle',
|
||||
testTimeout: 600000,
|
||||
preset: '../../jest.preset.js',
|
||||
};
|
||||
10
e2e/gradle/project.json
Normal file
10
e2e/gradle/project.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "e2e-gradle",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "e2e/gradle",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"e2e": {}
|
||||
},
|
||||
"implicitDependencies": ["eslint"]
|
||||
}
|
||||
87
e2e/gradle/src/gradle.test.ts
Normal file
87
e2e/gradle/src/gradle.test.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
createFile,
|
||||
e2eConsoleLogger,
|
||||
newProject,
|
||||
runCLI,
|
||||
runCommand,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e/utils';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
describe('Gradle', () => {
|
||||
let gradleProjectName = uniq('my-gradle-project');
|
||||
|
||||
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);
|
||||
|
||||
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`
|
||||
);
|
||||
});
|
||||
|
||||
it('should track dependencies for new app', () => {
|
||||
createFile(
|
||||
'app2/build.gradle.kts',
|
||||
`
|
||||
plugins {
|
||||
id("gradleProject.kotlin-application-conventions")
|
||||
}
|
||||
|
||||
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) {
|
||||
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`
|
||||
)
|
||||
);
|
||||
updateJson('nx.json', (nxJson) => {
|
||||
nxJson.plugins = ['@nx/gradle'];
|
||||
return nxJson;
|
||||
});
|
||||
createFile(
|
||||
'build.gradle.kts',
|
||||
`allprojects {
|
||||
apply {
|
||||
plugin("project-report")
|
||||
}
|
||||
}`
|
||||
);
|
||||
}
|
||||
13
e2e/gradle/tsconfig.json
Normal file
13
e2e/gradle/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"include": [],
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
e2e/gradle/tsconfig.spec.json
Normal file
20
e2e/gradle/tsconfig.spec.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx",
|
||||
"**/*.d.ts",
|
||||
"jest.config.ts"
|
||||
]
|
||||
}
|
||||
@ -43,6 +43,7 @@ const nxPackages = [
|
||||
`@nx/eslint-plugin`,
|
||||
`@nx/express`,
|
||||
`@nx/esbuild`,
|
||||
`@nx/gradle`,
|
||||
`@nx/jest`,
|
||||
`@nx/js`,
|
||||
`@nx/eslint`,
|
||||
|
||||
9
nx-dev/nx-dev/public/images/icons/gradle.svg
Normal file
9
nx-dev/nx-dev/public/images/icons/gradle.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Gradle" role="img"
|
||||
viewBox="0 0 512 512">
|
||||
<rect
|
||||
width="512" height="512"
|
||||
rx="15%"
|
||||
fill="#ffffff"/>
|
||||
<path d="M427 132.7a61 61 0 00-85-1a6 6 0 000 9l7 8a6 6 0 008 1a35 35 0 0146 53c-48 48-113 -87 -259 -17a20 20 0 00-9 28l25 43a20 20 0 0027 7l1 0l0 0l11-6a257 257 0 0035-26a6 6 0 018 0v0a6 6 0 010 9a263 263 0 01-37 28h0l-11 6a31 31 0 01-15 4a32 32 0 01-28-16L126 219C81 259 53 314 68.13 392.26a6 6 0 006 4.74H100.6a6 6 0 005.93-5.3a40 40 0 0178.62 0a6 6 0 005.72 5.08h26.2a6 6 0 005.7-5.1a40 40 0 0178.6 0a6 6 0 005.7 5h26a6 6 0 005.8-5.72c1-37 10 -79 38.7 -100c98-73 72 -136 49.4 -158.3zm-100 110l-19-9v0a12 12 0 1119 9z" fill="#02303a"/></svg>
|
||||
|
After Width: | Height: | Size: 734 B |
@ -8,6 +8,7 @@ export const iconsMap: Record<string, string> = {
|
||||
'eslint-plugin': '/images/icons/eslint.svg',
|
||||
expo: '/images/icons/expo.svg',
|
||||
express: '/images/icons/express.svg',
|
||||
gradle: '/images/icons/gradle.svg',
|
||||
jest: '/images/icons/jest.svg',
|
||||
js: '/images/icons/javascript.svg',
|
||||
eslint: '/images/icons/eslint.svg',
|
||||
|
||||
37
packages/gradle/.eslintrc.json
Normal file
37
packages/gradle/.eslintrc.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.json"],
|
||||
"parser": "jsonc-eslint-parser",
|
||||
"rules": {
|
||||
"@nx/dependency-checks": [
|
||||
"error",
|
||||
{
|
||||
"ignoredDependencies": ["nx"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["./package.json"],
|
||||
"parser": "jsonc-eslint-parser",
|
||||
"rules": {
|
||||
"@nx/nx-plugin-checks": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
18
packages/gradle/README.md
Normal file
18
packages/gradle/README.md
Normal file
@ -0,0 +1,18 @@
|
||||
<p style="text-align: center;">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-dark.svg">
|
||||
<img alt="Nx - Smart Monorepos · Fast CI" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-light.svg" width="100%">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
{{links}}
|
||||
|
||||
<hr>
|
||||
|
||||
# Nx: Smart Monorepos · Fast CI
|
||||
|
||||
Nx is a build system with built-in tooling and advanced CI capabilities. It helps you maintain and scale monorepos, both locally and on CI.
|
||||
|
||||
This package is a [Gradle plugin for Nx](https://nx.dev/gradle/overview).
|
||||
|
||||
{{content}}
|
||||
1
packages/gradle/index.ts
Normal file
1
packages/gradle/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './plugin';
|
||||
10
packages/gradle/jest.config.ts
Normal file
10
packages/gradle/jest.config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html', 'json'],
|
||||
globals: {},
|
||||
displayName: 'gradle',
|
||||
preset: '../../jest.preset.js',
|
||||
};
|
||||
4
packages/gradle/migrations.json
Normal file
4
packages/gradle/migrations.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"generators": {},
|
||||
"packageJsonUpdates": {}
|
||||
}
|
||||
34
packages/gradle/package.json
Normal file
34
packages/gradle/package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@nx/gradle",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "The Nx Plugin for gradle",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
"directory": "packages/gradle"
|
||||
},
|
||||
"keywords": [
|
||||
"Monorepo",
|
||||
"Java",
|
||||
"Gradle",
|
||||
"CLI"
|
||||
],
|
||||
"main": "./index",
|
||||
"typings": "./index.d.ts",
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nrwl/nx/issues"
|
||||
},
|
||||
"homepage": "https://nx.dev",
|
||||
"nx-migrate": {
|
||||
"migrations": "./migrations.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nx/devkit": "file:../devkit"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
2
packages/gradle/plugin.ts
Normal file
2
packages/gradle/plugin.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { createDependencies } from './src/plugin/dependencies';
|
||||
export { createNodes } from './src/plugin/nodes';
|
||||
73
packages/gradle/project.json
Normal file
73
packages/gradle/project.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "gradle",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/gradle/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"nx-release-publish": {
|
||||
"dependsOn": ["^nx-release-publish"],
|
||||
"executor": "@nx/js:release-publish",
|
||||
"options": {
|
||||
"packageRoot": "build/packages/gradle"
|
||||
}
|
||||
},
|
||||
"build-base": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"options": {
|
||||
"assets": [
|
||||
{
|
||||
"input": "packages/gradle",
|
||||
"glob": "**/@(files|files-angular)/**",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"input": "packages/gradle",
|
||||
"glob": "**/files/**/.gitkeep",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"input": "packages/gradle",
|
||||
"glob": "**/*.json",
|
||||
"ignore": ["**/tsconfig*.json", "project.json", ".eslintrc.json"],
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"input": "packages/gradle",
|
||||
"glob": "**/*.js",
|
||||
"ignore": ["**/jest.config.js"],
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"input": "packages/gradle",
|
||||
"glob": "**/*.d.ts",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"input": "",
|
||||
"glob": "LICENSE",
|
||||
"output": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"outputs": ["{workspaceRoot}/build/packages/gradle"],
|
||||
"options": {
|
||||
"command": "node ./scripts/copy-readme.js gradle"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"]
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "packages/gradle/jest.config.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
124
packages/gradle/src/plugin/dependencies.ts
Normal file
124
packages/gradle/src/plugin/dependencies.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import {
|
||||
CreateDependencies,
|
||||
CreateDependenciesContext,
|
||||
DependencyType,
|
||||
FileMap,
|
||||
RawProjectGraphDependency,
|
||||
validateDependency,
|
||||
} from '@nx/devkit';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { basename } from 'node:path';
|
||||
|
||||
import {
|
||||
getGradleReport,
|
||||
invalidateGradleReportCache,
|
||||
} from '../utils/get-gradle-report';
|
||||
import { calculatedTargets, writeTargetsToCache } from './nodes';
|
||||
|
||||
export const createDependencies: CreateDependencies = async (
|
||||
_,
|
||||
context: CreateDependenciesContext
|
||||
) => {
|
||||
const gradleFiles: string[] = findGradleFiles(context.filesToProcess);
|
||||
if (gradleFiles.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let dependencies: RawProjectGraphDependency[] = [];
|
||||
const gradleDependenciesStart = performance.mark('gradleDependencies:start');
|
||||
const {
|
||||
gradleFileToGradleProjectMap,
|
||||
gradleProjectToProjectName,
|
||||
buildFileToDepsMap,
|
||||
} = getGradleReport();
|
||||
|
||||
for (const gradleFile of gradleFiles) {
|
||||
const gradleProject = gradleFileToGradleProjectMap.get(gradleFile);
|
||||
const projectName = gradleProjectToProjectName.get(gradleProject);
|
||||
const depsFile = buildFileToDepsMap.get(gradleFile);
|
||||
|
||||
if (projectName && depsFile) {
|
||||
dependencies = dependencies.concat(
|
||||
processGradleDependencies(
|
||||
depsFile,
|
||||
gradleProjectToProjectName,
|
||||
projectName,
|
||||
gradleFile,
|
||||
context
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
const gradleDependenciesEnd = performance.mark('gradleDependencies:end');
|
||||
performance.measure(
|
||||
'gradleDependencies',
|
||||
gradleDependenciesStart.name,
|
||||
gradleDependenciesEnd.name
|
||||
);
|
||||
|
||||
writeTargetsToCache(calculatedTargets);
|
||||
if (dependencies.length) {
|
||||
invalidateGradleReportCache();
|
||||
}
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
const gradleConfigFileNames = new Set(['build.gradle', 'build.gradle.kts']);
|
||||
|
||||
function findGradleFiles(fileMap: FileMap): string[] {
|
||||
const gradleFiles: string[] = [];
|
||||
|
||||
for (const [_, files] of Object.entries(fileMap.projectFileMap)) {
|
||||
for (const file of files) {
|
||||
if (gradleConfigFileNames.has(basename(file.file))) {
|
||||
gradleFiles.push(file.file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gradleFiles;
|
||||
}
|
||||
|
||||
function processGradleDependencies(
|
||||
depsFile: string,
|
||||
gradleProjectToProjectName: Map<string, string>,
|
||||
sourceProjectName: string,
|
||||
gradleFile: string,
|
||||
context: CreateDependenciesContext
|
||||
) {
|
||||
const dependencies: RawProjectGraphDependency[] = [];
|
||||
const lines = readFileSync(depsFile).toString().split('\n');
|
||||
let inDeps = false;
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('implementationDependenciesMetadata')) {
|
||||
inDeps = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inDeps) {
|
||||
if (line === '') {
|
||||
inDeps = false;
|
||||
continue;
|
||||
}
|
||||
const [indents, dep] = line.split('--- ');
|
||||
if ((indents === '\\' || indents === '+') && dep.startsWith('project ')) {
|
||||
const gradleProjectName = dep
|
||||
.substring('project '.length)
|
||||
.replace(/ \(n\)$/, '')
|
||||
.trim();
|
||||
const target = gradleProjectToProjectName.get(
|
||||
gradleProjectName
|
||||
) as string;
|
||||
const dependency: RawProjectGraphDependency = {
|
||||
source: sourceProjectName,
|
||||
target,
|
||||
type: DependencyType.static,
|
||||
sourceFile: gradleFile,
|
||||
};
|
||||
validateDependency(dependency, context);
|
||||
dependencies.push(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
182
packages/gradle/src/plugin/nodes.ts
Normal file
182
packages/gradle/src/plugin/nodes.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import {
|
||||
CreateNodes,
|
||||
CreateNodesContext,
|
||||
TargetConfiguration,
|
||||
readJsonFile,
|
||||
writeJsonFile,
|
||||
} from '@nx/devkit';
|
||||
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
|
||||
|
||||
import { getGradleBinaryPath } from '../utils/exec-gradle';
|
||||
import { getGradleReport } from '../utils/get-gradle-report';
|
||||
|
||||
const nonCacheableGradleTaskTypes = new Set(['Application']);
|
||||
const dependsOnMap = {
|
||||
build: ['^build', 'classes'],
|
||||
test: ['classes'],
|
||||
classes: ['^classes'],
|
||||
};
|
||||
|
||||
interface GradleTask {
|
||||
type: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface GradlePluginOptions {
|
||||
testTargetName?: string;
|
||||
classesTargetName?: string;
|
||||
buildTargetName?: string;
|
||||
[taskTargetName: string]: string | undefined;
|
||||
}
|
||||
|
||||
const cachePath = join(projectGraphCacheDirectory, 'gradle.hash');
|
||||
const targetsCache = existsSync(cachePath) ? readTargetsCache() : {};
|
||||
|
||||
export const calculatedTargets: Record<
|
||||
string,
|
||||
{ name: string; targets: Record<string, TargetConfiguration> }
|
||||
> = {};
|
||||
|
||||
function readTargetsCache(): Record<
|
||||
string,
|
||||
{ name: string; targets: Record<string, TargetConfiguration> }
|
||||
> {
|
||||
return readJsonFile(cachePath);
|
||||
}
|
||||
|
||||
export function writeTargetsToCache(
|
||||
targets: Record<
|
||||
string,
|
||||
{ name: string; targets: Record<string, TargetConfiguration> }
|
||||
>
|
||||
) {
|
||||
writeJsonFile(cachePath, targets);
|
||||
}
|
||||
|
||||
export const createNodes: CreateNodes<GradlePluginOptions> = [
|
||||
'**/build.{gradle.kts,gradle}',
|
||||
(
|
||||
gradleFilePath,
|
||||
options: GradlePluginOptions | undefined,
|
||||
context: CreateNodesContext
|
||||
) => {
|
||||
const projectRoot = dirname(gradleFilePath);
|
||||
|
||||
const hash = calculateHashForCreateNodes(
|
||||
projectRoot,
|
||||
options ?? {},
|
||||
context
|
||||
);
|
||||
if (targetsCache[hash]) {
|
||||
calculatedTargets[hash] = targetsCache[hash];
|
||||
return {
|
||||
projects: {
|
||||
[projectRoot]: targetsCache[hash],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
tasksMap,
|
||||
gradleProjectToTasksTypeMap,
|
||||
gradleFileToOutputDirsMap,
|
||||
gradleFileToGradleProjectMap,
|
||||
gradleProjectToProjectName,
|
||||
} = getGradleReport();
|
||||
|
||||
const gradleProject = gradleFileToGradleProjectMap.get(
|
||||
gradleFilePath
|
||||
) as string;
|
||||
const projectName = gradleProjectToProjectName.get(gradleProject);
|
||||
if (!projectName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const availableTaskNames = tasksMap.get(gradleFilePath) as string[];
|
||||
const tasksTypeMap = gradleProjectToTasksTypeMap.get(
|
||||
gradleProject
|
||||
) as Map<string, string>;
|
||||
const tasks: GradleTask[] = availableTaskNames.map((taskName) => {
|
||||
return {
|
||||
type: tasksTypeMap.get(taskName) ?? 'Unknown',
|
||||
name: taskName,
|
||||
};
|
||||
});
|
||||
|
||||
const outputDirs = gradleFileToOutputDirsMap.get(gradleFilePath) as Map<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
|
||||
const targets = createGradleTargets(
|
||||
tasks,
|
||||
projectRoot,
|
||||
options,
|
||||
context,
|
||||
outputDirs
|
||||
);
|
||||
calculatedTargets[hash] = {
|
||||
name: projectName,
|
||||
targets,
|
||||
};
|
||||
|
||||
return {
|
||||
projects: {
|
||||
[projectRoot]: {
|
||||
root: projectRoot,
|
||||
name: projectName,
|
||||
targets,
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {};
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
function createGradleTargets(
|
||||
tasks: GradleTask[],
|
||||
projectRoot: string,
|
||||
options: GradlePluginOptions | undefined,
|
||||
context: CreateNodesContext,
|
||||
outputDirs: Map<string, string>
|
||||
): Record<string, TargetConfiguration> {
|
||||
const inputsMap = createInputsMap(context);
|
||||
|
||||
const targets: Record<string, TargetConfiguration> = {};
|
||||
for (const task of tasks) {
|
||||
const targetName = options?.[`${task.name}TargetName`] ?? task.name;
|
||||
|
||||
const outputs = outputDirs.get(task.name);
|
||||
targets[targetName] = {
|
||||
command: `${getGradleBinaryPath()} ${task.name}`,
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
},
|
||||
cache: !nonCacheableGradleTaskTypes.has(task.type),
|
||||
inputs: inputsMap[task.name],
|
||||
outputs: outputs ? [outputs] : undefined,
|
||||
dependsOn: dependsOnMap[task.name],
|
||||
};
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
function createInputsMap(
|
||||
context: CreateNodesContext
|
||||
): Record<string, TargetConfiguration['inputs']> {
|
||||
const namedInputs = context.nxJsonConfiguration.namedInputs;
|
||||
return {
|
||||
build: namedInputs?.production
|
||||
? ['production', '^production']
|
||||
: ['default', '^default'],
|
||||
test: ['default', namedInputs?.production ? '^production' : '^default'],
|
||||
classes: ['default', '^default'],
|
||||
};
|
||||
}
|
||||
63
packages/gradle/src/utils/exec-gradle.ts
Normal file
63
packages/gradle/src/utils/exec-gradle.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
import { ExecFileOptions } from 'child_process';
|
||||
import {
|
||||
ExecFileSyncOptionsWithBufferEncoding,
|
||||
execFile,
|
||||
execFileSync,
|
||||
} from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
export function execGradle(
|
||||
args: string[],
|
||||
execOptions: ExecFileSyncOptionsWithBufferEncoding
|
||||
) {
|
||||
const gradleBinaryPath = getGradleBinaryPath();
|
||||
|
||||
return execFileSync(gradleBinaryPath, args, execOptions);
|
||||
}
|
||||
|
||||
export function getGradleBinaryPath() {
|
||||
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;
|
||||
}
|
||||
|
||||
export function execGradleAsync(
|
||||
args: ReadonlyArray<string>,
|
||||
execOptions: ExecFileOptions
|
||||
) {
|
||||
const gradleBinaryPath = getGradleBinaryPath();
|
||||
if (!existsSync(gradleBinaryPath)) {
|
||||
throw new Error('Gradle is not setup. Run "gradle init"');
|
||||
}
|
||||
|
||||
return new Promise<Buffer>((res, rej) => {
|
||||
const cp = execFile(gradleBinaryPath, args, execOptions);
|
||||
|
||||
let stdout = Buffer.from('');
|
||||
cp.stdout?.on('data', (data) => {
|
||||
stdout += data;
|
||||
});
|
||||
|
||||
cp.on('exit', (code) => {
|
||||
if (code === 0) {
|
||||
res(stdout);
|
||||
} else {
|
||||
rej(
|
||||
new Error(
|
||||
`Executing Gradle with ${args.join(
|
||||
' '
|
||||
)} failed with code: ${code}. \nLogs: ${stdout}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
185
packages/gradle/src/utils/get-gradle-report.ts
Normal file
185
packages/gradle/src/utils/get-gradle-report.ts
Normal file
@ -0,0 +1,185 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
import { hashWithWorkspaceContext } from 'nx/src/utils/workspace-context';
|
||||
|
||||
import { execGradle } from './exec-gradle';
|
||||
|
||||
interface GradleReport {
|
||||
gradleFileToGradleProjectMap: Map<string, string>;
|
||||
buildFileToDepsMap: Map<string, string>;
|
||||
gradleFileToOutputDirsMap: Map<string, Map<string, string>>;
|
||||
gradleProjectToTasksTypeMap: Map<string, Map<string, string>>;
|
||||
tasksMap: Map<string, string[]>;
|
||||
gradleProjectToProjectName: Map<string, string>;
|
||||
}
|
||||
|
||||
let gradleReportCache: GradleReport;
|
||||
|
||||
export function invalidateGradleReportCache() {
|
||||
gradleReportCache = undefined;
|
||||
}
|
||||
|
||||
export function getGradleReport(): GradleReport {
|
||||
if (gradleReportCache) {
|
||||
return gradleReportCache;
|
||||
}
|
||||
|
||||
const gradleProjectReportStart = performance.mark(
|
||||
'gradleProjectReport:start'
|
||||
);
|
||||
const projectReportLines = execGradle(['projectReport'], {
|
||||
cwd: workspaceRoot,
|
||||
})
|
||||
.toString()
|
||||
.split('\n');
|
||||
const gradleProjectReportEnd = performance.mark('gradleProjectReport:end');
|
||||
performance.measure(
|
||||
'gradleProjectReport',
|
||||
gradleProjectReportStart.name,
|
||||
gradleProjectReportEnd.name
|
||||
);
|
||||
gradleReportCache = processProjectReports(projectReportLines);
|
||||
return gradleReportCache;
|
||||
}
|
||||
|
||||
function processProjectReports(projectReportLines: string[]): GradleReport {
|
||||
/**
|
||||
* Map of Gradle File path to Gradle Project Name
|
||||
*/
|
||||
const gradleFileToGradleProjectMap = new Map<string, string>();
|
||||
/**
|
||||
* Map of Gradle Project Name to Gradle File
|
||||
*/
|
||||
const gradleProjectToGradleFileMap = new Map<string, string>();
|
||||
const dependenciesMap = new Map<string, string>();
|
||||
/**
|
||||
* Map of Gradle Build File to available tasks
|
||||
*/
|
||||
const tasksMap = new Map<string, string[]>();
|
||||
/**
|
||||
* Map of Gradle Build File to tasks type map
|
||||
*/
|
||||
const gradleProjectToTasksTypeMap = new Map<string, Map<string, string>>();
|
||||
const gradleProjectToProjectName = new Map<string, string>();
|
||||
/**
|
||||
* Map of buildFile to dependencies report path
|
||||
*/
|
||||
const buildFileToDepsMap = new Map<string, string>();
|
||||
/**
|
||||
* Map fo possible output files of each gradle file
|
||||
* e.g. {build.gradle.kts: { projectReportDir: '' testReportDir: '' }}
|
||||
*/
|
||||
const gradleFileToOutputDirsMap = new Map<string, Map<string, string>>();
|
||||
|
||||
projectReportLines.forEach((line, index) => {
|
||||
if (line.startsWith('> Task ')) {
|
||||
const nextLine = projectReportLines[index + 1];
|
||||
if (line.endsWith(':dependencyReport')) {
|
||||
const gradleProject = line.substring(
|
||||
'> Task '.length,
|
||||
line.length - ':dependencyReport'.length
|
||||
);
|
||||
const [_, file] = nextLine.split('file://');
|
||||
dependenciesMap.set(gradleProject, file);
|
||||
}
|
||||
if (line.endsWith('propertyReport')) {
|
||||
const gradleProject = line.substring(
|
||||
'> Task '.length,
|
||||
line.length - ':propertyReport'.length
|
||||
);
|
||||
const [_, file] = nextLine.split('file://');
|
||||
const propertyReportLines = readFileSync(file).toString().split('\n');
|
||||
|
||||
let projectName: string,
|
||||
absBuildFilePath: string,
|
||||
absBuildDirPath: string;
|
||||
const tasks: string[] = [];
|
||||
const outputDirMap = new Map<string, string>();
|
||||
for (const line of propertyReportLines) {
|
||||
if (line.startsWith('name: ')) {
|
||||
projectName = line.substring('name: '.length);
|
||||
}
|
||||
if (line.startsWith('buildFile: ')) {
|
||||
absBuildFilePath = line.substring('buildFile: '.length);
|
||||
}
|
||||
if (line.startsWith('buildDir: ')) {
|
||||
absBuildDirPath = line.substring('buildDir: '.length);
|
||||
}
|
||||
if (line.includes(': task ')) {
|
||||
const taskSegments = line.split(': task ');
|
||||
tasks.push(taskSegments[0]);
|
||||
}
|
||||
if (line.includes('Dir: ')) {
|
||||
const [dirName, dirPath] = line.split(': ');
|
||||
const taskName = dirName.replace('Dir', '');
|
||||
outputDirMap.set(
|
||||
taskName,
|
||||
`{workspaceRoot}/${relative(workspaceRoot, dirPath)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!projectName || !absBuildFilePath || !absBuildDirPath) {
|
||||
return;
|
||||
}
|
||||
const buildFile = relative(workspaceRoot, absBuildFilePath);
|
||||
const buildDir = relative(workspaceRoot, absBuildDirPath);
|
||||
buildFileToDepsMap.set(
|
||||
buildFile,
|
||||
dependenciesMap.get(gradleProject) as string
|
||||
);
|
||||
|
||||
outputDirMap.set('build', `{workspaceRoot}/${buildDir}`);
|
||||
outputDirMap.set(
|
||||
'classes',
|
||||
`{workspaceRoot}/${join(buildDir, 'classes')}`
|
||||
);
|
||||
|
||||
gradleFileToOutputDirsMap.set(buildFile, outputDirMap);
|
||||
gradleFileToGradleProjectMap.set(buildFile, gradleProject);
|
||||
gradleProjectToGradleFileMap.set(gradleProject, buildFile);
|
||||
gradleProjectToProjectName.set(gradleProject, projectName);
|
||||
tasksMap.set(buildFile, tasks);
|
||||
}
|
||||
if (line.endsWith('taskReport')) {
|
||||
const gradleProject = line.substring(
|
||||
'> Task '.length,
|
||||
line.length - ':taskReport'.length
|
||||
);
|
||||
const [_, file] = nextLine.split('file://');
|
||||
const taskTypeMap = new Map<string, string>();
|
||||
const tasksFileLines = readFileSync(file).toString().split('\n');
|
||||
|
||||
let i = 0;
|
||||
while (i < tasksFileLines.length) {
|
||||
const line = tasksFileLines[i];
|
||||
|
||||
if (line.endsWith('tasks')) {
|
||||
const dashes = new Array(line.length + 1).join('-');
|
||||
if (tasksFileLines[i + 1] === dashes) {
|
||||
const type = line.substring(0, line.length - ' tasks'.length);
|
||||
i++;
|
||||
while (tasksFileLines[++i] !== '') {
|
||||
const [taskName] = tasksFileLines[i].split(' - ');
|
||||
taskTypeMap.set(taskName, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
gradleProjectToTasksTypeMap.set(gradleProject, taskTypeMap);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
gradleFileToGradleProjectMap,
|
||||
buildFileToDepsMap,
|
||||
gradleFileToOutputDirsMap,
|
||||
gradleProjectToTasksTypeMap,
|
||||
tasksMap,
|
||||
gradleProjectToProjectName,
|
||||
};
|
||||
}
|
||||
14
packages/gradle/tsconfig.json
Normal file
14
packages/gradle/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
packages/gradle/tsconfig.lib.json
Normal file
10
packages/gradle/tsconfig.lib.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
9
packages/gradle/tsconfig.spec.json
Normal file
9
packages/gradle/tsconfig.spec.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
|
||||
}
|
||||
@ -35,6 +35,8 @@
|
||||
"@nx/expo": ["packages/expo"],
|
||||
"@nx/expo/*": ["packages/expo/*"],
|
||||
"@nx/express": ["packages/express"],
|
||||
"@nx/gradle": ["packages/gradle/src/index.ts"],
|
||||
"@nx/gradle/*": ["packages/gradle/*"],
|
||||
"@nx/graph/project-details": ["graph/project-details/src/index.ts"],
|
||||
"@nx/graph/shared": ["graph/shared/src/index.ts"],
|
||||
"@nx/graph/ui-code-block": ["graph/ui-code-block/src/index.ts"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user