feat(core): add gradle plugin (#21055)

This commit is contained in:
Emily Xiong 2024-02-29 14:15:54 -05:00 committed by GitHub
parent e687aad0e4
commit 42ad573405
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 998 additions and 0 deletions

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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"]
}

View 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
View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["node", "jest"]
},
"include": [],
"files": [],
"references": [
{
"path": "./tsconfig.spec.json"
}
]
}

View 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"
]
}

View File

@ -43,6 +43,7 @@ const nxPackages = [
`@nx/eslint-plugin`,
`@nx/express`,
`@nx/esbuild`,
`@nx/gradle`,
`@nx/jest`,
`@nx/js`,
`@nx/eslint`,

View 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

View File

@ -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',

View 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
View 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
View File

@ -0,0 +1 @@
export * from './plugin';

View 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',
};

View File

@ -0,0 +1,4 @@
{
"generators": {},
"packageJsonUpdates": {}
}

View 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"
}
}

View File

@ -0,0 +1,2 @@
export { createDependencies } from './src/plugin/dependencies';
export { createNodes } from './src/plugin/nodes';

View 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": []
}

View 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;
}

View 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'],
};
}

View 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}`
)
);
}
});
});
}

View 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,
};
}

View File

@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View 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"]
}

View 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"]
}

View File

@ -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"],