Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET SDK Build Prompt For Project File Every Time #3993

Merged
merged 16 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,18 @@
"required": [
"appProject"
]
},
"dockerRun": {
"description": "%vscode-docker.tasks.dotnet-container-sdk.dockerRun.description%",
"properties": {
"containerName": {
"type": "string",
"description": "%vscode-docker.tasks.dotnet-container-sdk.dockerRun.containerName%"
}
},
"required": [
"containerName"
]
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@
"vscode-docker.tasks.docker-compose.dockerCompose.envFile.description": "File of environment variables read in and applied to the Docker containers.",
"vscode-docker.tasks.docker-compose.dockerCompose.files.description": "The docker-compose files to include, in order.",
"vscode-docker.tasks.docker-compose.dockerCompose.projectName.description": "Alternate project name to use when naming and labeling Docker objects. If using an alternate project name when composing up, the same project name must be specified when composing down.",
"vscode-docker.tasks.dotnet-container-sdk.dockerRun.description": "Options for running the Docker container used for debugging.",
"vscode-docker.tasks.dotnet-container-sdk.dockerRun.containerName": "Name of the container used for debugging.",
"vscode-docker.config.docker.promptForRegistryWhenPushingImages": "Prompt for registry selection if the image is not explicitly tagged.",
"vscode-docker.config.template.build.template": "The command template.",
"vscode-docker.config.template.build.label": "The label displayed to the user.",
Expand Down
7 changes: 6 additions & 1 deletion resources/netCore/GetProjectProperties.targets
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
</PropertyGroup>

<Target Name="GetProjectProperties" DependsOnTargets="$(GetProjectPropertiesDependsOn)">
<PropertyGroup>
<InferImageName>$(ContainerRepository)</InferImageName>
<InferImageName Condition=" '$(InferImageName)' == '' ">$(ContainerImageName)</InferImageName>
</PropertyGroup>
<WriteLinesToFile
File="$(InfoOutputPath)"
Lines="$(AssemblyName).dll
$(TargetFramework)$(TargetFrameworks.Split(';')[0])
$(OutputPath)$(AssemblyName).dll
$(ContainerWorkingDirectory)/$(AssemblyName).dll
$(SDKContainerSupportEnabled)"
$(SDKContainerSupportEnabled)
$(InferImageName)"
Overwrite="True" />
</Target>
</Project>
134 changes: 70 additions & 64 deletions src/debugging/netSdk/NetSdkDebugHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,33 @@

import { IActionContext } from "@microsoft/vscode-azext-utils";
import * as path from "path";
import { Uri, WorkspaceFolder, commands, l10n, tasks } from "vscode";
import { WorkspaceFolder, commands, l10n, tasks } from "vscode";
import { ext } from "../../extensionVariables";
import { NetChooseBuildTypeContext, netContainerBuild } from "../../scaffolding/wizard/net/NetContainerBuild";
import { AllNetContainerBuildOptions, NetContainerBuildOptionsKey } from "../../scaffolding/wizard/net/NetSdkChooseBuildStep";
import { getDefaultContainerName } from "../../tasks/TaskHelper";
import { netSdkRunTaskProvider } from "../../tasks/netSdk/NetSdkRunTaskProvider";
import { getContainerNameWithTag } from "../../tasks/TaskHelper";
import { NetSdkRunTaskDefinition, netSdkRunTaskProvider } from "../../tasks/netSdk/NetSdkRunTaskProvider";
import { normalizeArchitectureToRidArchitecture, normalizeOsToRidOs } from "../../tasks/netSdk/netSdkTaskUtils";
import { getNetCoreProjectInfo } from "../../utils/netCoreUtils";
import { getDockerOSType } from "../../utils/osUtils";
import { PlatformOS } from "../../utils/platform";
import { quickPickProjectFileItem } from "../../utils/quickPickFile";
import { unresolveWorkspaceFolder } from "../../utils/resolveVariables";
import { DockerDebugContext, DockerDebugScaffoldContext } from "../DebugHelper";
import { DockerDebugContext, DockerDebugScaffoldContext, ResolvedDebugConfiguration } from "../DebugHelper";
import { DockerDebugConfiguration } from "../DockerDebugConfigurationProvider";
import { NetCoreDebugHelper, NetCoreDebugScaffoldingOptions } from "../netcore/NetCoreDebugHelper";
import { NetCoreDebugHelper, NetCoreDebugScaffoldingOptions, NetCoreProjectProperties } from "../netcore/NetCoreDebugHelper";

export class NetSdkDebugHelper extends NetCoreDebugHelper {
export interface NetSdkProjectProperties extends NetCoreProjectProperties {
alexyaang marked this conversation as resolved.
Show resolved Hide resolved
containerWorkingDirectory: string;
isSdkContainerSupportEnabled: boolean;
containerName: string;
}

private static projPath: string | undefined;
export class NetSdkDebugHelper extends NetCoreDebugHelper {

public async provideDebugConfigurations(context: DockerDebugScaffoldContext, options?: NetCoreDebugScaffoldingOptions): Promise<DockerDebugConfiguration[]> {
protected projectProperties: NetSdkProjectProperties | undefined;

public override async provideDebugConfigurations(context: DockerDebugScaffoldContext, options?: NetCoreDebugScaffoldingOptions): Promise<DockerDebugConfiguration[]> {
const configurations: DockerDebugConfiguration[] = [];

const netCoreBuildContext: NetChooseBuildTypeContext = {
Expand All @@ -37,7 +42,7 @@ export class NetSdkDebugHelper extends NetCoreDebugHelper {

await netContainerBuild(netCoreBuildContext);
if (netCoreBuildContext?.containerBuildOptions === AllNetContainerBuildOptions[1]) {
const appProjectAbsolutePath = await this.inferProjPath(context.actionContext, context.folder);
const appProjectAbsolutePath = options?.appProject || await this.inferProjPath(context.actionContext, context.folder);

configurations.push({
name: 'Docker .NET Container SDK Launch',
Expand All @@ -55,67 +60,50 @@ export class NetSdkDebugHelper extends NetCoreDebugHelper {
return configurations;
}

public async afterResolveDebugConfiguration(context: DockerDebugContext, debugConfiguration: DockerDebugConfiguration): Promise<void> {
const { task, promise } = netSdkRunTaskProvider.createNetSdkRunTask(
{
netCore: {
appProject: await this.inferProjPath(context.actionContext, context.folder),
}
}
);

await tasks.executeTask(task);

public override async resolveDebugConfiguration(context: DockerDebugContext, debugConfiguration: DockerDebugConfiguration): Promise<ResolvedDebugConfiguration | undefined> {
try {
await promise;
return await super.resolveDebugConfiguration(context, debugConfiguration);
} catch (error) {
await NetSdkDebugHelper.clearWorkspaceState();
await ext.context.workspaceState.update(NetContainerBuildOptionsKey, '');
throw error;
}
}

public static async clearWorkspaceState(): Promise<void> {
await ext.context.workspaceState.update(NetContainerBuildOptionsKey, ''); // clear the workspace state
NetSdkDebugHelper.projPath = undefined; // clear the static projPath variable
return Promise.resolve();
public async afterResolveDebugConfiguration(context: DockerDebugContext, debugConfiguration: DockerDebugConfiguration): Promise<void> {
const projectInfo = await this.getProjectProperties(debugConfiguration);
const runDefinition: Omit<NetSdkRunTaskDefinition, "type"> = {
netCore: {
appProject: debugConfiguration?.netCore?.appProject || await this.inferProjPath(context.actionContext, context.folder),
},
dockerRun: {
containerName: projectInfo.containerName
}
};

const { task, promise } = netSdkRunTaskProvider.createNetSdkRunTask(runDefinition);
await tasks.executeTask(task);
await promise;
}

protected override async inferAppOutput(debugConfiguration: DockerDebugConfiguration): Promise<string> {
const ridOS = await normalizeOsToRidOs();
const ridArchitecture = await normalizeArchitectureToRidArchitecture();
const additionalProperties = `/p:ContainerRuntimeIdentifier="${ridOS}-${ridArchitecture}"`;
const projectInfo = await this.getProjectProperties(debugConfiguration);

let projectInfo: string[];
try {
projectInfo = await getNetCoreProjectInfo('GetProjectProperties', debugConfiguration.netCore?.appProject, additionalProperties);
} catch (error) {
await NetSdkDebugHelper.clearWorkspaceState();
throw error;
}

if (projectInfo.length >= 5) { // if .NET has support for SDK Build
// fifth is whether .NET Web apps supports SDK Containers
if (projectInfo[4] === 'true') {
return ridOS === 'win' // fourth is output path
? path.win32.normalize(projectInfo[3])
: path.posix.normalize(projectInfo[3]);
} else {
await NetSdkDebugHelper.clearWorkspaceState();
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."));
}
// fifth is whether .NET Web apps supports SDK Containers
if (projectInfo.isSdkContainerSupportEnabled) {
return await getDockerOSType() === 'windows' // fourth is output path
? path.win32.normalize(projectInfo.containerWorkingDirectory)
: path.posix.normalize(projectInfo.containerWorkingDirectory);
} else {
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."));
}

await NetSdkDebugHelper.clearWorkspaceState();
throw new Error(l10n.t('Unable to determine assembly output path.'));
}

protected override async loadExternalInfo(context: DockerDebugContext, debugConfiguration: DockerDebugConfiguration): Promise<{ configureSsl: boolean, containerName: string, platformOS: PlatformOS }> {
NetSdkDebugHelper.projPath = debugConfiguration.netCore.appProject;

const associatedTask = context.runDefinition;
const projectInfo = await this.getProjectProperties(debugConfiguration);
return {
configureSsl: !!(associatedTask?.netCore?.configureSsl),
containerName: this.inferDotNetSdkContainerName(debugConfiguration),
containerName: getContainerNameWithTag(projectInfo.containerName, "dev"),
platformOS: await getDockerOSType() === "windows" ? 'Windows' : 'Linux',
};
}
Expand All @@ -124,23 +112,41 @@ export class NetSdkDebugHelper extends NetCoreDebugHelper {
return appOutput;
}

private inferDotNetSdkContainerName(debugConfiguration: DockerDebugConfiguration): string {
const projFileUri = Uri.file(path.dirname(debugConfiguration.netCore.appProject));
return getDefaultContainerName(path.basename(projFileUri.fsPath), "dev");
protected override async getProjectProperties(debugConfiguration: DockerDebugConfiguration): Promise<NetSdkProjectProperties> {
if (this.projectProperties) {
return this.projectProperties;
}

const ridOS = await normalizeOsToRidOs();
const ridArchitecture = await normalizeArchitectureToRidArchitecture();
const additionalProperties = `/p:ContainerRuntimeIdentifier="${ridOS}-${ridArchitecture}"`;

const projectInfo = await getNetCoreProjectInfo('GetProjectProperties', debugConfiguration.netCore?.appProject, additionalProperties);

if (projectInfo.length < 6 || !projectInfo[5]) {
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',
containerName: projectInfo[5],
};

this.projectProperties = projectProperties;
return projectProperties;
}

/**
* @returns the project path stored in the static variable projPath if it exists,
* @returns the project path stored in NetCoreDebugScaffoldingOptions,
* otherwise prompts the user to select a .csproj file and stores the path
* in the static variable projPath
*/
private async inferProjPath(context: IActionContext, folder: WorkspaceFolder): Promise<string> {
if (NetSdkDebugHelper.projPath) {
return NetSdkDebugHelper.projPath;
}

const projFileItem = await quickPickProjectFileItem(context, undefined, folder, 'No project file could be found.');
NetSdkDebugHelper.projPath = projFileItem.absoluteFilePath; // save the path for future use
private async inferProjPath(actionContext: IActionContext, folder: WorkspaceFolder): Promise<string> {
const projFileItem = await quickPickProjectFileItem(actionContext, undefined, folder, 'No project file could be found.');
return projFileItem.absoluteFilePath;
}
}
Expand Down
40 changes: 33 additions & 7 deletions src/debugging/netcore/NetCoreDebugHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ export interface NetCoreDebugScaffoldingOptions {
appProject?: string;
}

export interface NetCoreProjectProperties {
assemblyName: string;
targetFramework: string;
appOutput: string;
}

export class NetCoreDebugHelper implements DebugHelper {

protected projectProperties: NetCoreProjectProperties | undefined;
alexyaang marked this conversation as resolved.
Show resolved Hide resolved

public async provideDebugConfigurations(context: DockerDebugScaffoldContext, options?: NetCoreDebugScaffoldingOptions): Promise<DockerDebugConfiguration[]> {
options = options || {};
options.appProject = options.appProject || await NetCoreTaskHelper.inferAppProject(context); // This method internally checks the user-defined input first
Expand Down Expand Up @@ -178,13 +187,8 @@ export class NetCoreDebugHelper implements DebugHelper {
}

protected async inferAppOutput(debugConfiguration: DockerDebugConfiguration): Promise<string> {
const projectInfo = await getNetCoreProjectInfo('GetProjectProperties', debugConfiguration.netCore?.appProject);

if (projectInfo.length < 3) {
throw new Error(l10n.t('Unable to determine assembly output path.'));
}

return projectInfo[2]; // First line is assembly name, second is target framework, third+ are output path(s)
const projectProperties = await this.getProjectProperties(debugConfiguration);
return projectProperties.appOutput;
}

protected inferAppContainerOutput(appOutput: string, platformOS: PlatformOS): string {
Expand All @@ -205,6 +209,28 @@ export class NetCoreDebugHelper implements DebugHelper {
};
}

protected async getProjectProperties(debugConfiguration: DockerDebugConfiguration): Promise<NetCoreProjectProperties> {
if (this.projectProperties) {
return this.projectProperties;
}

const projectInfo = await getNetCoreProjectInfo('GetProjectProperties', 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]
};

this.projectProperties = projectProperties;
return projectProperties;
}

private async acquireDebuggers(platformOS: PlatformOS): Promise<void> {
await window.withProgress(
{
Expand Down
6 changes: 5 additions & 1 deletion src/tasks/TaskHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,11 @@ export function getDefaultImageName(nameHint: string, tag?: 'dev' | 'latest'): s

export function getDefaultContainerName(nameHint: string, tag?: 'dev' | 'latest'): string {
tag = tag || 'dev';
return `${getValidImageName(nameHint)}-${tag}`;
return getContainerNameWithTag(getValidImageName(nameHint), tag);
}

export function getContainerNameWithTag(name: string, tag: string): string {
return `${name}-${tag}`;
}

export async function recursiveFindTaskByType(allTasks: TaskDefinitionBase[], type: string, node: DebugConfigurationBase | TaskDefinitionBase): Promise<TaskDefinitionBase | undefined> {
Expand Down
9 changes: 4 additions & 5 deletions src/tasks/netSdk/NetSdkRunTaskProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { NetSdkRunTaskType, getNetSdkBuildCommand, getNetSdkRunCommand } from '.

const NetSdkDebugTaskName = 'debug';

export type NetSdkRunTask = NetCoreRunTaskDefinition;
export type NetSdkRunTaskDefinition = NetCoreRunTaskDefinition;

export class NetSdkRunTaskProvider extends DockerTaskProvider {

Expand All @@ -28,10 +28,9 @@ export class NetSdkRunTaskProvider extends DockerTaskProvider {
const projectPath = task.definition.netCore?.appProject;
const isProjectWebApp = await NetCoreTaskHelper.isWebApp(projectPath);
const projectFolderPath = path.dirname(projectPath);
const projectFolderName = path.basename(projectFolderPath);

// use dotnet to build the image
const buildCommand = await getNetSdkBuildCommand(isProjectWebApp, projectFolderName);
const buildCommand = await getNetSdkBuildCommand(isProjectWebApp, task.definition.dockerRun.containerName);
await context.terminal.execAsyncInTerminal(
buildCommand,
{
Expand All @@ -42,7 +41,7 @@ export class NetSdkRunTaskProvider extends DockerTaskProvider {
);

// use docker run to run the image
const runCommand = await getNetSdkRunCommand(isProjectWebApp, projectFolderName);
const runCommand = await getNetSdkRunCommand(isProjectWebApp, task.definition.dockerRun.containerName);
await context.terminal.execAsyncInTerminal(
runCommand,
{
Expand All @@ -55,7 +54,7 @@ export class NetSdkRunTaskProvider extends DockerTaskProvider {
return Promise.resolve();
}

public createNetSdkRunTask(options?: Omit<NetSdkRunTask, "type">): { task: Task, promise: Promise<number> } {
public createNetSdkRunTask(options?: Omit<NetSdkRunTaskDefinition, "type">): { task: Task, promise: Promise<number> } {
let task: Task;
const definition = {
...options,
Expand Down
Loading