Skip to content

Commit 71f6893

Browse files
authored
fix(core): prevent stale cache entries from breaking swagger on dotnet upgrades (#633)
1 parent e6128d0 commit 71f6893

File tree

7 files changed

+83
-19
lines changed

7 files changed

+83
-19
lines changed

demo/apps/webapi/NxDotnet.Test.Webapi.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22
<PropertyGroup>
3-
<TargetFramework>net6.0</TargetFramework>
3+
<TargetFramework>net7.0</TargetFramework>
44
<Nullable>enable</Nullable>
55
<ImplicitUsings>enable</ImplicitUsings>
66
</PropertyGroup>

demo/apps/webapi/Program.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
// Add services to the container.
44

55
builder.Services.AddControllers();
6-
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
6+
// Learn more about configuring Swagger at https://aka.ms/aspnetcore/swashbuckle
77
builder.Services.AddEndpointsApiExplorer();
88
builder.Services.AddSwaggerGen();
99

1010
var app = builder.Build();
1111

1212
// Configure the HTTP request pipeline.
1313
if (app.Environment.IsDevelopment())
14-
{
15-
app.UseSwagger();
14+
{
15+
app.UseSwagger();
1616
app.UseSwaggerUI();
1717
}
1818

demo/libs/csharp-models/NxDotnet.Demo.Libs.CsharpModels.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net7.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
</PropertyGroup>

global.json

Lines changed: 0 additions & 6 deletions
This file was deleted.

packages/core/src/executors/build/executor.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import { ExecutorContext, workspaceRoot } from '@nrwl/devkit';
1+
import {
2+
ExecutorContext,
3+
ProjectConfiguration,
4+
workspaceRoot,
5+
} from '@nrwl/devkit';
26

3-
import { resolve } from 'path';
7+
import { rmSync, statSync } from 'fs';
8+
import { join, resolve } from 'path';
49

510
import { DotNetClient, dotnetFactory } from '@nx-dotnet/dotnet';
611
import {
712
getExecutedProjectConfiguration,
813
getProjectFileForNxProject,
14+
inlineNxTokens,
915
} from '@nx-dotnet/utils';
1016

1117
import { BuildExecutorSchema } from './schema';
@@ -16,6 +22,8 @@ export default async function runExecutor(
1622
dotnetClient: DotNetClient = new DotNetClient(dotnetFactory()),
1723
) {
1824
const nxProjectConfiguration = getExecutedProjectConfiguration(context);
25+
removeOldArtifacts(context, nxProjectConfiguration);
26+
1927
dotnetClient.cwd = resolve(workspaceRoot, nxProjectConfiguration.root);
2028
dotnetClient.printSdkVersion();
2129
const projectFilePath = resolve(
@@ -35,3 +43,46 @@ export default async function runExecutor(
3543
success: true,
3644
};
3745
}
46+
47+
function removeOldArtifacts(
48+
context: ExecutorContext,
49+
projectConfiguration: ProjectConfiguration,
50+
) {
51+
const outputs = context.target?.outputs?.map((output) =>
52+
join(context.root, inlineNxTokens(output, projectConfiguration)),
53+
);
54+
if (
55+
!outputs &&
56+
Object.values(context.nxJsonConfiguration?.tasksRunnerOptions ?? {}).some(
57+
(runnerOptions) =>
58+
runnerOptions.options?.cacheableOperations?.includes(
59+
context.targetName,
60+
),
61+
)
62+
) {
63+
throw new Error(`[nx-dotnet] ${context.projectGraph}:${context.targetName} is cacheable, but has no outputs listed.
64+
65+
This will result in cache hits not retrieving build artifacts, only terminal outputs.
66+
67+
See: https://nx.dev/reference/project-configuration#outputs`);
68+
}
69+
for (const output of outputs || []) {
70+
if (
71+
// No reason to clear build intermediates, just makes the resulting build command slower.
72+
!output.includes('intermediates') &&
73+
!output.endsWith('obj') &&
74+
// Prevent exceptions from trying to rmdirSync(globPattern)
75+
getStatsOrNull(output)?.isDirectory()
76+
) {
77+
rmSync(output, { recursive: true });
78+
}
79+
}
80+
}
81+
82+
function getStatsOrNull(f: string) {
83+
try {
84+
return statSync(f);
85+
} catch {
86+
return null;
87+
}
88+
}

packages/core/src/generators/utils/get-path-to-startup-assembly.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,24 @@ export function buildStartupAssemblyPath(
3737
/(?:\.csproj|\.vbproj|\.fsproj)$/,
3838
'.dll',
3939
);
40-
const foundDll = sync(`**/${dllName}`, { cwd: outputDirectory })[0];
41-
if (!foundDll) {
40+
const matchingDlls = sync(`**/${dllName}`, { cwd: outputDirectory });
41+
if (!matchingDlls.length) {
4242
throw new Error(
4343
`[nx-dotnet] Unable to locate ${dllName} in ${relative(
4444
workspaceRoot,
4545
outputDirectory,
4646
)}`,
4747
);
4848
}
49-
return joinPathFragments(
50-
outputDirectory,
51-
sync(`**/${dllName}`, { cwd: outputDirectory })[0],
52-
);
49+
if (matchingDlls.length > 1) {
50+
throw new Error(
51+
`[nx-dotnet] Located multiple matching dlls for ${projectName}.
52+
53+
You may need to clean old build artifacts from your outputs, or manually
54+
specify the path to the output assembly within ${project.root}/project.json.`,
55+
);
56+
}
57+
return joinPathFragments(outputDirectory, matchingDlls[0]);
5358
}
5459

5560
function findBuildTarget(

packages/utils/src/lib/utility-functions/workspace.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from '@nrwl/devkit';
99

1010
import { readFileSync } from 'fs';
11+
import { NX_PREFIX } from 'nx/src/utils/logger';
1112
import { dirname, isAbsolute, relative, resolve } from 'path';
1213
import { XmlDocument, XmlElement } from 'xmldoc';
1314

@@ -135,3 +136,16 @@ export function getProjectFilesForProject(
135136
function normalizePath(p: string): string {
136137
return nxNormalizePath(p).split('\\').join('/');
137138
}
139+
140+
export function inlineNxTokens(value: string, project: ProjectConfiguration) {
141+
if (value.startsWith('{workspaceRoot}/')) {
142+
value = value.replace(/^\{workspaceRoot\}\//, '');
143+
}
144+
if (value.includes('{workspaceRoot}')) {
145+
throw new Error(
146+
`${NX_PREFIX} The {workspaceRoot} token is only valid at the beginning of an output.`,
147+
);
148+
}
149+
value = value.replace('{projectRoot}', project.root);
150+
return value.replace('{projectName}', project.name as string);
151+
}

0 commit comments

Comments
 (0)