diff --git a/resources/netCore/GetBlazorManifestLocations.targets b/resources/netCore/GetBlazorManifestLocations.targets
deleted file mode 100644
index 1c49e80c..00000000
--- a/resources/netCore/GetBlazorManifestLocations.targets
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- $([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(StaticWebAssetDevelopmentManifestPath)))
- $([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(OutputPath), $(TargetName).staticwebassets.runtime.json))
-
-
-
- $([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(_GeneratedStaticWebAssetsDevelopmentManifest)))
- $([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(OutputPath), $(TargetName).StaticWebAssets.xml))
-
-
-
-
-
diff --git a/resources/netCore/GetProjectProperties.targets b/resources/netCore/GetProjectProperties.targets
deleted file mode 100644
index 72fe4422..00000000
--- a/resources/netCore/GetProjectProperties.targets
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
- $(SDKContainerSupportEnabled)
- $(GetProjectPropertiesDependsOn);ComputeContainerConfig;
-
-
- $(GetProjectPropertiesDependsOn);_ComputeContainerExecutionArgs
-
-
- $(GetProjectPropertiesDependsOn);_ContainerEstablishRIDNess;_ComputeContainerExecutionArgs
-
-
-
-
-
- $(ContainerRepository)
- $(ContainerImageName)
-
-
-
-
diff --git a/src/debugging/netSdk/NetSdkDebugHelper.ts b/src/debugging/netSdk/NetSdkDebugHelper.ts
index 7e5c7ee9..68e2e072 100644
--- a/src/debugging/netSdk/NetSdkDebugHelper.ts
+++ b/src/debugging/netSdk/NetSdkDebugHelper.ts
@@ -111,31 +111,29 @@ export class NetSdkDebugHelper extends NetCoreDebugHelper {
const ridOS = await normalizeOsToRidOs();
const ridArchitecture = await normalizeArchitectureToRidArchitecture();
const additionalProperties = composeArgs(
- withNamedArg('/p:ContainerRuntimeIdentifier', `"${ridOS}-${ridArchitecture}"`, { assignValue: true }), // We have to pre-quote the file paths because we cannot simultaneously use `assignValue` and `shouldQuote`
+ withNamedArg('-p:ContainerRuntimeIdentifier', `"${ridOS}-${ridArchitecture}"`, { assignValue: true }), // We have to pre-quote the RID because we cannot simultaneously use `assignValue` and `shouldQuote`
)();
const resolvedAppProject = resolveVariables(debugConfiguration.netCore?.appProject, folder);
- const projectInfo = await getNetCoreProjectInfo('GetProjectProperties', resolvedAppProject, additionalProperties);
+ const projectInfo = await getNetCoreProjectInfo(resolvedAppProject, additionalProperties);
- if (projectInfo.length < 6 || !projectInfo[5]) {
+ if (!projectInfo.enableSdkContainerSupport) {
throw new Error(l10n.t("Your current project configuration or .NET SDK version doesn't support SDK Container build. Please choose a compatible project or update .NET SDK."));
}
- const projectProperties: NetSdkProjectProperties = {
- assemblyName: projectInfo[0],
- targetFramework: projectInfo[1],
- appOutput: projectInfo[2],
- containerWorkingDirectory: projectInfo[3],
- isSdkContainerSupportEnabled: projectInfo[4] === 'true',
- imageName: projectInfo[5],
+ return {
+ assemblyName: projectInfo.assemblyName,
+ targetFramework: projectInfo.targetFrameworks[0],
+ appOutput: projectInfo.assemblyRelativeOutputPath,
+ containerWorkingDirectory: projectInfo.assemblyContainerPath,
+ isSdkContainerSupportEnabled: projectInfo.enableSdkContainerSupport,
+ imageName: projectInfo.imageName,
};
-
- return projectProperties;
}
private async normalizeAppOutput(unnormalizedContainerWorkingDirectory: string, isSdkContainerSupportEnabled: boolean): Promise {
if (isSdkContainerSupportEnabled) {
- return await getDockerOSType() === 'windows' // fourth is output path
+ return await getDockerOSType() === 'windows'
? path.win32.normalize(unnormalizedContainerWorkingDirectory)
: path.posix.normalize(unnormalizedContainerWorkingDirectory);
} else {
diff --git a/src/debugging/netcore/NetCoreDebugHelper.ts b/src/debugging/netcore/NetCoreDebugHelper.ts
index c4ff0358..e3da177f 100644
--- a/src/debugging/netcore/NetCoreDebugHelper.ts
+++ b/src/debugging/netcore/NetCoreDebugHelper.ts
@@ -204,20 +204,13 @@ export class NetCoreDebugHelper implements DebugHelper {
}
protected async getProjectProperties(debugConfiguration: DockerDebugConfiguration): Promise {
- const projectInfo = await getNetCoreProjectInfo('GetProjectProperties', debugConfiguration.netCore?.appProject);
+ const projectInfo = await getNetCoreProjectInfo(debugConfiguration.netCore?.appProject);
- if (projectInfo.length < 3) {
- throw new Error(l10n.t('Unable to determine assembly output path.'));
- }
-
- // First line is assembly name, second is target framework, third+ are output path(s)
- const projectProperties: NetCoreProjectProperties = {
- assemblyName: projectInfo[0],
- targetFramework: projectInfo[1],
- appOutput: projectInfo[2]
+ return {
+ assemblyName: projectInfo.assemblyName,
+ targetFramework: projectInfo.targetFrameworks[0],
+ appOutput: projectInfo.assemblyRelativeOutputPath,
};
-
- return projectProperties;
}
private async acquireDebuggers(platformOS: PlatformOS): Promise {
diff --git a/src/scaffolding/wizard/netCore/NetCoreGatherInformationStep.ts b/src/scaffolding/wizard/netCore/NetCoreGatherInformationStep.ts
index d245efff..c274eb07 100644
--- a/src/scaffolding/wizard/netCore/NetCoreGatherInformationStep.ts
+++ b/src/scaffolding/wizard/netCore/NetCoreGatherInformationStep.ts
@@ -28,18 +28,14 @@ export class NetCoreGatherInformationStep extends GatherInformationStep {
await this.ensureNetCoreBuildTasks(wizardContext);
- const projectInfo = await getNetCoreProjectInfo('GetProjectProperties', wizardContext.artifact);
-
- if (projectInfo.length < 2) {
- throw new Error(vscode.l10n.t('Unable to determine project info for \'{0}\'', wizardContext.artifact));
- }
+ const projectInfo = await getNetCoreProjectInfo(wizardContext.artifact);
if (!wizardContext.netCoreAssemblyName) {
- wizardContext.netCoreAssemblyName = projectInfo[0]; // Line 1 is the assembly name including ".dll"
+ wizardContext.netCoreAssemblyName = projectInfo.assemblyName;
}
if (!wizardContext.netCoreRuntimeBaseImage || !wizardContext.netCoreSdkBaseImage) {
- this.targetFramework = projectInfo[1]; // Line 2 is the value, or first item from
+ this.targetFramework = projectInfo.targetFrameworks[0];
const regexMatch = /net(coreapp)?([\d.]+)/i.exec(this.targetFramework);
diff --git a/src/tasks/netcore/updateBlazorManifest.ts b/src/tasks/netcore/updateBlazorManifest.ts
index 80064c8e..fb4fd471 100644
--- a/src/tasks/netcore/updateBlazorManifest.ts
+++ b/src/tasks/netcore/updateBlazorManifest.ts
@@ -4,45 +4,21 @@
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
-import * as path from 'path';
-import { l10n } from 'vscode';
-import * as xml2js from 'xml2js';
-import { getNetCoreProjectInfo } from '../../utils/netCoreUtils';
+import * as vscode from 'vscode';
+import { getBlazorManifestInfo } from '../../utils/netCoreUtils';
import { pathNormalize } from '../../utils/pathNormalize';
import { PlatformOS } from '../../utils/platform';
import { DockerContainerVolume } from '../DockerRunTaskDefinitionBase';
import { DockerRunTaskDefinition } from "../DockerRunTaskProvider";
import { DockerRunTaskContext } from "../TaskHelper";
-interface ContentRootAttributes {
- BasePath: string;
- Path: string;
-}
-
-interface ContentRoot {
- $: ContentRootAttributes;
-}
-
-interface StaticWebAssets {
- ContentRoot: ContentRoot[];
-}
-
-interface XmlManifest {
- StaticWebAssets: StaticWebAssets;
-}
-
interface JsonManifest {
ContentRoots: string[];
}
export async function updateBlazorManifest(context: DockerRunTaskContext, runDefinition: DockerRunTaskDefinition): Promise {
- const contents = await getNetCoreProjectInfo('GetBlazorManifestLocations', runDefinition.netCore.appProject);
-
- if (contents.length < 2) {
- throw new Error(l10n.t('Unable to determine Blazor manifest locations from output file.'));
- }
-
- await transformBlazorManifest(context, contents[0].trim(), contents[1].trim(), runDefinition.dockerRun.volumes, runDefinition.dockerRun.os);
+ const blazorInfo = await getBlazorManifestInfo(runDefinition.netCore.appProject);
+ await transformBlazorManifest(context, blazorInfo.inputManifestPath, blazorInfo.outputManifestPath, runDefinition.dockerRun.volumes, runDefinition.dockerRun.os);
}
async function transformBlazorManifest(context: DockerRunTaskContext, inputManifest: string, outputManifest: string, volumes: DockerContainerVolume[], os: PlatformOS): Promise {
@@ -58,20 +34,16 @@ async function transformBlazorManifest(context: DockerRunTaskContext, inputManif
os = os || 'Linux';
- context.terminal.writeOutputLine(l10n.t('Attempting to containerize Blazor static web assets manifest...'));
+ context.terminal.writeOutputLine(vscode.l10n.t('Attempting to containerize Blazor static web assets manifest...'));
- if (path.extname(inputManifest) === '.json') {
- await transformJsonBlazorManifest(inputManifest, outputManifest, volumes, os);
- } else {
- await transformXmlBlazorManifest(inputManifest, outputManifest, volumes, os);
- }
+ await transformJsonBlazorManifest(inputManifest, outputManifest, volumes, os);
}
async function transformJsonBlazorManifest(inputManifest: string, outputManifest: string, volumes: DockerContainerVolume[], os: PlatformOS): Promise {
const manifest = await fse.readJson(inputManifest);
if (!manifest?.ContentRoots) {
- throw new Error(l10n.t('Failed to parse Blazor static web assets manifest.'));
+ throw new Error(vscode.l10n.t('Failed to parse Blazor static web assets manifest.'));
}
if (!Array.isArray(manifest.ContentRoots)) {
@@ -87,33 +59,6 @@ async function transformJsonBlazorManifest(inputManifest: string, outputManifest
await fse.utimes(outputManifest, 0, 0);
}
-async function transformXmlBlazorManifest(inputManifest: string, outputManifest: string, volumes: DockerContainerVolume[], os: PlatformOS): Promise {
- const contents = (await fse.readFile(inputManifest)).toString();
- const manifest = await xml2js.parseStringPromise(contents);
-
- if (!manifest?.StaticWebAssets) {
- throw new Error(l10n.t('Failed to parse Blazor static web assets manifest.'));
- }
-
- if (!Array.isArray(manifest.StaticWebAssets.ContentRoot)) {
- return;
- }
-
- for (const contentRoot of manifest.StaticWebAssets.ContentRoot) {
- if (contentRoot && contentRoot.$) {
- contentRoot.$.Path = tryContainerizePath(contentRoot.$.Path, volumes, os);
- }
- }
-
- const outputContents = (new xml2js.Builder()).buildObject(manifest);
-
- // Write out a new manifest
- await fse.writeFile(outputManifest, outputContents);
-
- // Set the mtime to 1970 so that next time .NET builds, it will overwrite the output file
- await fse.utimes(outputManifest, 0, 0);
-}
-
function tryContainerizePath(oldPath: string, volumes: DockerContainerVolume[], os: PlatformOS): string {
const matchingVolume: DockerContainerVolume = volumes.find(v => oldPath.toLowerCase().startsWith(v.localPath.toLowerCase()));
diff --git a/src/utils/netCoreUtils.ts b/src/utils/netCoreUtils.ts
index b4329517..07985a07 100644
--- a/src/utils/netCoreUtils.ts
+++ b/src/utils/netCoreUtils.ts
@@ -4,49 +4,123 @@
*--------------------------------------------------------------------------------------------*/
import { parseError } from '@microsoft/vscode-azext-utils';
-import { CommandLineArgs, composeArgs, withArg, withNamedArg, withQuotedArg } from '@microsoft/vscode-processutils';
-import * as fse from 'fs-extra';
+import { CommandLineArgs, composeArgs, withArg, withQuotedArg } from '@microsoft/vscode-processutils';
import * as path from 'path';
-import { l10n } from 'vscode';
-import { ext } from '../extensionVariables';
+import * as vscode from 'vscode';
+import { z } from 'zod';
import { execAsync } from './execAsync';
-import { getTempFileName } from './osUtils';
-export async function getNetCoreProjectInfo(target: 'GetBlazorManifestLocations' | 'GetProjectProperties', project: string, additionalProperties?: CommandLineArgs): Promise {
- const targetsFile = path.join(ext.context.asAbsolutePath('resources'), 'netCore', `${target}.targets`);
- const outputFile = getTempFileName();
+interface NetCoreCommonProjectInfo {
+ assemblyName: string;
+ targetFrameworks: string[];
+ assemblyRelativeOutputPath: string;
+}
+
+interface NetCoreContainerProjectInfo {
+ enableSdkContainerSupport: true;
+ assemblyContainerPath: string;
+ imageName: string;
+}
+
+interface NetCoreNonContainerProjectInfo {
+ enableSdkContainerSupport: false;
+ assemblyContainerPath: never;
+ imageName: never;
+}
+
+export type NetCoreProjectInfo = NetCoreCommonProjectInfo & (NetCoreContainerProjectInfo | NetCoreNonContainerProjectInfo);
+const RawNetCoreProjectInfoSchema = z.object({
+ Properties: z
+ .object({
+ AssemblyName: z.string().min(1, vscode.l10n.t('AssemblyName must have a value')),
+ OutputPath: z.string().min(1, vscode.l10n.t('OutputPath must have a value')),
+ TargetFramework: z.string().optional(),
+ TargetFrameworks: z.string().optional(),
+ EnableSdkContainerSupport: z.stringbool().optional(),
+ ContainerWorkingDirectory: z.string().optional(),
+ ContainerRepository: z.string().optional(),
+ })
+ .refine(info => info.TargetFramework || info.TargetFrameworks, vscode.l10n.t('Either TargetFramework or TargetFrameworks must have a value'))
+ .refine(info => !info.EnableSdkContainerSupport || (info.ContainerWorkingDirectory && info.ContainerRepository), vscode.l10n.t('ContainerWorkingDirectory and ContainerRepository must have values when EnableSdkContainerSupport is true'))
+});
+
+export async function getNetCoreProjectInfo(project: string, additionalProperties?: CommandLineArgs): Promise {
const args = composeArgs(
- withArg('build'),
- withArg('/r:false'),
- withArg(`/t:${target}`), // Target name doesn't need quoting
- withNamedArg('/p:CustomAfterMicrosoftCommonTargets', `"${targetsFile}"`, { assignValue: true }), // We have to pre-quote the file paths because we cannot simultaneously use `assignValue` and `shouldQuote`
- withNamedArg('/p:CustomAfterMicrosoftCommonCrossTargetingTargets', `"${targetsFile}"`, { assignValue: true }),
- withNamedArg('/p:InfoOutputPath', `"${outputFile}"`, { assignValue: true }),
+ withArg('build', '--no-restore'),
+ withArg('-target:ComputeContainerConfig'),
+ withArg('-getProperty:AssemblyName,TargetFramework,TargetFrameworks,OutputPath,EnableSdkContainerSupport,ContainerWorkingDirectory,ContainerRepository'),
withArg(...(additionalProperties ?? [])),
withQuotedArg(project),
)();
try {
- try {
- await execAsync('dotnet', args, { timeout: 20000 });
- } catch (err) {
- const error = parseError(err);
- throw new Error(l10n.t('Unable to determine project information for target \'{0}\' on project \'{1}\' {2}', target, project, error.message));
- }
+ const { stdout } = await execAsync('dotnet', args, { timeout: 20000 });
+ const rawInfo = RawNetCoreProjectInfoSchema.parse(JSON.parse(stdout));
- if (await fse.pathExists(outputFile)) {
- const contents = await fse.readFile(outputFile, 'utf-8');
+ const assemblyName = `${rawInfo.Properties.AssemblyName}.dll`;
+ const targetFrameworks = rawInfo.Properties.TargetFrameworks ?
+ rawInfo.Properties.TargetFrameworks.split(';') : [rawInfo.Properties.TargetFramework!]; // eslint-disable-line @typescript-eslint/no-non-null-assertion -- we know it must be one of the two due to the schema refinement
- if (contents) {
- return contents.split(/\r?\n/ig);
- }
- }
+ const commonInfo = {
+ assemblyName: assemblyName,
+ targetFrameworks: targetFrameworks,
+ assemblyRelativeOutputPath: path.join(rawInfo.Properties.OutputPath, assemblyName),
+ };
- throw new Error(l10n.t('Unable to determine project information for target \'{0}\' on project \'{1}\'', target, project));
- } finally {
- if (await fse.pathExists(outputFile)) {
- await fse.unlink(outputFile);
+ if (rawInfo.Properties.EnableSdkContainerSupport) {
+ return {
+ ...commonInfo,
+ enableSdkContainerSupport: true,
+ assemblyContainerPath: path.posix.join(rawInfo.Properties.ContainerWorkingDirectory!, assemblyName), // eslint-disable-line @typescript-eslint/no-non-null-assertion -- we know this is set if EnableSdkContainerSupport is true due to the schema refinement
+ imageName: rawInfo.Properties.ContainerRepository!, // eslint-disable-line @typescript-eslint/no-non-null-assertion -- we know this is set if EnableSdkContainerSupport is true due to the schema refinement
+ };
+ } else {
+ return {
+ ...commonInfo,
+ enableSdkContainerSupport: false,
+ assemblyContainerPath: undefined as never,
+ imageName: undefined as never,
+ };
}
+ } catch (err) {
+ const error = parseError(err);
+ throw new Error(vscode.l10n.t('Unable to determine project information for project \'{0}\': {1}', project, error.message));
+ }
+}
+
+export interface BlazorManifestInfo {
+ inputManifestPath: string;
+ outputManifestPath: string;
+}
+
+const RawBlazorManifestInfoSchema = z.object({
+ Properties: z.object({
+ MSBuildProjectDirectory: z.string().min(1, vscode.l10n.t('MSBuildProjectDirectory must have a value')),
+ StaticWebAssetDevelopmentManifestPath: z.string().min(1, vscode.l10n.t('StaticWebAssetDevelopmentManifestPath must have a value')),
+ OutputPath: z.string().min(1, vscode.l10n.t('OutputPath must have a value')),
+ TargetName: z.string().min(1, vscode.l10n.t('TargetName must have a value')),
+ })
+});
+
+export async function getBlazorManifestInfo(project: string): Promise {
+ const args = composeArgs(
+ withArg('build', '--no-restore'),
+ withArg('-target:ResolveStaticWebAssetsConfiguration'),
+ withArg('-getProperty:MSBuildProjectDirectory,StaticWebAssetDevelopmentManifestPath,OutputPath,TargetName'),
+ withQuotedArg(project),
+ )();
+
+ try {
+ const { stdout } = await execAsync('dotnet', args, { timeout: 20000 });
+ const rawInfo = RawBlazorManifestInfoSchema.parse(JSON.parse(stdout));
+
+ return {
+ inputManifestPath: path.join(rawInfo.Properties.MSBuildProjectDirectory, rawInfo.Properties.StaticWebAssetDevelopmentManifestPath),
+ outputManifestPath: path.join(rawInfo.Properties.MSBuildProjectDirectory, rawInfo.Properties.OutputPath, `${rawInfo.Properties.TargetName}.staticwebassets.runtime.json`),
+ };
+ } catch (err) {
+ const error = parseError(err);
+ throw new Error(vscode.l10n.t('Unable to determine Blazor project information for project \'{0}\': {1}', project, error.message));
}
}