@nx/dotnet 23.2.0-pr.36527.2252709 → 23.2.0-pr.36838.ef8e258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/analyzer/analyzer-client.d.ts +6 -24
- package/dist/analyzer/analyzer-client.d.ts.map +1 -1
- package/dist/analyzer/analyzer-client.js +141 -112
- package/dist/lib/MsbuildAnalyzer.deps.json +13 -55
- package/dist/lib/MsbuildAnalyzer.dll +0 -0
- package/dist/lib/MsbuildAnalyzer.pdb +0 -0
- package/dist/plugins/create-nodes.d.ts +3 -49
- package/dist/plugins/create-nodes.d.ts.map +1 -1
- package/dist/plugins/create-nodes.js +10 -24
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
<div style="text-align: center;">
|
|
9
9
|
|
|
10
|
-
[]()
|
|
10
|
+
[](<>)
|
|
11
11
|
[](https://www.npmjs.com/package/nx)
|
|
12
|
-
[]()
|
|
12
|
+
[](<>)
|
|
13
13
|
[](http://commitizen.github.io/cz-cli/)
|
|
14
14
|
[](https://go.nx.dev/community)
|
|
15
15
|
[](https://nx.dev/docs/features/ci-features/sandboxing)
|
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
import { ProjectConfiguration } from '@nx/devkit';
|
|
2
|
+
export declare const ANALYZER_CANCELLED_MESSAGE = "Analyzer run was cancelled";
|
|
3
|
+
/**
|
|
4
|
+
* Cancel any in-flight analyzer process. Safe to call even if nothing is running.
|
|
5
|
+
*/
|
|
6
|
+
export declare function cancelPendingAnalysis(): void;
|
|
7
|
+
export declare function getAnalysisTimeoutMs(): number;
|
|
2
8
|
export interface AnalysisSuccessResult {
|
|
3
9
|
nodesByFile: Record<string, ProjectConfiguration>;
|
|
4
10
|
referencesByRoot: Record<string, {
|
|
5
11
|
refs: string[];
|
|
6
12
|
sourceConfigFile: string;
|
|
7
13
|
}>;
|
|
8
|
-
/**
|
|
9
|
-
* Project roots whose targets were derived from C# sources rather than from
|
|
10
|
-
* project files alone — i.e. projects that produced atomized test targets.
|
|
11
|
-
*
|
|
12
|
-
* Absent (not empty) when nothing atomized, both so that output from an older
|
|
13
|
-
* analyzer still parses and so the common case is trivially detectable.
|
|
14
|
-
*/
|
|
15
|
-
atomizedRoots?: string[];
|
|
16
|
-
/**
|
|
17
|
-
* Workspace-relative paths of sources that fed discovery but sit outside their
|
|
18
|
-
* project's directory — linked or shared files.
|
|
19
|
-
*
|
|
20
|
-
* `atomizedRoots` is hashed as a per-root glob, which by construction cannot
|
|
21
|
-
* reach these, so they are hashed alongside it.
|
|
22
|
-
*/
|
|
23
|
-
atomizedExternalSources?: string[];
|
|
24
14
|
}
|
|
25
15
|
export interface AnalysisErrorResult {
|
|
26
16
|
error: Error;
|
|
@@ -37,14 +27,6 @@ export interface DotNetAnalyzerOptions {
|
|
|
37
27
|
restoreTargetName?: string;
|
|
38
28
|
publishTargetName?: string;
|
|
39
29
|
packTargetName?: string;
|
|
40
|
-
/**
|
|
41
|
-
* Name of the atomized parent target. Undefined disables test splitting
|
|
42
|
-
* entirely, which is the default.
|
|
43
|
-
*/
|
|
44
|
-
testCiTargetName?: string;
|
|
45
|
-
/** Pre-derived group name; the analyzer does not compute one. */
|
|
46
|
-
testCiGroupName?: string;
|
|
47
|
-
testCiSplitBy?: 'class' | 'method';
|
|
48
30
|
}
|
|
49
31
|
/**
|
|
50
32
|
* Get project analysis results for the given project files.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer-client.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyzer-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyzer-client.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyzer-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAmBpB,eAAO,MAAM,0BAA0B,+BAA+B,CAAC;AAIvE;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAS7C;AAED,MAAM,WAAW,qBAAqB;IAEpC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAElD,gBAAgB,EAAE,MAAM,CACtB,MAAM,EACN;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAC7C,CAAC;CACH;AACD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,KAAK,CAAC;CACd;AACD,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;AAEzE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA4MD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,cAAc,CAAC,CA+DzB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,cAAc,CASzD;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,GACrB,MAAM,IAAI,mBAAmB,CAE/B;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,GACrB,MAAM,IAAI,qBAAqB,CAEjC"}
|
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ANALYZER_CANCELLED_MESSAGE = void 0;
|
|
4
|
+
exports.cancelPendingAnalysis = cancelPendingAnalysis;
|
|
5
|
+
exports.getAnalysisTimeoutMs = getAnalysisTimeoutMs;
|
|
3
6
|
exports.analyzeProjects = analyzeProjects;
|
|
4
7
|
exports.readCachedAnalysisResult = readCachedAnalysisResult;
|
|
5
8
|
exports.clearCache = clearCache;
|
|
6
9
|
exports.isAnalysisErrorResult = isAnalysisErrorResult;
|
|
7
10
|
exports.isAnalysisSuccessResult = isAnalysisSuccessResult;
|
|
8
|
-
const node_child_process_1 = require("node:child_process");
|
|
9
11
|
const node_fs_1 = require("node:fs");
|
|
10
12
|
const node_path_1 = require("node:path");
|
|
11
13
|
const devkit_1 = require("@nx/devkit");
|
|
12
14
|
const internal_1 = require("@nx/devkit/internal");
|
|
15
|
+
const DEFAULT_ANALYSIS_TIMEOUT_SECONDS = (0, internal_1.isCI)() ? 600 : 120;
|
|
16
|
+
// setTimeout silently clamps a delay past the 32-bit signed max to 1ms, which
|
|
17
|
+
// would abort immediately — the opposite of what a large value asks for.
|
|
18
|
+
const MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
19
|
+
// Thrown when a newer analysis supersedes an in-flight one. Callers swallow it
|
|
20
|
+
// rather than surfacing an internal string to the user.
|
|
21
|
+
exports.ANALYZER_CANCELLED_MESSAGE = 'Analyzer run was cancelled';
|
|
22
|
+
let currentAbortController;
|
|
23
|
+
/**
|
|
24
|
+
* Cancel any in-flight analyzer process. Safe to call even if nothing is running.
|
|
25
|
+
*/
|
|
26
|
+
function cancelPendingAnalysis() {
|
|
27
|
+
if (currentAbortController) {
|
|
28
|
+
currentAbortController.abort('cancelled');
|
|
29
|
+
currentAbortController = undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getAnalysisTimeoutMs() {
|
|
33
|
+
const envTimeout = process.env.NX_DOTNET_PROJECT_GRAPH_TIMEOUT;
|
|
34
|
+
if (envTimeout) {
|
|
35
|
+
const parsed = Number(envTimeout);
|
|
36
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
37
|
+
return Math.min(parsed * 1000, MAX_TIMEOUT_MS);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return DEFAULT_ANALYSIS_TIMEOUT_SECONDS * 1000;
|
|
41
|
+
}
|
|
13
42
|
let cache = null;
|
|
14
43
|
/**
|
|
15
44
|
* Get the path to the msbuild-analyzer executable
|
|
@@ -37,64 +66,11 @@ function getAnalyzerPath() {
|
|
|
37
66
|
async function calculateProjectFilesHash(files) {
|
|
38
67
|
return await (0, internal_1.hashWithWorkspaceContext)(devkit_1.workspaceRoot, files);
|
|
39
68
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Joins a project root and a glob pattern, the way `{projectRoot}` inputs are
|
|
42
|
-
* expanded for ordinary task hashing.
|
|
43
|
-
*/
|
|
44
|
-
function rootGlob(root, pattern) {
|
|
45
|
-
const combined = `${root}/${pattern}`;
|
|
46
|
-
// Same problem Nx's own {projectRoot} substitution hits in task-hasher.ts:
|
|
47
|
-
// a workspace-root project's root is ".", and "./**" matches nothing.
|
|
48
|
-
return combined.startsWith('./') ? combined.slice(2) : combined;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Hash the C# sources of every project that produced atomized targets.
|
|
52
|
-
*
|
|
53
|
-
* Atomized targets are derived from the test classes and methods declared in a
|
|
54
|
-
* project's sources, but `calculateProjectFilesHash` only covers project files.
|
|
55
|
-
* Without this, adding or removing a test would leave the cached analysis in
|
|
56
|
-
* place and the new test would silently never get a target.
|
|
57
|
-
*
|
|
58
|
-
* Returns null when nothing atomized, which skips the work entirely — the point
|
|
59
|
-
* being that a workspace which never opts in pays nothing for this.
|
|
60
|
-
*
|
|
61
|
-
* All roots go into a single glob group on purpose. The underlying native
|
|
62
|
-
* implementation walks the whole file list once per group, so grouping per
|
|
63
|
-
* project root would make this O(roots x files) instead of O(files).
|
|
64
|
-
*/
|
|
65
|
-
async function computeSourceHash(result) {
|
|
66
|
-
if (!result.atomizedRoots?.length) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
const rootsHash = await (0, internal_1.hashWithWorkspaceContext)(devkit_1.workspaceRoot, result.atomizedRoots.map((root) => rootGlob(root, '**/*.cs')));
|
|
70
|
-
// Discovery reads MSBuild's Compile items, which can point outside the
|
|
71
|
-
// project. A linked test file would otherwise change the discovered units
|
|
72
|
-
// without changing this hash. Hashed by exact path rather than folded into
|
|
73
|
-
// the glob group above: these are literal paths, not patterns, and one
|
|
74
|
-
// containing a glob metacharacter (*, !, {, ...) would silently change
|
|
75
|
-
// which files the group matches.
|
|
76
|
-
const externalHashes = (result.atomizedExternalSources ?? []).map((relativePath) => (0, internal_1.hashFile)((0, node_path_1.join)(devkit_1.workspaceRoot, relativePath)) ?? '');
|
|
77
|
-
return externalHashes.length === 0
|
|
78
|
-
? rootsHash
|
|
79
|
-
: (0, devkit_1.hashArray)([rootsHash, ...externalHashes]);
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Whether a cached analysis still reflects what is on disk.
|
|
83
|
-
*
|
|
84
|
-
* A null `sourceHash` means the analysis did not depend on C# sources, so the
|
|
85
|
-
* project-files hash the entry is keyed by is already sufficient.
|
|
86
|
-
*/
|
|
87
|
-
async function isSourceHashCurrent(sourceHash, result) {
|
|
88
|
-
if (sourceHash === null) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
return sourceHash === (await computeSourceHash(result));
|
|
92
|
-
}
|
|
93
69
|
/**
|
|
94
70
|
* Run the msbuild-analyzer and return the results.
|
|
95
71
|
* Uses stdin for large file lists to avoid ARG_MAX issues.
|
|
96
72
|
*/
|
|
97
|
-
function runAnalyzer(files, options) {
|
|
73
|
+
async function runAnalyzer(files, options) {
|
|
98
74
|
if (files.length === 0) {
|
|
99
75
|
return { nodesByFile: {}, referencesByRoot: {} };
|
|
100
76
|
}
|
|
@@ -125,44 +101,106 @@ function runAnalyzer(files, options) {
|
|
|
125
101
|
// }
|
|
126
102
|
// }
|
|
127
103
|
// }
|
|
104
|
+
// Nothing here may contain a double quote: on Windows `safeSpawn` routes a bare binary
|
|
105
|
+
// name through cmd.exe, whose quoting cannot express one, so it refuses the argument.
|
|
106
|
+
// That is why the options JSON goes over stdin instead (see below).
|
|
107
|
+
const args = [analyzerPath, devkit_1.workspaceRoot];
|
|
108
|
+
// Cancel any in-flight analyzer from a previous call, then create a fresh controller.
|
|
109
|
+
cancelPendingAnalysis();
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
currentAbortController = controller;
|
|
112
|
+
const signal = controller.signal;
|
|
113
|
+
const timeoutMs = getAnalysisTimeoutMs();
|
|
114
|
+
const timeoutSeconds = timeoutMs / 1000;
|
|
115
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
116
|
+
let stdout = '';
|
|
117
|
+
let stderr = '';
|
|
128
118
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
119
|
+
await new Promise((resolve, reject) => {
|
|
120
|
+
const child = (0, internal_1.safeSpawn)('dotnet', args, {
|
|
121
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
122
|
+
windowsHide: true,
|
|
123
|
+
env,
|
|
124
|
+
});
|
|
125
|
+
// A plugin worker torn down by `nx reset` would otherwise orphan the analyzer.
|
|
126
|
+
(0, internal_1.killChildOnHostExit)(child);
|
|
127
|
+
// On abort, kill the process tree and settle immediately — a wedged
|
|
128
|
+
// process that outlives the kill signal would otherwise keep this
|
|
129
|
+
// promise pending and the abort error would never surface.
|
|
130
|
+
const onAbort = () => {
|
|
131
|
+
if (child.pid) {
|
|
132
|
+
(0, internal_1.killProcessTreeGraceful)(child.pid).catch(() => { });
|
|
133
|
+
}
|
|
134
|
+
reject(new Error('Analyzer aborted'));
|
|
135
|
+
};
|
|
136
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
137
|
+
child.stdout?.setEncoding('utf-8');
|
|
138
|
+
child.stderr?.setEncoding('utf-8');
|
|
139
|
+
child.stdout?.on('data', (data) => {
|
|
140
|
+
stdout += data;
|
|
141
|
+
});
|
|
142
|
+
child.stderr?.on('data', (data) => {
|
|
143
|
+
stderr += data;
|
|
144
|
+
});
|
|
145
|
+
child.on('error', (error) => {
|
|
146
|
+
signal.removeEventListener('abort', onAbort);
|
|
147
|
+
reject(error);
|
|
148
|
+
});
|
|
149
|
+
child.on('close', (code) => {
|
|
150
|
+
signal.removeEventListener('abort', onAbort);
|
|
151
|
+
if (code === 0) {
|
|
152
|
+
resolve();
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
reject(new Error(`Analyzer exited with code ${code}: ${stderr}`));
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
// Stream the options JSON, then the file list, over stdin. The first line is
|
|
159
|
+
// always written, empty when there are no options, so the analyzer never has to
|
|
160
|
+
// infer whether one was sent. The analyzer partitions paths by filename, so we
|
|
161
|
+
// just send everything in one block.
|
|
162
|
+
// EPIPE if the analyzer dies early. Surfaced via `close` only when the
|
|
163
|
+
// child also exits non-zero; a stdin error with a clean exit means the
|
|
164
|
+
// analyzer read a truncated file list.
|
|
165
|
+
child.stdin?.on('error', () => { });
|
|
166
|
+
child.stdin?.end([options ? JSON.stringify(options) : '', ...files].join('\n'));
|
|
146
167
|
});
|
|
147
|
-
|
|
148
|
-
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (signal.reason === 'cancelled') {
|
|
171
|
+
throw new Error(exports.ANALYZER_CANCELLED_MESSAGE);
|
|
149
172
|
}
|
|
150
|
-
if (
|
|
151
|
-
throw new Error(`
|
|
173
|
+
if (signal.aborted) {
|
|
174
|
+
throw new Error(`msbuild-analyzer timed out after ${timeoutSeconds} ${timeoutSeconds === 1 ? 'second' : 'seconds'}.\n` +
|
|
175
|
+
` 1. Set the environment variable NX_DOTNET_PROJECT_GRAPH_TIMEOUT to a higher value (in seconds) to increase the timeout.\n` +
|
|
176
|
+
` 2. If the issue persists, set NX_DOTNET_DISABLE=true to disable the .NET plugin entirely.`);
|
|
152
177
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
178
|
+
const err = error;
|
|
179
|
+
const stderrAlreadyInMessage = err.message.includes(stderr);
|
|
180
|
+
if (stderr && !stderrAlreadyInMessage) {
|
|
181
|
+
devkit_1.logger.error(`msbuild-analyzer error: ${stderr}`);
|
|
156
182
|
}
|
|
157
|
-
|
|
158
|
-
return JSON.parse(output);
|
|
183
|
+
throw new Error(`Failed to run msbuild-analyzer: ${err.message}${stderr && !stderrAlreadyInMessage ? `\n${stderr}` : ''}`);
|
|
159
184
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
185
|
+
finally {
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
// Drop the settled controller so a later cancelPendingAnalysis() cannot
|
|
188
|
+
// abort a run that already finished.
|
|
189
|
+
if (currentAbortController === controller) {
|
|
190
|
+
currentAbortController = undefined;
|
|
164
191
|
}
|
|
165
|
-
|
|
192
|
+
}
|
|
193
|
+
// Output stderr (includes performance logs when NX_PERF_LOGGING=true)
|
|
194
|
+
if (stderr) {
|
|
195
|
+
console.error(stderr);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
// parseJson over JSON.parse: on malformed output it reports line:column with
|
|
199
|
+
// a code frame, where JSON.parse gives only a character offset.
|
|
200
|
+
return (0, devkit_1.parseJson)(stdout);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
throw new Error(`Failed to parse msbuild-analyzer output: ${error.message}`);
|
|
166
204
|
}
|
|
167
205
|
}
|
|
168
206
|
/**
|
|
@@ -172,47 +210,36 @@ function runAnalyzer(files, options) {
|
|
|
172
210
|
*/
|
|
173
211
|
async function analyzeProjects(files, options) {
|
|
174
212
|
const filesHash = await calculateProjectFilesHash(files);
|
|
175
|
-
const optionsHash = (0, internal_1.hashObject)(options);
|
|
176
213
|
// Return cached results if the hash matches
|
|
177
214
|
if (cache &&
|
|
178
215
|
cache.hash === filesHash &&
|
|
179
|
-
cache.optionsHash === optionsHash &&
|
|
180
216
|
// NOTE: We don't read from the cache here if it's an error result,
|
|
181
217
|
// to allow retrying analysis in case of transient errors or errors fixed
|
|
182
218
|
// that may not be reflected in the hash (like setting an env var).
|
|
183
|
-
isAnalysisSuccessResult(cache.result)
|
|
184
|
-
(await isSourceHashCurrent(cache.sourceHash, cache.result))) {
|
|
219
|
+
isAnalysisSuccessResult(cache.result)) {
|
|
185
220
|
return cache.result;
|
|
186
221
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const cachedEntry = analyzerCache.get(filesHash);
|
|
192
|
-
if (cachedEntry?.result &&
|
|
193
|
-
(await isSourceHashCurrent(cachedEntry.sourceHash, cachedEntry.result))) {
|
|
222
|
+
const optionsHash = (0, internal_1.hashObject)(options);
|
|
223
|
+
const analyzerCache = new internal_1.PluginCache((0, node_path_1.join)(internal_1.workspaceDataDirectory, `dotnet-${optionsHash}.hash`));
|
|
224
|
+
const cachedResult = analyzerCache.get(filesHash);
|
|
225
|
+
if (cachedResult) {
|
|
194
226
|
// Update cache
|
|
195
227
|
cache = {
|
|
196
228
|
hash: filesHash,
|
|
197
|
-
|
|
198
|
-
result: cachedEntry.result,
|
|
199
|
-
sourceHash: cachedEntry.sourceHash,
|
|
229
|
+
result: cachedResult,
|
|
200
230
|
};
|
|
201
|
-
return
|
|
231
|
+
return cachedResult;
|
|
202
232
|
}
|
|
203
233
|
// Run the analyzer
|
|
204
234
|
try {
|
|
205
|
-
const result = runAnalyzer(files, options);
|
|
206
|
-
const sourceHash = await computeSourceHash(result);
|
|
235
|
+
const result = await runAnalyzer(files, options);
|
|
207
236
|
// Update local cache
|
|
208
237
|
cache = {
|
|
209
238
|
hash: filesHash,
|
|
210
|
-
optionsHash,
|
|
211
239
|
result,
|
|
212
|
-
sourceHash,
|
|
213
240
|
};
|
|
214
241
|
// Update persistent cache
|
|
215
|
-
analyzerCache.set(filesHash,
|
|
242
|
+
analyzerCache.set(filesHash, result);
|
|
216
243
|
analyzerCache.writeToDisk();
|
|
217
244
|
return result;
|
|
218
245
|
}
|
|
@@ -226,12 +253,14 @@ async function analyzeProjects(files, options) {
|
|
|
226
253
|
const errorResult = {
|
|
227
254
|
error: err,
|
|
228
255
|
};
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
256
|
+
// A superseded run is not a result: caching it would hand the sentinel to
|
|
257
|
+
// createDependencies via readCachedAnalysisResult().
|
|
258
|
+
if (err.message !== exports.ANALYZER_CANCELLED_MESSAGE) {
|
|
259
|
+
cache = {
|
|
260
|
+
hash: filesHash,
|
|
261
|
+
result: errorResult,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
235
264
|
return errorResult;
|
|
236
265
|
}
|
|
237
266
|
}
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"Microsoft.Build": "17.0.0",
|
|
12
12
|
"Microsoft.Build.Framework": "17.0.0",
|
|
13
|
-
"Microsoft.Build.Locator": "1.8.1"
|
|
14
|
-
"Microsoft.CodeAnalysis.CSharp": "4.11.0"
|
|
13
|
+
"Microsoft.Build.Locator": "1.8.1"
|
|
15
14
|
},
|
|
16
15
|
"runtime": {
|
|
17
16
|
"MsbuildAnalyzer.dll": {}
|
|
@@ -22,9 +21,9 @@
|
|
|
22
21
|
"Microsoft.Build.Framework": "17.0.0",
|
|
23
22
|
"Microsoft.NET.StringTools": "1.0.0",
|
|
24
23
|
"Microsoft.Win32.Registry": "4.3.0",
|
|
25
|
-
"System.Collections.Immutable": "
|
|
24
|
+
"System.Collections.Immutable": "5.0.0",
|
|
26
25
|
"System.Configuration.ConfigurationManager": "4.7.0",
|
|
27
|
-
"System.Reflection.Metadata": "
|
|
26
|
+
"System.Reflection.Metadata": "1.6.0",
|
|
28
27
|
"System.Security.Principal.Windows": "4.7.0",
|
|
29
28
|
"System.Text.Encoding.CodePages": "4.0.1",
|
|
30
29
|
"System.Text.Json": "5.0.2",
|
|
@@ -44,22 +43,6 @@
|
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
},
|
|
47
|
-
"Microsoft.CodeAnalysis.Analyzers/3.3.4": {},
|
|
48
|
-
"Microsoft.CodeAnalysis.Common/4.11.0": {
|
|
49
|
-
"dependencies": {
|
|
50
|
-
"Microsoft.CodeAnalysis.Analyzers": "3.3.4",
|
|
51
|
-
"System.Collections.Immutable": "8.0.0",
|
|
52
|
-
"System.Reflection.Metadata": "8.0.0"
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"Microsoft.CodeAnalysis.CSharp/4.11.0": {
|
|
56
|
-
"dependencies": {
|
|
57
|
-
"Microsoft.CodeAnalysis.Analyzers": "3.3.4",
|
|
58
|
-
"Microsoft.CodeAnalysis.Common": "4.11.0",
|
|
59
|
-
"System.Collections.Immutable": "8.0.0",
|
|
60
|
-
"System.Reflection.Metadata": "8.0.0"
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
46
|
"Microsoft.NET.StringTools/1.0.0": {
|
|
64
47
|
"dependencies": {
|
|
65
48
|
"System.Memory": "4.5.4",
|
|
@@ -92,7 +75,7 @@
|
|
|
92
75
|
"System.Runtime": "4.3.0"
|
|
93
76
|
}
|
|
94
77
|
},
|
|
95
|
-
"System.Collections.Immutable/
|
|
78
|
+
"System.Collections.Immutable/5.0.0": {},
|
|
96
79
|
"System.Configuration.ConfigurationManager/4.7.0": {
|
|
97
80
|
"dependencies": {
|
|
98
81
|
"System.Security.Cryptography.ProtectedData": "4.7.0",
|
|
@@ -131,11 +114,7 @@
|
|
|
131
114
|
"System.Runtime": "4.3.0"
|
|
132
115
|
}
|
|
133
116
|
},
|
|
134
|
-
"System.Reflection.Metadata/
|
|
135
|
-
"dependencies": {
|
|
136
|
-
"System.Collections.Immutable": "8.0.0"
|
|
137
|
-
}
|
|
138
|
-
},
|
|
117
|
+
"System.Reflection.Metadata/1.6.0": {},
|
|
139
118
|
"System.Reflection.Primitives/4.3.0": {
|
|
140
119
|
"dependencies": {
|
|
141
120
|
"Microsoft.NETCore.Platforms": "3.1.0",
|
|
@@ -269,27 +248,6 @@
|
|
|
269
248
|
"path": "microsoft.build.locator/1.8.1",
|
|
270
249
|
"hashPath": "microsoft.build.locator.1.8.1.nupkg.sha512"
|
|
271
250
|
},
|
|
272
|
-
"Microsoft.CodeAnalysis.Analyzers/3.3.4": {
|
|
273
|
-
"type": "package",
|
|
274
|
-
"serviceable": true,
|
|
275
|
-
"sha512": "sha512-AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==",
|
|
276
|
-
"path": "microsoft.codeanalysis.analyzers/3.3.4",
|
|
277
|
-
"hashPath": "microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512"
|
|
278
|
-
},
|
|
279
|
-
"Microsoft.CodeAnalysis.Common/4.11.0": {
|
|
280
|
-
"type": "package",
|
|
281
|
-
"serviceable": true,
|
|
282
|
-
"sha512": "sha512-djf8ujmqYImFgB04UGtcsEhHrzVqzHowS+EEl/Yunc5LdrYrZhGBWUTXoCF0NzYXJxtfuD+UVQarWpvrNc94Qg==",
|
|
283
|
-
"path": "microsoft.codeanalysis.common/4.11.0",
|
|
284
|
-
"hashPath": "microsoft.codeanalysis.common.4.11.0.nupkg.sha512"
|
|
285
|
-
},
|
|
286
|
-
"Microsoft.CodeAnalysis.CSharp/4.11.0": {
|
|
287
|
-
"type": "package",
|
|
288
|
-
"serviceable": true,
|
|
289
|
-
"sha512": "sha512-6XYi2EusI8JT4y2l/F3VVVS+ISoIX9nqHsZRaG6W5aFeJ5BEuBosHfT/ABb73FN0RZ1Z3cj2j7cL28SToJPXOw==",
|
|
290
|
-
"path": "microsoft.codeanalysis.csharp/4.11.0",
|
|
291
|
-
"hashPath": "microsoft.codeanalysis.csharp.4.11.0.nupkg.sha512"
|
|
292
|
-
},
|
|
293
251
|
"Microsoft.NET.StringTools/1.0.0": {
|
|
294
252
|
"type": "package",
|
|
295
253
|
"serviceable": true,
|
|
@@ -332,12 +290,12 @@
|
|
|
332
290
|
"path": "system.collections/4.3.0",
|
|
333
291
|
"hashPath": "system.collections.4.3.0.nupkg.sha512"
|
|
334
292
|
},
|
|
335
|
-
"System.Collections.Immutable/
|
|
293
|
+
"System.Collections.Immutable/5.0.0": {
|
|
336
294
|
"type": "package",
|
|
337
295
|
"serviceable": true,
|
|
338
|
-
"sha512": "sha512-
|
|
339
|
-
"path": "system.collections.immutable/
|
|
340
|
-
"hashPath": "system.collections.immutable.
|
|
296
|
+
"sha512": "sha512-FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==",
|
|
297
|
+
"path": "system.collections.immutable/5.0.0",
|
|
298
|
+
"hashPath": "system.collections.immutable.5.0.0.nupkg.sha512"
|
|
341
299
|
},
|
|
342
300
|
"System.Configuration.ConfigurationManager/4.7.0": {
|
|
343
301
|
"type": "package",
|
|
@@ -381,12 +339,12 @@
|
|
|
381
339
|
"path": "system.reflection/4.3.0",
|
|
382
340
|
"hashPath": "system.reflection.4.3.0.nupkg.sha512"
|
|
383
341
|
},
|
|
384
|
-
"System.Reflection.Metadata/
|
|
342
|
+
"System.Reflection.Metadata/1.6.0": {
|
|
385
343
|
"type": "package",
|
|
386
344
|
"serviceable": true,
|
|
387
|
-
"sha512": "sha512-
|
|
388
|
-
"path": "system.reflection.metadata/
|
|
389
|
-
"hashPath": "system.reflection.metadata.
|
|
345
|
+
"sha512": "sha512-COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==",
|
|
346
|
+
"path": "system.reflection.metadata/1.6.0",
|
|
347
|
+
"hashPath": "system.reflection.metadata.1.6.0.nupkg.sha512"
|
|
390
348
|
},
|
|
391
349
|
"System.Reflection.Primitives/4.3.0": {
|
|
392
350
|
"type": "package",
|
|
Binary file
|
|
Binary file
|
|
@@ -1,50 +1,10 @@
|
|
|
1
|
-
import { CreateNodes,
|
|
1
|
+
import { CreateNodes, TargetConfiguration } from '@nx/devkit';
|
|
2
2
|
export type TargetConfigurationWithName = Partial<TargetConfiguration> & {
|
|
3
3
|
/**
|
|
4
4
|
* The name of the target. Defaults to the target type (e.g., 'build', 'test', etc.)
|
|
5
5
|
*/
|
|
6
6
|
targetName?: string;
|
|
7
7
|
};
|
|
8
|
-
/**
|
|
9
|
-
* Configuration for the test target, including the options that split it into
|
|
10
|
-
* one task per test unit.
|
|
11
|
-
*/
|
|
12
|
-
export type TestTargetConfiguration = TargetConfigurationWithName & {
|
|
13
|
-
/**
|
|
14
|
-
* Name of the target that runs the project's tests split across many tasks.
|
|
15
|
-
*
|
|
16
|
-
* Leave unset (the default) and nothing is split — the project keeps only its
|
|
17
|
-
* ordinary `test` target. Setting it turns on atomization for every project
|
|
18
|
-
* this plugin registration matches, so to split a single project, register
|
|
19
|
-
* `@nx/dotnet` a second time with an `include` pattern scoped to it.
|
|
20
|
-
*
|
|
21
|
-
* Requires Microsoft.Testing.Platform. Running the group needs Nx Cloud: Nx
|
|
22
|
-
* refuses the parent target without it, because expanding into every task on
|
|
23
|
-
* one machine is slower than not splitting at all. Individual tasks carry no
|
|
24
|
-
* such restriction and run locally.
|
|
25
|
-
*/
|
|
26
|
-
ciTargetName?: string;
|
|
27
|
-
/**
|
|
28
|
-
* Overrides the name of the target group these tasks are collapsed into in Nx
|
|
29
|
-
* Console and the graph. Defaults to a name derived from `ciTargetName`
|
|
30
|
-
* (`test-ci` becomes `TEST (CI)`).
|
|
31
|
-
*/
|
|
32
|
-
ciGroupName?: string;
|
|
33
|
-
/**
|
|
34
|
-
* Whether each task runs one test class or one test method. Defaults to
|
|
35
|
-
* `'class'`.
|
|
36
|
-
*
|
|
37
|
-
* `'method'` is worth opting into when each test method carries its own
|
|
38
|
-
* expensive fixture — spinning up a distributed application, a database, a
|
|
39
|
-
* browser — because then grouping methods into one task saves no setup work
|
|
40
|
-
* and only serializes tests that could have run on separate agents. For
|
|
41
|
-
* ordinary suites it multiplies the target count for no gain, since process
|
|
42
|
-
* startup dominates a fast test.
|
|
43
|
-
*
|
|
44
|
-
* `'method'` currently requires MSTest; see the plugin docs.
|
|
45
|
-
*/
|
|
46
|
-
ciSplitBy?: 'class' | 'method';
|
|
47
|
-
};
|
|
48
8
|
/**
|
|
49
9
|
* Configuration options for the @nx/dotnet plugin.
|
|
50
10
|
*
|
|
@@ -85,11 +45,9 @@ export interface DotNetPluginOptions {
|
|
|
85
45
|
build?: TargetConfigurationWithName | false;
|
|
86
46
|
/**
|
|
87
47
|
* Configuration for the test target.
|
|
88
|
-
* Use `targetName` to rename the target,
|
|
89
|
-
* the tests across one task per test unit, and provide additional
|
|
90
|
-
* options/configurations to merge with the generated target.
|
|
48
|
+
* Use `targetName` to rename the target, and provide additional options/configurations to merge with the generated target.
|
|
91
49
|
*/
|
|
92
|
-
test?:
|
|
50
|
+
test?: TargetConfigurationWithName | false;
|
|
93
51
|
/**
|
|
94
52
|
* Configuration for the clean target.
|
|
95
53
|
* Use `targetName` to rename the target, and provide additional options/configurations to merge with the generated target.
|
|
@@ -121,10 +79,6 @@ export interface DotNetPluginOptions {
|
|
|
121
79
|
*/
|
|
122
80
|
run?: TargetConfigurationWithName | false;
|
|
123
81
|
}
|
|
124
|
-
/**
|
|
125
|
-
* Merge user-specified target configurations with the generated targets from the analyzer
|
|
126
|
-
*/
|
|
127
|
-
export declare function mergeUserTargetConfigurations(node: ProjectConfiguration, options: DotNetPluginOptions): ProjectConfiguration;
|
|
128
82
|
export declare const createNodes: CreateNodes<DotNetPluginOptions>;
|
|
129
83
|
/**
|
|
130
84
|
* @deprecated Use {@link createNodes} instead. This will be removed in Nx 24.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-nodes.d.ts","sourceRoot":"","sources":["../../src/plugins/create-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,
|
|
1
|
+
{"version":3,"file":"create-nodes.d.ts","sourceRoot":"","sources":["../../src/plugins/create-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAGX,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAQpB,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG;IACvE;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;OAGG;IACH,IAAI,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC3C;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;OAGG;IACH,OAAO,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC9C;;;OAGG;IACH,OAAO,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC9C;;;OAGG;IACH,IAAI,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC3C;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;OAGG;IACH,GAAG,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;CAC3C;AAqFD,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,mBAAmB,CAqFxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,kCAAc,CAAC"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createNodesV2 = exports.createNodes = void 0;
|
|
4
|
-
exports.mergeUserTargetConfigurations = mergeUserTargetConfigurations;
|
|
5
4
|
const devkit_1 = require("@nx/devkit");
|
|
6
5
|
const analyzer_client_1 = require("../analyzer/analyzer-client");
|
|
7
6
|
const internal_1 = require("@nx/devkit/internal");
|
|
@@ -38,13 +37,8 @@ function mergeUserTargetConfigurations(node, options) {
|
|
|
38
37
|
delete mergedTargets[defaultTargetName];
|
|
39
38
|
continue;
|
|
40
39
|
}
|
|
41
|
-
// Use empty object as default when option is not provided
|
|
42
|
-
|
|
43
|
-
// The ci* keys configure how the analyzer generates targets rather than
|
|
44
|
-
// being target configuration themselves, so they are pulled out here
|
|
45
|
-
// alongside targetName — otherwise they would be merged onto the generated
|
|
46
|
-
// target as junk properties.
|
|
47
|
-
const { targetName, ciTargetName: _ciTargetName, ciGroupName: _ciGroupName, ciSplitBy: _ciSplitBy, ...userSpecifiedConfig } = targetOption ?? {};
|
|
40
|
+
// Use empty object as default when option is not provided
|
|
41
|
+
const { targetName, ...userSpecifiedConfig } = targetOption ?? {};
|
|
48
42
|
const actualTargetName = targetName ?? defaultTargetName;
|
|
49
43
|
// Find the generated target - it might be under the default name or the user-specified name
|
|
50
44
|
const generatedTarget = mergedTargets[actualTargetName] ?? mergedTargets[defaultTargetName];
|
|
@@ -69,7 +63,6 @@ function mergeUserTargetConfigurations(node, options) {
|
|
|
69
63
|
return {
|
|
70
64
|
...node,
|
|
71
65
|
targets: mergedTargets,
|
|
72
|
-
...(node.metadata ? { metadata: node.metadata } : {}),
|
|
73
66
|
};
|
|
74
67
|
}
|
|
75
68
|
exports.createNodes = [
|
|
@@ -79,23 +72,8 @@ exports.createNodes = [
|
|
|
79
72
|
try {
|
|
80
73
|
// Normalize options to handle undefined (when plugin is registered as string)
|
|
81
74
|
const normalizedOptions = options ?? {};
|
|
82
|
-
const testOptions = normalizedOptions.test === false ? undefined : normalizedOptions.test;
|
|
83
|
-
// Only meaningful when the test target itself is enabled; splitting a
|
|
84
|
-
// target that will be deleted would generate targets referring to it.
|
|
85
|
-
const ciTargetName = testOptions?.ciTargetName;
|
|
86
75
|
// Extract target names from new format and create options for analyzer
|
|
87
76
|
const analyzerOptions = {
|
|
88
|
-
// Derived here rather than in the analyzer so there is one
|
|
89
|
-
// implementation of the naming convention shared with every other Nx
|
|
90
|
-
// plugin that splits tests.
|
|
91
|
-
testCiTargetName: ciTargetName,
|
|
92
|
-
testCiGroupName: ciTargetName
|
|
93
|
-
? (testOptions?.ciGroupName ??
|
|
94
|
-
(0, internal_1.deriveGroupNameFromTarget)(ciTargetName))
|
|
95
|
-
: undefined,
|
|
96
|
-
testCiSplitBy: ciTargetName
|
|
97
|
-
? (testOptions?.ciSplitBy ?? 'class')
|
|
98
|
-
: undefined,
|
|
99
77
|
buildTargetName: (normalizedOptions.build && normalizedOptions.build.targetName) ||
|
|
100
78
|
'build',
|
|
101
79
|
testTargetName: (normalizedOptions.test && normalizedOptions.test.targetName) ||
|
|
@@ -114,6 +92,14 @@ exports.createNodes = [
|
|
|
114
92
|
};
|
|
115
93
|
const result = await (0, analyzer_client_1.analyzeProjects)([...configFilePaths], analyzerOptions);
|
|
116
94
|
if ((0, analyzer_client_1.isAnalysisErrorResult)(result)) {
|
|
95
|
+
if (result.error.message === analyzer_client_1.ANALYZER_CANCELLED_MESSAGE) {
|
|
96
|
+
// Superseded by a newer analysis — silently return empty rather than
|
|
97
|
+
// failing the user's command with an internal sentinel.
|
|
98
|
+
// Safe only because the daemon drops this compute at its
|
|
99
|
+
// `stalePostCreateNodes` guard before createDependencies runs; that one
|
|
100
|
+
// would throw on the cache this run deliberately did not write.
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
117
103
|
throw result.error;
|
|
118
104
|
}
|
|
119
105
|
const { nodesByFile } = result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/dotnet",
|
|
3
|
-
"version": "23.2.0-pr.
|
|
3
|
+
"version": "23.2.0-pr.36838.ef8e258",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Nx Plugin for .NET containing graph support for working with .NET projects in an Nx workspace.",
|
|
6
6
|
"keywords": [
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"tslib": "^2.3.0",
|
|
51
51
|
"ignore": "^7.0.5",
|
|
52
|
-
"@nx/devkit": "23.2.0-pr.
|
|
52
|
+
"@nx/devkit": "23.2.0-pr.36838.ef8e258"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/jest": "30.0.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"memfs": "^4.9.2",
|
|
59
59
|
"ts-jest": "^29.4.7",
|
|
60
60
|
"typescript": "~6.0.3",
|
|
61
|
-
"nx": "23.2.0-pr.
|
|
61
|
+
"nx": "23.2.0-pr.36838.ef8e258"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|