Skip to content

Commit c5bcdf7

Browse files
authored
Add nuget restore to yarn build (#15376)
* Restore Nuget packages on yarn build * Change files
1 parent fef2c30 commit c5bcdf7

File tree

5 files changed

+270
-10
lines changed

5 files changed

+270
-10
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Restore Nuget packages on yarn build",
4+
"packageName": "@rnw-scripts/just-task",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Restore Nuget packages on yarn build",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*
5+
* @format
6+
*/
7+
8+
const fs = require('fs');
9+
const path = require('path');
10+
const {execSync, spawnSync} = require('child_process');
11+
const {task} = require('just-scripts');
12+
13+
function registerNuGetRestoreTask(options) {
14+
const config = normalizeOptions(options);
15+
task(config.taskName, () => executeNuGetRestore(config));
16+
}
17+
18+
function runNuGetRestore(options) {
19+
const config = normalizeOptions(options);
20+
executeNuGetRestore(config);
21+
}
22+
23+
function executeNuGetRestore(config) {
24+
if (process.platform !== 'win32') {
25+
console.log('Skipping NuGet restore on non-Windows host');
26+
return;
27+
}
28+
29+
if (!fs.existsSync(config.scriptPath)) {
30+
console.warn(`NuGet restore script not found: ${config.scriptPath}`);
31+
return;
32+
}
33+
34+
const vsDevCmd = findVsDevCmd();
35+
36+
fs.mkdirSync(config.logDirectory, {recursive: true});
37+
pruneRestoreLogs(config.logDirectory, config.maxLogCount);
38+
39+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
40+
const logPath = path.join(
41+
config.logDirectory,
42+
`NuGetRestore-${timestamp}.log`,
43+
);
44+
45+
console.log(
46+
`Restoring NuGet packages (log: ${path.relative(process.cwd(), logPath)})`,
47+
);
48+
49+
const scriptArgs = config.scriptArguments.length
50+
? ` ${config.scriptArguments.join(' ')}`
51+
: '';
52+
const restoreCommand = `call ${quote(
53+
vsDevCmd,
54+
)} && powershell -NoProfile -ExecutionPolicy Bypass -File ${quote(
55+
config.scriptPath,
56+
)}${scriptArgs}`;
57+
const wrappedCommand = `${restoreCommand}`;
58+
59+
writeLogHeader(
60+
logPath,
61+
restoreCommand,
62+
config.workingDirectory,
63+
config.scriptArguments,
64+
);
65+
66+
const result = spawnSync('cmd.exe', ['/d', '/s', '/c', wrappedCommand], {
67+
encoding: 'utf8',
68+
windowsHide: true,
69+
cwd: config.workingDirectory,
70+
windowsVerbatimArguments: true,
71+
});
72+
73+
appendProcessOutput(logPath, result);
74+
75+
if (result.error) {
76+
throw new Error(`Failed to start NuGet restore: ${result.error.message}`);
77+
}
78+
79+
if (result.status !== 0) {
80+
const tail = readLogTail(logPath);
81+
throw new Error(
82+
`NuGet restore failed. See ${path.relative(
83+
process.cwd(),
84+
logPath,
85+
)} for details.\n${tail}`,
86+
);
87+
}
88+
89+
console.log('NuGet restore completed');
90+
}
91+
92+
function findVsDevCmd() {
93+
const programFilesX86 =
94+
process.env['ProgramFiles(x86)'] || process.env.ProgramFiles;
95+
96+
if (!programFilesX86) {
97+
throw new Error('Program Files directory not found in environment');
98+
}
99+
100+
const vsWherePath = path.join(
101+
programFilesX86,
102+
'Microsoft Visual Studio',
103+
'Installer',
104+
'vswhere.exe',
105+
);
106+
107+
if (!fs.existsSync(vsWherePath)) {
108+
throw new Error(
109+
'vswhere.exe not found. Install Visual Studio 2022 (or Build Tools).',
110+
);
111+
}
112+
113+
let installationRoot = '';
114+
try {
115+
installationRoot = execSync(
116+
`${quote(vsWherePath)} -latest -products * -requires Microsoft.Component.MSBuild -property installationPath -format value`,
117+
{encoding: 'utf8'},
118+
)
119+
.split(/\r?\n/)
120+
.find(Boolean);
121+
} catch (error) {
122+
throw new Error(`vswhere.exe failed: ${error.message}`);
123+
}
124+
125+
if (!installationRoot) {
126+
throw new Error('No Visual Studio installation with MSBuild found');
127+
}
128+
129+
const vsDevCmd = path.join(
130+
installationRoot.trim(),
131+
'Common7',
132+
'Tools',
133+
'VsDevCmd.bat',
134+
);
135+
136+
if (!fs.existsSync(vsDevCmd)) {
137+
throw new Error(`VsDevCmd.bat not found at ${vsDevCmd}`);
138+
}
139+
140+
return vsDevCmd;
141+
}
142+
143+
function pruneRestoreLogs(logDir, maxLogCount) {
144+
const files = fs
145+
.readdirSync(logDir)
146+
.filter(file => file.startsWith('NuGetRestore-') && file.endsWith('.log'))
147+
.sort();
148+
149+
while (files.length > maxLogCount) {
150+
const oldest = files.shift();
151+
if (oldest) {
152+
fs.rmSync(path.join(logDir, oldest), {force: true});
153+
}
154+
}
155+
}
156+
157+
function writeLogHeader(logPath, command, cwd, scriptArguments) {
158+
const header = [
159+
`Command line : ${command}`,
160+
`Working dir : ${cwd}`,
161+
`Script args : ${
162+
scriptArguments && scriptArguments.length
163+
? scriptArguments.join(' ')
164+
: '(none)'
165+
}`,
166+
`Timestamp : ${new Date().toISOString()}`,
167+
'',
168+
].join('\n');
169+
170+
fs.writeFileSync(logPath, `${header}\n`, {encoding: 'utf8'});
171+
}
172+
173+
function appendProcessOutput(logPath, result) {
174+
let output = '';
175+
176+
if (result.stdout) {
177+
output += result.stdout;
178+
}
179+
if (result.stderr) {
180+
output += result.stderr;
181+
}
182+
183+
if (!output) {
184+
return;
185+
}
186+
187+
fs.appendFileSync(logPath, output, {encoding: 'utf8'});
188+
}
189+
190+
function readLogTail(logPath, lineCount = 40) {
191+
try {
192+
const contents = fs.readFileSync(logPath, 'utf8');
193+
const lines = contents.trim().split(/\r?\n/);
194+
return lines.slice(-lineCount).join('\n');
195+
} catch (error) {
196+
return `Could not read log tail: ${error.message}`;
197+
}
198+
}
199+
200+
function normalizeOptions(options = {}) {
201+
if (!options.scriptPath) {
202+
throw new Error('scriptPath is required for NuGet restore task');
203+
}
204+
205+
const resolvedScriptPath = path.resolve(options.scriptPath);
206+
const resolvedLogDir = options.logDirectory
207+
? path.resolve(options.logDirectory)
208+
: path.join(path.dirname(resolvedScriptPath), 'logs');
209+
210+
return {
211+
taskName: options.taskName || 'restoreNuGetPackages',
212+
scriptPath: resolvedScriptPath,
213+
logDirectory: resolvedLogDir,
214+
workingDirectory: options.workingDirectory
215+
? path.resolve(options.workingDirectory)
216+
: path.dirname(resolvedScriptPath),
217+
maxLogCount: options.maxLogCount ?? 5,
218+
scriptArguments: Array.isArray(options.scriptArguments)
219+
? options.scriptArguments
220+
: [],
221+
};
222+
}
223+
224+
function quote(value) {
225+
return `"${value}"`;
226+
}
227+
228+
module.exports = {
229+
registerNuGetRestoreTask,
230+
runNuGetRestore,
231+
};
Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
param(
2+
[switch] $SkipLockDeletion
23
)
34

45
[string] $RepoRoot = Resolve-Path "$PSScriptRoot\..\.."
56

67
$StartingLocation = Get-Location
78
Set-Location -Path $RepoRoot
89

9-
try
10-
{
11-
# Delete existing lock files
12-
$existingLockFiles = (Get-ChildItem -File -Recurse -Path $RepoRoot -Filter *.lock.json)
13-
$existingLockFiles | Foreach-Object {
14-
Write-Host Deleting $_.FullName
15-
Remove-Item $_.FullName
10+
try {
11+
if (-not $SkipLockDeletion) {
12+
# Delete existing lock files
13+
$existingLockFiles = (Get-ChildItem -File -Recurse -Path $RepoRoot -Filter *.lock.json)
14+
$existingLockFiles | Foreach-Object {
15+
Write-Host Deleting $_.FullName
16+
Remove-Item $_.FullName
17+
}
1618
}
1719

18-
$packagesSolutions = (Get-ChildItem -File -Recurse -Path $RepoRoot\packages -Filter *.sln )| Where-Object { !$_.FullName.Contains('node_modules') -and !$_.FullName.Contains('e2etest') }
20+
$packagesSolutions = (Get-ChildItem -File -Recurse -Path $RepoRoot\packages -Filter *.sln ) | Where-Object { !$_.FullName.Contains('node_modules') -and !$_.FullName.Contains('e2etest') }
1921
$vnextSolutions = (Get-ChildItem -File -Path $RepoRoot\vnext -Filter *.sln)
2022

2123
# Run all solutions with their defaults
@@ -31,7 +33,6 @@ try
3133
& msbuild /t:Restore /p:RestoreForceEvaluate=true /p:UseExperimentalWinUI3=true $_.FullName
3234
}
3335
}
34-
finally
35-
{
36+
finally {
3637
Set-Location -Path "$StartingLocation"
3738
}

vnext/just-task.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ require('@rnw-scripts/just-task/flow-tasks');
2222

2323
const {execSync} = require('child_process');
2424
const fs = require('fs');
25+
const {
26+
registerNuGetRestoreTask,
27+
} = require('@rnw-scripts/just-task/nuget-restore-task');
2528

2629
option('production');
2730
option('clean');
@@ -71,6 +74,16 @@ task('copyReadmeAndLicenseFromRoot', () => {
7174

7275
task('compileTsPlatformOverrides', tscTask());
7376

77+
registerNuGetRestoreTask({
78+
taskName: 'restoreNuGetPackages',
79+
scriptPath: path.resolve(
80+
__dirname,
81+
'Scripts/NuGetRestoreForceEvaluateAllSolutions.ps1',
82+
),
83+
logDirectory: path.resolve(__dirname, 'logs'),
84+
scriptArguments: ['-SkipLockDeletion'],
85+
});
86+
7487
task(
7588
'build',
7689
series(
@@ -79,6 +92,7 @@ task(
7992
'copyReadmeAndLicenseFromRoot',
8093
'layoutMSRNCxx',
8194
'compileTsPlatformOverrides',
95+
'restoreNuGetPackages',
8296
'codegen',
8397
),
8498
);

0 commit comments

Comments
 (0)