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

Support .NET SDK Container Build #3960

Merged
merged 47 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
3b319b0
Experimental Base Structure
alexyaang May 10, 2023
794d867
register new task provider
alexyaang May 11, 2023
127f053
added build options for DotnetSdk
alexyaang May 11, 2023
09236a7
removed redundent fields in NetCoreSdkBuildOptions
alexyaang May 11, 2023
dbfc62a
added json schema
alexyaang May 12, 2023
b9b9607
basci working implementation of task provider
alexyaang May 12, 2023
d289b42
reversed unnecessary commits
alexyaang May 15, 2023
6982df7
added dotnet specific util methods
alexyaang May 15, 2023
4cd942e
fixed dotnet CLI flags
alexyaang May 15, 2023
26058d3
made code more organized
alexyaang May 15, 2023
9a148bc
organiized files into new folder
alexyaang May 15, 2023
440cb49
added helper class
alexyaang May 17, 2023
ee21b06
combined build & run into one task
alexyaang May 17, 2023
7d62777
adddded working version of run command
alexyaang May 17, 2023
78f1374
added mounts to run arg
alexyaang May 18, 2023
9832b7b
added ambient launch task
alexyaang May 24, 2023
b2712c8
working version of ambient launch task
alexyaang May 24, 2023
86cc95f
added prompt for docker debugger on csharp languag
alexyaang May 25, 2023
796c1ad
stored user image build choices
alexyaang May 26, 2023
068fcc1
added auto .csproj selection & refactored code
alexyaang May 30, 2023
a054ac9
added debug helper
alexyaang Jun 1, 2023
b113ce7
small tweaks to handle null cases
alexyaang Jun 2, 2023
edf1338
add appOutput override to fix dotnet project name bug
alexyaang Jun 2, 2023
8920a8f
fixed image not running if foldername has special chars
alexyaang Jun 5, 2023
def78b9
changed task name and altered schema
alexyaang Jun 5, 2023
8b5fedd
determine helper from prelaunchtask instead of `platform`
alexyaang Jun 5, 2023
132f252
move helper methods to taskhelper
alexyaang Jun 5, 2023
bcb9d9d
cleaned up unused comments & method
alexyaang Jun 5, 2023
46df754
made NetTaskHelper more readable
alexyaang Jun 5, 2023
0afe708
changed momento name to more detailed
alexyaang Jun 5, 2023
e29212c
changed targets to find SDK Container path
alexyaang Jun 6, 2023
75f491d
added method to check for sdk container builds
alexyaang Jun 6, 2023
00af811
set git ignore case to false
alexyaang Jun 6, 2023
d69f933
Delete netContainerBuild.ts
alexyaang Jun 6, 2023
aff5e9e
added error checking
alexyaang Jun 6, 2023
17142be
added error checking
alexyaang Jun 6, 2023
583a4d5
Changed const to pascal case & use pre-defined options
alexyaang Jun 7, 2023
2d1defe
removed redundent duplication method
alexyaang Jun 7, 2023
f96921a
removed util class that is unnecessary
alexyaang Jun 8, 2023
b8a3755
Merge branch 'main' into yangalex/ambientSdkTask
alexyaang Jun 8, 2023
3d02acd
added expose field inside RunContainerCommandOptions
alexyaang Jun 13, 2023
e9f4626
converted netSdkHelper class into methods only
alexyaang Jun 13, 2023
63f86e8
minor change to make debugger mount more flexible
alexyaang Jun 13, 2023
f49d517
simplified logic for mounting debugger
alexyaang Jun 13, 2023
2747b83
changed naming of expose ports
alexyaang Jun 14, 2023
9d4e45e
updated if condition for checking for empty debug config
alexyaang Jun 14, 2023
fd4f75b
renamed helper methods in sdk task utils
alexyaang Jun 15, 2023
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
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,11 @@
{
"type": "docker",
"label": "Docker: Debug in Container",
"languages": [
"csharp",
"razor",
"aspnetcorerazor"
],
"configurationAttributes": {
"launch": {
"properties": {
Expand Down Expand Up @@ -1500,6 +1505,9 @@
"required": [
"dockerCompose"
]
},
{
"type": "dotnet-container-sdk"
}
],
"languages": [
Expand Down
11 changes: 8 additions & 3 deletions resources/netCore/GetProjectProperties.targets
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<Project>
<!-- All the relevant info is in root-level PropertyGroups, so there are no dependent targets to make this work -->
<Target Name="GetProjectProperties">
<PropertyGroup>
<GetProjectPropertiesDependsOn Condition=" '$(SDKContainerSupportEnabled)' == 'true' ">$(GetProjectPropertiesDependsOn);ComputeContainerConfig;</GetProjectPropertiesDependsOn>
</PropertyGroup>

<Target Name="GetProjectProperties" DependsOnTargets="$(GetProjectPropertiesDependsOn)">
<WriteLinesToFile
File="$(InfoOutputPath)"
Lines="$(AssemblyName).dll
$(TargetFramework)$(TargetFrameworks.Split(';')[0])
$(OutputPath)$(AssemblyName).dll"
$(OutputPath)$(AssemblyName).dll
$(ContainerWorkingDirectory)/$(AssemblyName).dll
$(SDKContainerSupportEnabled)"
Overwrite="True" />
</Target>
</Project>
2 changes: 2 additions & 0 deletions src/debugging/DebugHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DockerDebugConfiguration, DockerDebugConfigurationProvider } from './Do
import { DockerPlatform } from './DockerPlatformHelper';
import { registerServerReadyAction } from './DockerServerReadyAction';
import { netCoreDebugHelper } from './netcore/NetCoreDebugHelper';
import { netSdkDebugHelper } from './netSdk/NetSdkDebugHelper';
import { nodeDebugHelper } from './node/NodeDebugHelper';
import { pythonDebugHelper } from './python/PythonDebugHelper';

Expand Down Expand Up @@ -51,6 +52,7 @@ export function registerDebugProvider(ctx: ExtensionContext): void {
netCore: netCoreDebugHelper,
node: nodeDebugHelper,
python: pythonDebugHelper,
netSdk: netSdkDebugHelper
}
)
)
Expand Down
73 changes: 52 additions & 21 deletions src/debugging/DockerDebugConfigurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { callWithTelemetryAndErrorHandling, IActionContext, registerEvent } from '@microsoft/vscode-azext-utils';
import { CancellationToken, commands, debug, DebugConfiguration, DebugConfigurationProvider, DebugSession, l10n, MessageItem, ProviderResult, window, workspace, WorkspaceFolder } from 'vscode';
import { DockerOrchestration } from '../constants';
import { callWithTelemetryAndErrorHandling, IActionContext, registerEvent, UserCancelledError } from '@microsoft/vscode-azext-utils';
import { CancellationToken, commands, debug, DebugConfiguration, DebugConfigurationProvider, DebugSession, l10n, ProviderResult, workspace, WorkspaceFolder } from 'vscode';
import { CSPROJ_GLOB_PATTERN, DockerOrchestration } from '../constants';
import { ext } from '../extensionVariables';
import { getAssociatedDockerRunTask } from '../tasks/TaskHelper';
import { resolveFilesOfPattern } from '../utils/quickPickFile';
import { DebugHelper, DockerDebugContext, ResolvedDebugConfiguration } from './DebugHelper';
import { DockerPlatform, getPlatform } from './DockerPlatformHelper';
import { NetCoreDockerDebugConfiguration } from './netcore/NetCoreDebugHelper';
import { netSdkDebugHelper } from './netSdk/NetSdkDebugHelper';
import { NodeDockerDebugConfiguration } from './node/NodeDebugHelper';

export interface DockerDebugConfiguration extends NetCoreDockerDebugConfiguration, NodeDockerDebugConfiguration {
Expand Down Expand Up @@ -42,21 +44,14 @@ export class DockerDebugConfigurationProvider implements DebugConfigurationProvi
}

public provideDebugConfigurations(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult<DebugConfiguration[]> {
const add: MessageItem = { title: l10n.t('Add Docker Files') };

// Prompt them to add Docker files since they probably haven't
/* eslint-disable-next-line @typescript-eslint/no-floating-promises */
window.showErrorMessage(
l10n.t('To debug in a Docker container on supported platforms, use the command "Docker: Add Docker Files to Workspace", or click "Add Docker Files".'),
...[add])
.then((result) => {
if (result === add) {
/* eslint-disable-next-line @typescript-eslint/no-floating-promises */
commands.executeCommand('vscode-docker.configure');
}
});

return [];
return callWithTelemetryAndErrorHandling(
'provideDebugConfigurations',
async (actionContext: IActionContext) => {
return this.handleEmptyDebugConfig(folder, actionContext);
}
);

}

public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DockerDebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration | undefined> {
Expand All @@ -75,10 +70,15 @@ export class DockerDebugConfigurationProvider implements DebugConfigurationProvi
}
}

if (debugConfiguration.type === undefined) {
// If type is undefined, they may be doing F5 without creating any real launch.json, which won't work
// VSCode subsequently will call provideDebugConfigurations which will show an error message
return null;
if (Object.keys(debugConfiguration).length === 0) {

const newlyCreatedDebugConfig = await this.handleEmptyDebugConfig(folder, actionContext);
// if there is no debugConfiguration, we should return undefined to exit the debug session
if (newlyCreatedDebugConfig.length === 0 || !newlyCreatedDebugConfig[0]) {
return undefined;
}

debugConfiguration = newlyCreatedDebugConfig[0];
}

if (!debugConfiguration.request) {
Expand Down Expand Up @@ -170,4 +170,35 @@ export class DockerDebugConfigurationProvider implements DebugConfigurationProvi
}
}
}

/**
* If the user has an empty debug launch.json, then we will:
* 1. check if it's a .NET Core project, if so, we will provide .NET Core debug configurations
* 2. otherwise, we will scaffold docker files
*/
private async handleEmptyDebugConfig(folder: WorkspaceFolder, actionContext: IActionContext): Promise<DockerDebugConfiguration[]> {

// NOTE: We can not determine the language from `DockerDebugContext`, so we need to check the
// type of files inside the folder here to determine the language.

// check if it's a .NET Core project
const csProjUris = await resolveFilesOfPattern(folder, [CSPROJ_GLOB_PATTERN]);
if (csProjUris) {
return await netSdkDebugHelper.provideDebugConfigurations(
{
actionContext,
dockerfile: undefined,
folder: folder
},
{
appProject: csProjUris[0]?.absoluteFilePath || '',
}
);
} else {
// for now, we scaffold docker files
await commands.executeCommand('vscode-docker.configure');
throw new UserCancelledError();
}
// TODO: (potentially) in the future, we can add more support for ambient tasks for other types of projects
}
}
9 changes: 7 additions & 2 deletions src/debugging/DockerPlatformHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

export type DockerPlatform = 'netCore' | 'node' | 'python';
import { netSdkDebugHelper } from "./netSdk/NetSdkDebugHelper";

export type DockerPlatform = 'netCore' | 'node' | 'python' | 'netSdk';

interface DockerPlatformConfiguration {
platform?: DockerPlatform;
netCore?: unknown;
node?: unknown;
python?: unknown;
preLaunchTask?: string;
}

export function getPlatform<T extends DockerPlatformConfiguration>(configuration: T): DockerPlatform | undefined {
if (configuration.platform === 'netCore' || configuration.netCore !== undefined) {
if (netSdkDebugHelper.isDotNetSdkBuild(configuration?.preLaunchTask) && configuration.netCore !== undefined) {
return 'netSdk';
} else if (configuration.platform === 'netCore' || configuration.netCore !== undefined) {
return 'netCore';
} else if (configuration.platform === 'node' || configuration.node !== undefined) {
return 'node';
Expand Down
52 changes: 52 additions & 0 deletions src/debugging/netSdk/NetSdkDebugHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { commands } from "vscode";
import { NetChooseBuildTypeContext, netContainerBuild } from "../../scaffolding/wizard/net/NetContainerBuild";
import { AllNetContainerBuildOptions } from "../../scaffolding/wizard/net/NetSdkChooseBuildStep";
import { unresolveWorkspaceFolder } from "../../utils/resolveVariables";
import { DockerDebugScaffoldContext } from "../DebugHelper";
import { DockerDebugConfiguration } from "../DockerDebugConfigurationProvider";
import { NetCoreDebugHelper, NetCoreDebugScaffoldingOptions } from "../netcore/NetCoreDebugHelper";

const NetSdkTaskFullSymbol = 'dotnet-container-sdk: debug';
export class NetSdkDebugHelper extends NetCoreDebugHelper {

public async provideDebugConfigurations(context: DockerDebugScaffoldContext, options?: NetCoreDebugScaffoldingOptions): Promise<DockerDebugConfiguration[]> {

const configurations: DockerDebugConfiguration[] = [];

const netCoreBuildContext: NetChooseBuildTypeContext = {
...context.actionContext,
scaffoldType: 'debugging',
workspaceFolder: context.folder,
};

await netContainerBuild(netCoreBuildContext);

if (netCoreBuildContext?.containerBuildOptions === AllNetContainerBuildOptions[1]) {
configurations.push({
name: 'Docker .NET Container SDK Launch',
type: 'docker',
request: 'launch',
preLaunchTask: NetSdkTaskFullSymbol,
netCore: {
appProject: unresolveWorkspaceFolder(options.appProject, context.folder),
},
});
} else {
await commands.executeCommand('vscode-docker.configure');
}

return configurations;
}

/**
* Checks if the launch task is using the .NET SDK Container build
* @param preLaunchTask
* @returns true if the launch task is using the .NET SDK Container build
* false otherwise
*/
public isDotNetSdkBuild(preLaunchTask: string): boolean {
return preLaunchTask === NetSdkTaskFullSymbol;
}
}

export const netSdkDebugHelper = new NetSdkDebugHelper();
22 changes: 18 additions & 4 deletions src/debugging/netcore/NetCoreDebugHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as path from 'path';
import { DebugConfiguration, MessageItem, ProgressLocation, l10n, window } from 'vscode';
import { ext } from '../../extensionVariables';
import { CommandLineArgs, ContainerOS, VoidCommandResponse, composeArgs, withArg, withQuotedArg } from '../../runtimes/docker';
import { NetContainerBuildOptionsKey } from '../../scaffolding/wizard/net/NetSdkChooseBuildStep';
import { NetCoreTaskHelper, NetCoreTaskOptions } from '../../tasks/netcore/NetCoreTaskHelper';
import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem';
import { getNetCoreProjectInfo } from '../../utils/netCoreUtils';
Expand All @@ -18,6 +19,7 @@ import { PlatformOS } from '../../utils/platform';
import { unresolveWorkspaceFolder } from '../../utils/resolveVariables';
import { DebugHelper, DockerDebugContext, DockerDebugScaffoldContext, ResolvedDebugConfiguration, inferContainerName, resolveDockerServerReadyAction } from '../DebugHelper';
import { DockerAttachConfiguration, DockerDebugConfiguration } from '../DockerDebugConfigurationProvider';
import { netSdkDebugHelper } from '../netSdk/NetSdkDebugHelper';
import { exportCertificateIfNecessary, getHostSecretsFolders, trustCertificateIfNecessary } from './AspNetSslHelper';
import { VsDbgType, installDebuggersIfNecessary, vsDbgInstallBasePath } from './VsDbgHelper';

Expand Down Expand Up @@ -68,7 +70,7 @@ export class NetCoreDebugHelper implements DebugHelper {
debugConfiguration.netCore.appProject = await NetCoreTaskHelper.inferAppProject(context, debugConfiguration.netCore); // This method internally checks the user-defined input first

const { configureSsl, containerName, platformOS } = await this.loadExternalInfo(context, debugConfiguration);
const appOutput = await this.inferAppOutput(debugConfiguration.netCore);
const appOutput = debugConfiguration.netCore?.appOutput || await this.inferAppOutput(debugConfiguration);
if (context.cancellationToken && context.cancellationToken.isCancellationRequested) {
// inferAppOutput is slow, give a chance to cancel
return undefined;
Expand All @@ -90,7 +92,9 @@ export class NetCoreDebugHelper implements DebugHelper {

const additionalProbingPathsArgs = NetCoreDebugHelper.getAdditionalProbingPathsArgs(platformOS);

const containerAppOutput = NetCoreDebugHelper.getContainerAppOutput(debugConfiguration, appOutput, platformOS);
const containerAppOutput = netSdkDebugHelper.isDotNetSdkBuild(debugConfiguration?.preLaunchTask)
? appOutput
: NetCoreDebugHelper.getContainerAppOutput(debugConfiguration, appOutput, platformOS);

const dockerServerReadyAction = resolveDockerServerReadyAction(
debugConfiguration,
Expand Down Expand Up @@ -176,12 +180,22 @@ export class NetCoreDebugHelper implements DebugHelper {
};
}

private async inferAppOutput(helperOptions: NetCoreDebugOptions): Promise<string> {
const projectInfo = await getNetCoreProjectInfo('GetProjectProperties', helperOptions.appProject);
private 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.'));
}

if (netSdkDebugHelper.isDotNetSdkBuild(debugConfiguration.preLaunchTask) && projectInfo.length >= 5) { // if .NET has support for SDK Build
if (projectInfo[4] === 'true') { // fifth is whether .NET supports SDK Containers
return projectInfo[3]; // fourth is output path
} else {
await ext.context.workspaceState.update(NetContainerBuildOptionsKey, ''); // clear the workspace state
throw new Error(l10n.t('Your current version of .NET SDK does not support SDK Container build. Please update to a later version of .NET SDK to use this feature.'));
}
}

return projectInfo[2]; // First line is assembly name, second is target framework, third+ are output path(s)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import { withContainerPathArg } from './withContainerPathArg';
import { withDockerAddHostArg } from './withDockerAddHostArg';
import { withDockerBuildArg } from './withDockerBuildArg';
import { withDockerEnvArg } from './withDockerEnvArg';
import { withDockerExposePortsArg } from './withDockerExposePortsArg';
import { withDockerBooleanFilterArg, withDockerFilterArg } from './withDockerFilterArg';
import { withDockerIgnoreSizeArg } from './withDockerIgnoreSizeArg';
import { withDockerJsonFormatArg } from "./withDockerJsonFormatArg";
Expand Down Expand Up @@ -683,6 +684,7 @@ export abstract class DockerClientBase extends ConfigurableClient implements ICo
withDockerEnvArg(options.environmentVariables),
withNamedArg('--env-file', options.environmentFiles),
withNamedArg('--entrypoint', options.entrypoint),
withDockerExposePortsArg(options.exposePorts),
withVerbatimArg(options.customOptions),
withArg(options.imageRef),
typeof options.command === 'string' ? withVerbatimArg(options.command) : withArg(...(toArray(options.command || []))),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { withNamedArg } from "../../utils/commandLineBuilder";

export function withDockerExposePortsArg(ports?: Array<number>) {
return withNamedArg('--expose', (ports || []).map(port => port.toString()), { shouldQuote: false });
}
4 changes: 4 additions & 0 deletions src/runtimes/docker/contracts/ContainerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,10 @@ export type RunContainerCommandOptions = CommonCommandOptions & {
* Optional command to use in starting the container
*/
command?: Array<string> | string;
/**
* Optional expose ports for the container
*/
exposePorts?: Array<number>;
/**
* Additional custom options to pass
*/
Expand Down
31 changes: 31 additions & 0 deletions src/scaffolding/wizard/net/NetContainerBuild.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizard, AzureWizardPromptStep, UserCancelledError } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';
import { ScaffoldingWizardContext } from '../ScaffoldingWizardContext';
import { NetContainerBuildOptions, NetSdkChooseBuildStep } from './NetSdkChooseBuildStep';

export interface NetChooseBuildTypeContext extends ScaffoldingWizardContext {
containerBuildOptions?: NetContainerBuildOptions;
}

export async function netContainerBuild(wizardContext: Partial<NetChooseBuildTypeContext>, apiInput?: NetChooseBuildTypeContext): Promise<void> {
if (!vscode.workspace.isTrusted) {
throw new UserCancelledError('enforceTrust');
}

const promptSteps: AzureWizardPromptStep<NetChooseBuildTypeContext>[] = [
new NetSdkChooseBuildStep()
];

const wizard = new AzureWizard<NetChooseBuildTypeContext>(wizardContext as NetChooseBuildTypeContext, {
promptSteps: promptSteps,
title: vscode.l10n.t('Initialize for Debugging'),
});

await wizard.prompt();
await wizard.execute();
}
Loading