@nx/gradle
Version:
178 lines (177 loc) • 7.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.batchRunnerPath = void 0;
exports.default = gradleBatch;
exports.getGradlewTasksToRun = getGradlewTasksToRun;
const devkit_1 = require("@nx/devkit");
const exec_gradle_1 = require("../../utils/exec-gradle");
const path_1 = require("path");
const child_process_1 = require("child_process");
const readline_1 = require("readline");
const get_exclude_task_1 = require("./get-exclude-task");
exports.batchRunnerPath = (0, path_1.join)(__dirname, '../../../batch-runner/build/libs/gradle-batch-runner-all.jar');
async function* gradleBatch(taskGraph, inputs, overrides, context) {
const projectName = taskGraph.tasks[taskGraph.roots[0]]?.target?.project;
const projectRoot = context.projectGraph.nodes[projectName]?.data?.root ?? '';
const customGradleExecutableDirectory = (0, exec_gradle_1.getCustomGradleExecutableDirectoryFromPlugin)(context.nxJsonConfiguration);
let gradlewPath = (0, exec_gradle_1.findGradlewFile)((0, path_1.join)(projectRoot, 'project.json'), devkit_1.workspaceRoot, customGradleExecutableDirectory);
gradlewPath = (0, path_1.join)(context.root, gradlewPath);
const root = (0, path_1.dirname)(gradlewPath);
// set args with passed in args and overrides in command line
const input = inputs[taskGraph.roots[0]];
const args = typeof input.args === 'string'
? input.args.trim().split(' ')
: Array.isArray(input.args)
? input.args
: [];
if (overrides.__overrides_unparsed__.length) {
args.push(...overrides.__overrides_unparsed__);
}
const taskIds = Object.keys(taskGraph.tasks);
const { gradlewTasksToRun, excludeTasks, excludeTestTasks } = getGradlewTasksToRun(taskIds, taskGraph, inputs, context.projectGraph.nodes, context.taskGraph ?? taskGraph);
const yielded = new Set();
try {
for await (const event of streamTasksInBatch(gradlewTasksToRun, excludeTasks, excludeTestTasks, args, root)) {
yielded.add(event.task);
yield event;
}
}
catch (e) {
devkit_1.output.error({
title: `Gradlew batch failed`,
bodyLines: [e.toString()],
});
for (const taskId of taskIds) {
if (!yielded.has(taskId)) {
yielded.add(taskId);
yield {
task: taskId,
result: { success: false, terminalOutput: e.toString() },
};
}
}
}
}
function getGradlewTasksToRun(taskIds, taskGraph, inputs, nodes, fullTaskGraph = taskGraph) {
const tasksWithExcludeIds = new Set();
const testTasksWithExcludeIds = new Set();
const tasksWithoutExcludeIds = new Set();
const gradlewTasksToRun = {};
const includeDependsOnTasks = new Set();
for (const taskId of taskIds) {
const input = inputs[taskId];
gradlewTasksToRun[taskId] = input;
if (input.includeDependsOnTasks) {
for (const t of input.includeDependsOnTasks) {
includeDependsOnTasks.add(t);
}
}
if (input.excludeDependsOn) {
if (input.testClassName) {
testTasksWithExcludeIds.add(taskId);
}
else {
tasksWithExcludeIds.add(taskId);
}
}
else {
tasksWithoutExcludeIds.add(taskId);
}
}
const runningTaskIds = new Set(taskIds);
for (const depId of (0, get_exclude_task_1.getAllDependsOnFromTaskGraph)(tasksWithoutExcludeIds, fullTaskGraph)) {
runningTaskIds.add(depId);
}
const excludeTasks = (0, get_exclude_task_1.getExcludeTasksFromTaskGraph)(tasksWithExcludeIds, runningTaskIds, fullTaskGraph, nodes, includeDependsOnTasks);
const excludeTestTasks = (0, get_exclude_task_1.getExcludeTasksFromTaskGraph)(testTasksWithExcludeIds, new Set(), fullTaskGraph, nodes);
return {
gradlewTasksToRun,
excludeTasks,
excludeTestTasks,
};
}
async function* streamTasksInBatch(gradlewTasksToRun, excludeTasks, excludeTestTasks, args, root) {
const gradlewBatchStart = performance.mark(`gradlew-batch:start`);
const debugOptions = (process.env.NX_GRADLE_BATCH_DEBUG ?? '').trim();
const spawnArgs = [
...(debugOptions ? debugOptions.split(/\s+/) : []),
'-jar',
exports.batchRunnerPath,
`--tasks=${JSON.stringify(gradlewTasksToRun)}`,
`--workspaceRoot=${root}`,
`--args=${args.join(' ').replaceAll("'", '"')}`,
`--excludeTasks=${Array.from(excludeTasks).join(',')}`,
`--excludeTestTasks=${Array.from(excludeTestTasks).join(',')}`,
...(process.env.NX_VERBOSE_LOGGING === 'true' ? [] : ['--quiet']),
];
// stderr is inherited so Gradle output (tee'd to System.err by TeeOutputStream)
// and logger output flow to the terminal in real-time.
// stdout is piped so we can read NX_RESULT lines emitted per task.
const cp = (0, child_process_1.spawn)('java', spawnArgs, {
cwd: devkit_1.workspaceRoot,
windowsHide: true,
env: process.env,
stdio: ['pipe', 'pipe', 'inherit'],
});
const exit = new Promise((resolve, reject) => {
cp.on('error', reject);
cp.on('close', (code) => resolve(code ?? 0));
});
const rl = (0, readline_1.createInterface)({ input: cp.stdout, crlfDelay: Infinity });
// Drain stdout into a queue eagerly. Yielding inside the readline loop creates
// back-pressure: if the consumer is slow, `yield` blocks, readline pauses, the
// OS pipe between Java and Node fills, and Java's NX_RESULT println blocks on
// a full pipe — the JVM hangs even though all task work is done.
const queue = [];
let notifyAvailable = null;
let readerClosed = false;
rl.on('line', (line) => {
if (!line.startsWith('NX_RESULT:'))
return;
try {
const data = JSON.parse(line.slice('NX_RESULT:'.length));
queue.push({
task: data.task,
result: {
success: data.result.success ?? false,
status: data.result.status,
terminalOutput: data.result.terminalOutput ?? '',
startTime: data.result.startTime,
endTime: data.result.endTime,
},
});
}
catch (e) {
console.error('[Gradle Batch] Failed to parse result line:', line, e);
return;
}
notifyAvailable?.();
notifyAvailable = null;
});
rl.on('close', () => {
readerClosed = true;
notifyAvailable?.();
notifyAvailable = null;
});
try {
while (!readerClosed || queue.length > 0) {
if (queue.length > 0) {
yield queue.shift();
}
else {
await new Promise((resolve) => {
notifyAvailable = resolve;
});
}
}
}
finally {
rl.close();
}
const code = await exit;
const gradlewBatchEnd = performance.mark(`gradlew-batch:end`);
performance.measure(`gradlew-batch`, gradlewBatchStart.name, gradlewBatchEnd.name);
if (code !== 0) {
throw new Error(`Gradle batch runner exited with code ${code}`);
}
}