@nx/gradle
Version:
281 lines (280 loc) • 14.3 kB
JavaScript
;
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;
}