UNPKG

@nx/gradle

Version:

The Nx Plugin for Gradle allows Gradle tasks to be run through Nx

281 lines (280 loc) • 14.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.writeGradleReportToCache = writeGradleReportToCache; exports.getCurrentGradleReport = getCurrentGradleReport; exports.populateGradleReport = populateGradleReport; exports.processProjectReports = processProjectReports; exports.processGradleDependencies = processGradleDependencies; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const devkit_1 = require("@nx/devkit"); const workspace_context_1 = require("nx/src/utils/workspace-context"); const path_1 = require("path"); const split_config_files_1 = require("../../utils/split-config-files"); const get_project_report_lines_1 = require("./get-project-report-lines"); const cache_directory_1 = require("nx/src/utils/cache-directory"); const exec_gradle_1 = require("../../utils/exec-gradle"); function readGradleReportCache(cachePath, hash) { const gradleReportJson = (0, node_fs_1.existsSync)(cachePath) ? (0, devkit_1.readJsonFile)(cachePath) : undefined; if (!gradleReportJson || gradleReportJson.hash !== hash) { return; } let results = { gradleFileToGradleProjectMap: new Map(Object.entries(gradleReportJson['gradleFileToGradleProjectMap'])), gradleProjectToDepsMap: new Map(Object.entries(gradleReportJson['gradleProjectToDepsMap']).map(([key, value]) => [key, new Set(value)])), gradleFileToOutputDirsMap: new Map(Object.entries(gradleReportJson['gradleFileToOutputDirsMap']).map(([key, value]) => [key, new Map(Object.entries(value))])), gradleProjectToTasksTypeMap: new Map(Object.entries(gradleReportJson['gradleProjectToTasksTypeMap']).map(([key, value]) => [key, new Map(Object.entries(value))])), gradleProjectToTasksMap: new Map(Object.entries(gradleReportJson['gradleProjectToTasksMap']).map(([key, value]) => [key, new Set(value)])), gradleProjectToProjectName: new Map(Object.entries(gradleReportJson['gradleProjectToProjectName'])), gradleProjectNameToProjectRootMap: new Map(Object.entries(gradleReportJson['gradleProjectNameToProjectRootMap'])), gradleProjectToChildProjects: new Map(Object.entries(gradleReportJson['gradleProjectToChildProjects'])), }; return results; } function writeGradleReportToCache(cachePath, results, hash) { let gradleReportJson = { hash, gradleFileToGradleProjectMap: Object.fromEntries(results.gradleFileToGradleProjectMap), gradleProjectToDepsMap: Object.fromEntries(Array.from(results.gradleProjectToDepsMap).map(([key, value]) => [ key, Array.from(value), ])), gradleFileToOutputDirsMap: Object.fromEntries(Array.from(results.gradleFileToOutputDirsMap).map(([key, value]) => [ key, Object.fromEntries(value), ])), gradleProjectToTasksTypeMap: Object.fromEntries(Array.from(results.gradleProjectToTasksTypeMap).map(([key, value]) => [ key, Object.fromEntries(value), ])), gradleProjectToTasksMap: Object.fromEntries(Array.from(results.gradleProjectToTasksMap).map(([key, value]) => [ key, Array.from(value), ])), gradleProjectToProjectName: Object.fromEntries(results.gradleProjectToProjectName), gradleProjectNameToProjectRootMap: Object.fromEntries(results.gradleProjectNameToProjectRootMap), gradleProjectToChildProjects: Object.fromEntries(results.gradleProjectToChildProjects), }; try { (0, devkit_1.writeJsonFile)(cachePath, gradleReportJson); } catch (e) { devkit_1.logger.warn(`Failed to write Gradle report cache to ${cachePath}: ${e instanceof Error ? e.message : 'unknown error'}`); } } let gradleReportCache; let gradleReportCachePath = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, 'gradle-report-v1.hash'); function getCurrentGradleReport() { if (!gradleReportCache) { throw new devkit_1.AggregateCreateNodesError([ [ null, new Error(`Expected cached gradle report. Please open an issue at https://github.com/nrwl/nx/issues/new/choose`), ], ], []); } 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> */ async function populateGradleReport(workspaceRoot, gradlewFiles) { const gradleConfigHash = await (0, workspace_context_1.hashWithWorkspaceContext)(workspaceRoot, [ split_config_files_1.gradleConfigAndTestGlob, ]); const cached = readGradleReportCache(gradleReportCachePath, gradleConfigHash); if (cached) { gradleReportCache = cached; return; } const gradleProjectReportStart = performance.mark('gradleProjectReport:start'); const projectReportLines = await gradlewFiles.reduce(async (projectReportLines, gradlewFile) => { const allLines = await projectReportLines; const currentLines = await (0, get_project_report_lines_1.getProjectReportLines)(gradlewFile); return [...allLines, ...currentLines]; }, Promise.resolve([])); const gradleProjectReportEnd = performance.mark('gradleProjectReport:end'); performance.measure('gradleProjectReport', gradleProjectReportStart.name, gradleProjectReportEnd.name); gradleReportCache = processProjectReports(projectReportLines); writeGradleReportToCache(gradleReportCachePath, gradleReportCache, gradleConfigHash); } function processProjectReports(projectReportLines) { /** * Map of Gradle File path to Gradle Project Name */ const gradleFileToGradleProjectMap = new Map(); const gradleProjectToDepsMap = new Map(); /** * Map of Gradle Build File to tasks type map */ const gradleProjectToTasksTypeMap = new Map(); const gradleProjectToTasksMap = new Map(); const gradleProjectToProjectName = new Map(); const gradleProjectNameToProjectRootMap = new Map(); /** * Map fo possible output files of each gradle file * e.g. {build.gradle.kts: { projectReportDir: '' testReportDir: '' }} */ const gradleFileToOutputDirsMap = new Map(); /** * Map of Gradle Project to its child projects */ const gradleProjectToChildProjects = new Map(); let index = 0; while (index < projectReportLines.length) { const line = projectReportLines[index].trim(); if (line.startsWith('> Task ')) { if (line.endsWith(':dependencyReport')) { const gradleProject = line.substring('> Task '.length, line.length - ':dependencyReport'.length); while (index < projectReportLines.length && !projectReportLines[index].includes(exec_gradle_1.fileSeparator)) { index++; } const [_, file] = projectReportLines[index].split(exec_gradle_1.fileSeparator); gradleProjectToDepsMap.set(gradleProject, processGradleDependencies(file)); } if (line.endsWith('propertyReport')) { const gradleProject = line.substring('> Task '.length, line.length - ':propertyReport'.length); while (index < projectReportLines.length && !projectReportLines[index].includes(exec_gradle_1.fileSeparator)) { index++; } const [_, file] = projectReportLines[index].split(exec_gradle_1.fileSeparator); const propertyReportLines = (0, node_fs_1.existsSync)(file) ? (0, node_fs_1.readFileSync)(file).toString().split(exec_gradle_1.newLineSeparator) : []; let projectName, absBuildFilePath, absBuildDirPath; const outputDirMap = new Map(); const tasks = new Set(); 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.startsWith('childProjects: ')) { const childProjects = line.substring('childProjects: {'.length, line.length - 1); // remove curly braces {} around childProjects gradleProjectToChildProjects.set(gradleProject, childProjects .split(',') .map((c) => c.trim().split('=')[0]) .filter(Boolean) // e.g. get project name from text like "app=project ':app', mylibrary=project ':mylibrary'" ); } if (line.includes('Dir: ')) { const [dirName, dirPath] = line.split(': '); const taskName = dirName.replace('Dir', ''); outputDirMap.set(taskName, `{workspaceRoot}/${(0, node_path_1.relative)(devkit_1.workspaceRoot, dirPath)}`); } if (line.includes(': task ')) { const [task] = line.split(': task '); tasks.add(task); } } if (!projectName || !absBuildFilePath || !absBuildDirPath) { continue; } const buildFile = (0, devkit_1.normalizePath)((0, node_path_1.relative)(devkit_1.workspaceRoot, absBuildFilePath)); const buildDir = (0, node_path_1.relative)(devkit_1.workspaceRoot, absBuildDirPath); outputDirMap.set('build', `{workspaceRoot}/${buildDir}`); outputDirMap.set('classes', `{workspaceRoot}/${(0, node_path_1.join)(buildDir, 'classes')}`); gradleFileToOutputDirsMap.set(buildFile, outputDirMap); gradleFileToGradleProjectMap.set(buildFile, gradleProject); gradleProjectToProjectName.set(gradleProject, projectName); gradleProjectNameToProjectRootMap.set(gradleProject, (0, path_1.dirname)(buildFile)); gradleProjectToTasksMap.set(gradleProject, tasks); } if (line.endsWith('taskReport')) { const gradleProject = line.substring('> Task '.length, line.length - ':taskReport'.length); while (index < projectReportLines.length && !projectReportLines[index].includes(exec_gradle_1.fileSeparator)) { index++; } const [_, file] = projectReportLines[index].split(exec_gradle_1.fileSeparator); const taskTypeMap = new Map(); const tasksFileLines = (0, node_fs_1.existsSync)(file) ? (0, node_fs_1.readFileSync)(file).toString().split(exec_gradle_1.newLineSeparator) : []; 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] !== '' && i < tasksFileLines.length && tasksFileLines[i]?.includes(' - ')) { const [taskName] = tasksFileLines[i].split(' - '); taskTypeMap.set(taskName, type); } } } i++; } gradleProjectToTasksTypeMap.set(gradleProject, taskTypeMap); } } index++; } return { gradleFileToGradleProjectMap, gradleFileToOutputDirsMap, gradleProjectToTasksTypeMap, gradleProjectToDepsMap, gradleProjectToTasksMap, gradleProjectToProjectName, gradleProjectNameToProjectRootMap, gradleProjectToChildProjects, }; } function processGradleDependencies(depsFile) { const dependedProjects = new Set(); const lines = (0, node_fs_1.readFileSync)(depsFile).toString().split(exec_gradle_1.newLineSeparator); let inDeps = false; for (const line of lines) { if (line.startsWith('implementationDependenciesMetadata') || line.startsWith('compileClasspath')) { inDeps = true; continue; } if (inDeps) { if (line === '') { inDeps = false; continue; } const [indents, dep] = line.split('--- '); if (indents === '\\' || indents === '+') { let targetProjectName; if (dep.startsWith('project ')) { targetProjectName = dep .substring('project '.length) .replace(/ \(n\)$/, '') .trim() .split(' ')?.[0]; } else if (dep.includes('-> project')) { const [_, projectName] = dep.split('-> project'); targetProjectName = projectName.trim().split(' ')?.[0]; } if (targetProjectName) { dependedProjects.add(targetProjectName); } } } } return dependedProjects; }