Skip to content

Commit d72ea1f

Browse files
bhaveshbhatiBhavesh BhatiCopilot
authored
Adding build scripts for building the workspace packages using pnpm (#21326)
* Scripts for building the workspaces * Update build to run parallel * Temp changes to validate on Mac and Linux * Put the tasks in quotes * Revert the changes for build * Minor, fix the package.json * Correct package and package-lock * Code review comments * Code review comments * Update build-scripts/download-utils.js Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Bhavesh Bhati <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 8745524 commit d72ea1f

File tree

10 files changed

+518
-4
lines changed

10 files changed

+518
-4
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,5 @@ _generated_local/
227227
# Auto-cloned dependencies during build
228228
task-lib/
229229
tasks-common/
230+
231+
pnpm-exec-summary.json

Tasks/NotationV0/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "Azure Pipepine Task for setting up Notation CLI, sign and verify with Notation",
55
"main": "src/index.js",
66
"scripts": {
7-
"build": "tsc -b",
7+
"build": "node ../../make.js build --task NotationV0",
88
"serverBuild": "node ../../make.js serverBuild --task NotationV0"
99
},
1010
"keywords": [],

build-parallel.js

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
#!/usr/bin/env node
2+
3+
const { spawn } = require('child_process');
4+
const path = require('path');
5+
const minimist = require('minimist');
6+
const fs = require('fs');
7+
8+
/**
9+
* Convert task glob pattern to pnpm workspace filters using path-based approach
10+
* @param {string} taskPattern - Task pattern (e.g., "@(TaskA|TaskB|TaskC)" or "TaskName")
11+
* @returns {string[]} - Array of --filter arguments for pnpm
12+
*/
13+
function convertTaskPatternToWorkspaceFilters(taskPattern) {
14+
if (!taskPattern) {
15+
return [];
16+
}
17+
18+
// Handle @(task1|task2|task3) pattern - convert to single filter with path pattern
19+
if (taskPattern.startsWith('@(') && taskPattern.endsWith(')')) {
20+
const tasksString = taskPattern.slice(2, -1); // Remove @( and )
21+
const tasks = tasksString.split('|').map(task => task.trim());
22+
23+
if (tasks.length === 0) {
24+
return [];
25+
} else if (tasks.length === 1) {
26+
return ['--filter', `"./Tasks/${tasks[0]}"`];
27+
} else {
28+
// Create a single filter pattern that matches multiple task paths
29+
const pathPattern = `./Tasks/{${tasks.join(',')}}`;
30+
return ['--filter', `"${pathPattern}"`];
31+
}
32+
}
33+
34+
// Handle single task or other patterns - use path-based filter
35+
return ['--filter', `"./Tasks/${taskPattern}"`];
36+
}
37+
38+
/**
39+
* Convert parsed arguments to workspace filter arguments for pnpm
40+
* @param {object} argv - Parsed minimist arguments
41+
* @returns {string[]} - Converted arguments for pnpm
42+
*/
43+
function convertArgsToWorkspaceArgs(argv) {
44+
const convertedArgs = [];
45+
46+
// Handle task parameter
47+
if (argv.task) {
48+
const taskFilters = convertTaskPatternToWorkspaceFilters(argv.task);
49+
convertedArgs.push(...taskFilters);
50+
}
51+
52+
// Handle other arguments (excluding task and the command)
53+
Object.keys(argv).forEach(key => {
54+
if (key !== 'task' && key !== '_') {
55+
const value = argv[key];
56+
if (typeof value === 'boolean' && value) {
57+
convertedArgs.push(`--${key}`);
58+
} else if (typeof value !== 'boolean') {
59+
convertedArgs.push(`--${key}=${value}`);
60+
}
61+
}
62+
});
63+
64+
return convertedArgs;
65+
}
66+
67+
/**
68+
* Executes a command with arguments and returns a promise
69+
* @param {string} command - The command to execute
70+
* @param {string[]} args - Array of arguments
71+
* @param {object} options - Spawn options
72+
* @returns {Promise<number>} - Exit code
73+
*/
74+
function executeCommand(command, args, options = {}) {
75+
return new Promise((resolve, reject) => {
76+
// For shell commands, we need to properly quote arguments that contain special characters
77+
const quotedArgs = args.map(arg => {
78+
// Quote arguments that contain pipes, parentheses, or spaces
79+
if (arg.includes('|') || arg.includes('(') || arg.includes(')') || arg.includes(' ')) {
80+
return `"${arg}"`;
81+
}
82+
return arg;
83+
});
84+
85+
const child = spawn(command, quotedArgs, {
86+
stdio: 'inherit',
87+
shell: true,
88+
...options
89+
});
90+
91+
child.on('close', (code) => {
92+
resolve(code);
93+
});
94+
95+
child.on('error', (error) => {
96+
reject(error);
97+
});
98+
});
99+
}
100+
101+
/**
102+
* Run parallel build command
103+
* @param {string[]} additionalArgs - Additional arguments to pass to the commands
104+
*/
105+
async function runBuild(additionalArgs = []) {
106+
// Parse arguments using minimist (same as make.js)
107+
const argv = minimist(additionalArgs);
108+
109+
failIfTasksMissing(argv);
110+
111+
// First run pre-build steps
112+
console.log('Running pre-build steps...');
113+
try {
114+
const preBuildArgs = ['make.js', 'build', '--onlyPreBuildSteps', '--enableConcurrentTaskBuild', ...additionalArgs];
115+
const preBuildExitCode = await executeCommand('node', preBuildArgs);
116+
if (preBuildExitCode !== 0) {
117+
console.error('Pre-build steps failed');
118+
process.exit(preBuildExitCode);
119+
}
120+
} catch (error) {
121+
console.error('Error running pre-build steps:', error.message);
122+
process.exit(1);
123+
}
124+
125+
// Then run parallel build
126+
const pnpmPath = path.join(__dirname, 'node_modules', '.bin', 'pnpm');
127+
const workspaceArgs = convertArgsToWorkspaceArgs(argv);
128+
const args = [
129+
'-r',
130+
'--report-summary',
131+
'--aggregate-output',
132+
'--reporter=append-only',
133+
'--workspace-concurrency=2',
134+
...workspaceArgs, // Move workspace filters before 'run'
135+
'run',
136+
'build',
137+
'--skipPrebuildSteps',
138+
'--enableConcurrentTaskBuild'
139+
];
140+
141+
console.log('Running parallel build...');
142+
console.log('pnpm command:', 'pnpm', args.join(' '));
143+
try {
144+
const exitCode = await executeCommand(pnpmPath, args);
145+
printBuildSummary();
146+
process.exit(exitCode);
147+
} catch (error) {
148+
console.error('Error running build:', error.message);
149+
process.exit(1);
150+
}
151+
}
152+
153+
/**
154+
* Run parallel server build command
155+
* @param {string[]} additionalArgs - Additional arguments to pass to the commands
156+
*/
157+
async function runServerBuild(additionalArgs = []) {
158+
// Parse arguments using minimist (same as make.js)
159+
const argv = minimist(additionalArgs);
160+
failIfTasksMissing(argv);
161+
162+
// First run pre-build steps
163+
console.log('Running pre-build steps for server build...');
164+
try {
165+
const preBuildArgs = ['make.js', 'build', '--onlyPreBuildSteps', '--enableConcurrentTaskBuild', ...additionalArgs];
166+
const preBuildExitCode = await executeCommand('node', preBuildArgs);
167+
if (preBuildExitCode !== 0) {
168+
console.error('Pre-build steps failed for server build');
169+
process.exit(preBuildExitCode);
170+
}
171+
} catch (error) {
172+
console.error('Error running pre-build steps for server build:', error.message);
173+
process.exit(1);
174+
}
175+
176+
// Then run parallel server build
177+
const pnpmPath = path.join(__dirname, 'node_modules', '.bin', 'pnpm');
178+
const workspaceArgs = convertArgsToWorkspaceArgs(argv);
179+
const args = [
180+
'-r',
181+
'--report-summary',
182+
'--aggregate-output',
183+
'--reporter=append-only',
184+
'--workspace-concurrency=2',
185+
...workspaceArgs, // Move workspace filters before 'run'
186+
'run',
187+
'serverBuild',
188+
'--skipPrebuildSteps',
189+
'--enableConcurrentTaskBuild'
190+
];
191+
192+
console.log('Running parallel server build...');
193+
console.log('pnpm command:', 'pnpm', args.join(' '));
194+
try {
195+
const exitCode = await executeCommand(pnpmPath, args);
196+
printBuildSummary();
197+
process.exit(exitCode);
198+
} catch (error) {
199+
console.error('Error running server build:', error.message);
200+
process.exit(1);
201+
}
202+
}
203+
204+
// Parse command line arguments
205+
const command = process.argv[2];
206+
const additionalArgs = process.argv.slice(3); // Capture all arguments after the command
207+
208+
switch (command) {
209+
case 'build':
210+
runBuild(additionalArgs);
211+
break;
212+
case 'serverBuild':
213+
runServerBuild(additionalArgs);
214+
break;
215+
default:
216+
console.log('Usage: node build-parallel.js [build|serverBuild] [additional arguments...]');
217+
console.log('Commands:');
218+
console.log(' build - Run parallel build');
219+
console.log(' serverBuild - Run parallel server build');
220+
console.log('');
221+
console.log('Examples:');
222+
console.log(' node build-parallel.js build --task "MyTask"');
223+
console.log(' node build-parallel.js serverBuild --task "@(TaskA|TaskB|TaskC)"');
224+
console.log('');
225+
console.log('Note: Task patterns are converted to path-based --filter arguments for pnpm workspace filtering');
226+
console.log(' Multiple tasks use a single filter pattern:');
227+
console.log(' - Glob pattern: --task "@(Task1|Task2|Task3)" → --filter "./Tasks/{Task1,Task2,Task3}"');
228+
console.log(' - Single task: --task "Task1" → --filter "./Tasks/Task1"');
229+
process.exit(1);
230+
}
231+
232+
// Print build summary
233+
const printBuildSummary = () => {
234+
const summaryPath = path.join(__dirname, 'pnpm-exec-summary.json');
235+
if (!fs.existsSync(summaryPath)) {
236+
console.log('No build summary found.');
237+
return;
238+
}
239+
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
240+
const execStatus = summary.executionStatus || {};
241+
242+
let total = 0, success = 0, failed = 0, skipped = 0, running = 0;
243+
for (const task in execStatus) {
244+
total++;
245+
const status = execStatus[task].status;
246+
if (status === 'passed') success++;
247+
else if (status === 'failure') failed++;
248+
else if (status === 'running') running++;
249+
else skipped++;
250+
}
251+
252+
console.log('');
253+
console.log('📊 BUILD SUMMARY');
254+
console.log('================================================================================');
255+
console.log(` Total tasks built: ${total}`);
256+
console.log(` ✅ Successful: ${success}`);
257+
console.log(` ❌ Failed: ${failed}`);
258+
console.log(` ⏭️ Skipped: ${skipped}`);
259+
console.log(` 🟡 Running: ${running}`);
260+
console.log('===================================');
261+
};
262+
263+
/**
264+
* Checks if all requested tasks exist in ./Tasks directory. Exits with error if any are missing.
265+
*/
266+
function failIfTasksMissing(argv) {
267+
const argvTask = argv.task;
268+
let requestedTasks = [];
269+
if (argvTask) {
270+
if (argvTask.startsWith('@(') && argvTask.endsWith(')')) {
271+
requestedTasks = argvTask.slice(2, -1).split('|').map(t => t.trim());
272+
} else {
273+
requestedTasks = [argvTask];
274+
}
275+
}
276+
if (requestedTasks.length) {
277+
// Check existence in ./Tasks directory
278+
const tasksDir = path.join(__dirname, 'Tasks');
279+
let missingTasks = [];
280+
for (const t of requestedTasks) {
281+
const taskPath = path.join(tasksDir, t);
282+
if (!fs.existsSync(taskPath)) {
283+
missingTasks.push(t);
284+
}
285+
}
286+
if (missingTasks.length) {
287+
console.error(`Error: The following tasks do not exist: ${missingTasks.join(', ')}`);
288+
process.exit(2);
289+
}
290+
}
291+
}

0 commit comments

Comments
 (0)