diff --git a/.vscode-test.js b/.vscode-test.js index 56aa8ef89..ad3faf00d 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -89,6 +89,42 @@ module.exports = defineConfig({ reuseMachineInstall: !isCIBuild, installExtensions, }, + { + label: "codeWorkspaceTests", + files: [ + "dist/test/common.js", + "dist/test/integration-tests/extension.test.js", + "dist/test/integration-tests/WorkspaceContext.test.js", + "dist/test/integration-tests/tasks/**/*.test.js", + "dist/test/integration-tests/commands/build.test.js", + "dist/test/integration-tests/testexplorer/TestExplorerIntegration.test.js", + "dist/test/integration-tests/commands/dependency.test.js", + ], + version: process.env["VSCODE_VERSION"] ?? "stable", + workspaceFolder: "./assets/test.code-workspace", + launchArgs, + extensionDevelopmentPath: vsixPath + ? [`${__dirname}/.vscode-test/extensions/${publisher}.${name}-${version}`] + : undefined, + mocha: { + ui: "tdd", + color: true, + timeout, + forbidOnly: isCIBuild, + grep: isFastTestRun ? "@slow" : undefined, + invert: isFastTestRun, + slow: 10000, + retries: 1, + reporter: path.join(__dirname, ".mocha-reporter.js"), + reporterOptions: { + jsonReporterOptions: { + output: path.join(__dirname, "test-results", "code-workspace-tests.json"), + }, + }, + }, + reuseMachineInstall: !isCIBuild, + installExtensions, + }, { label: "unitTests", files: ["dist/test/common.js", "dist/test/unit-tests/**/*.test.js"], diff --git a/.vscode/launch.json b/.vscode/launch.json index ebd884101..14beaaaeb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,6 +29,19 @@ }, "preLaunchTask": "compile-tests" }, + { + "name": "Code Workspace Tests", + "type": "extensionHost", + "request": "launch", + "testConfiguration": "${workspaceFolder}/.vscode-test.js", + "testConfigurationLabel": "codeWorkspaceTests", + "args": ["--profile=testing-debug"], + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "env": { + "VSCODE_DEBUG": "1" + }, + "preLaunchTask": "compile-tests" + }, { "name": "Unit Tests", "type": "extensionHost", diff --git a/assets/test.code-workspace b/assets/test.code-workspace new file mode 100644 index 000000000..39881bcb6 --- /dev/null +++ b/assets/test.code-workspace @@ -0,0 +1,140 @@ +{ + "folders": [ + { + "name": "diagnostics", + "path": "./test/diagnostics" + }, + { + "name": "command-plugin", + "path": "./test/command-plugin" + }, + { + "name": "defaultPackage", + "path": "./test/defaultPackage" + } + ], + "settings": { + "swift.disableAutoResolve": true, + "swift.autoGenerateLaunchConfigurations": false, + "swift.debugger.debugAdapter": "lldb-dap", + "swift.debugger.setupCodeLLDB": "alwaysUpdateGlobal", + "swift.additionalTestArguments": [ + "-Xswiftc", + "-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING" + ], + "lldb.verboseLogging": true, + "lldb.launch.terminal": "external", + "lldb-dap.detachOnError": true, + "swift.sourcekit-lsp.backgroundIndexing": "off" + }, + "tasks": { + "version": "2.0.0", + "tasks": [ + { + "type": "swift", + "args": [ + "build", + "--build-tests", + "--verbose", + "-Xswiftc", + "-DBAR" + ], + "cwd": "${workspaceFolder:defaultPackage}", + "group": { + "kind": "build", + "isDefault": true + }, + "label": "swift: Build All (defaultPackage) (workspace)", + "detail": "swift build --build-tests --verbose -Xswiftc -DBAR" + }, + { + "type": "swift", + "args": [ + "build", + "--show-bin-path" + ], + "cwd": "${workspaceFolder:defaultPackage}", + "group": "build", + "label": "swift: Build All from code workspace", + "detail": "swift build --show-bin-path" + }, + { + "type": "swift-plugin", + "command": "command_plugin", + "args": [ + "--foo" + ], + "cwd": "${workspaceFolder:command-plugin}", + "disableSandbox": true, + "label": "swift: command-plugin from code workspace", + "detail": "swift package command_plugin --foo" + }, + { + "type": "swift", + "args": [ + "build", + "--product", + "PackageExe", + "-Xswiftc", + "-diagnostic-style=llvm", + "-Xswiftc", + "-DBAR" + ], + "cwd": "${workspaceFolder:defaultPackage}", + "group": "build", + "label": "swift: Build Debug PackageExe (defaultPackage) (workspace)", + "detail": "swift build --product PackageExe -Xswiftc -diagnostic-style=llvm -Xswiftc -DBAR" + }, + { + "type": "swift", + "args": [ + "build", + "-c", + "release", + "--product", + "PackageExe", + "-Xswiftc", + "-diagnostic-style=llvm", + "-Xswiftc", + "-DBAR" + ], + "cwd": "${workspaceFolder:defaultPackage}", + "group": "build", + "label": "swift: Build Release PackageExe (defaultPackage) (workspace)", + "detail": "swift build -c release --product PackageExe -Xswiftc -diagnostic-style=llvm -Xswiftc -DBAR" + } + ] + }, + "launch": { + "version": "0.2.0", + "configurations": [ + { + "type": "swift", + "request": "launch", + "name": "Debug PackageExe (defaultPackage) (workspace)", + "program": "${workspaceFolder:defaultPackage}/.build/debug/PackageExe", + "args": [], + "cwd": "${workspaceFolder:defaultPackage}", + "preLaunchTask": "swift: Build Debug PackageExe (defaultPackage) (workspace)", + "disableASLR": false, + "initCommands": [ + "settings set target.disable-aslr false" + ] + }, + { + "type": "swift", + "request": "launch", + "name": "Release PackageExe (defaultPackage) (workspace)", + "program": "${workspaceFolder:defaultPackage}/.build/release/PackageExe", + "args": [], + "cwd": "${workspaceFolder:defaultPackage}", + "preLaunchTask": "swift: Build Release PackageExe (defaultPackage) (workspace)", + "disableASLR": false, + "initCommands": [ + "settings set target.disable-aslr false" + ] + } + ], + "compounds": [] + } +} \ No newline at end of file diff --git a/assets/test/.vscode/tasks.json b/assets/test/.vscode/tasks.json index 406dec54f..e309bd7a0 100644 --- a/assets/test/.vscode/tasks.json +++ b/assets/test/.vscode/tasks.json @@ -6,7 +6,9 @@ "args": [ "build", "--build-tests", - "--verbose" + "--verbose", + "-Xswiftc", + "-DFOO" ], "cwd": "defaultPackage", "problemMatcher": [ @@ -14,7 +16,7 @@ ], "group": "build", "label": "swift: Build All (defaultPackage)", - "detail": "swift build --build-tests --verbose" + "detail": "swift build --build-tests --verbose -Xswiftc -DFOO" }, { "type": "swift", @@ -33,7 +35,9 @@ { "type": "swift-plugin", "command": "command_plugin", - "args": ["--foo"], + "args": [ + "--foo" + ], "cwd": "command-plugin", "disableSandbox": true, "problemMatcher": [ diff --git a/scripts/test_windows.ps1 b/scripts/test_windows.ps1 index 06e93e25e..434b68105 100644 --- a/scripts/test_windows.ps1 +++ b/scripts/test_windows.ps1 @@ -15,6 +15,7 @@ function Update-SwiftBuildAndPackageArguments { param ( [string]$jsonFilePath = "./assets/test/.vscode/settings.json", + [string]$codeWorkspaceFilePath = "./assets/test.code-workspace", [string]$windowsSdkVersion = "10.0.22000.0", [string]$vcToolsVersion = "14.43.34808" ) @@ -28,9 +29,17 @@ function Update-SwiftBuildAndPackageArguments { exit 1 } + try { + $codeWorkspaceContent = Get-Content -Raw -Path $codeWorkspaceFilePath | ConvertFrom-Json + } catch { + Write-Host "Invalid JSON content in $codeWorkspaceFilePath" + exit 1 + } + if ($jsonContent.PSObject.Properties['swift.buildArguments']) { $jsonContent.PSObject.Properties.Remove('swift.buildArguments') } + $jsonContent | Add-Member -MemberType NoteProperty -Name "swift.buildArguments" -Value @( "-Xbuild-tools-swiftc", "-windows-sdk-root", "-Xbuild-tools-swiftc", $windowsSdkRoot, @@ -54,10 +63,20 @@ function Update-SwiftBuildAndPackageArguments { "-Xswiftc", "-visualc-tools-version", "-Xswiftc", $vcToolsVersion ) + + $codeWorkspaceContent.PSObject.Properties.Remove('settings') + $codeWorkspaceContent | Add-Member -MemberType NoteProperty -Name "settings" -Value $jsonContent + $jsonContent | ConvertTo-Json -Depth 32 | Set-Content -Path $jsonFilePath + + $codeWorkspaceContent | ConvertTo-Json -Depth 32 | Set-Content -Path $codeWorkspaceFilePath + Write-Host "Contents of ${jsonFilePath}:" Get-Content -Path $jsonFilePath + + Write-Host "Contents of ${codeWorkspaceFilePath}:" + Get-Content -Path $codeWorkspaceFilePath } $swiftVersionOutput = & swift --version diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index 17319fd16..a1c1c8963 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -51,6 +51,7 @@ import { reduceTestItemChildren } from "./TestUtils"; import { CompositeCancellationToken } from "../utilities/cancellation"; // eslint-disable-next-line @typescript-eslint/no-require-imports import stripAnsi = require("strip-ansi"); +import { packageName, resolveScope } from "../utilities/tasks"; export enum TestLibrary { xctest = "XCTest", @@ -773,8 +774,8 @@ export class TestRunner { `Building and Running Tests${kindLabel}`, { cwd: this.folderContext.folder, - scope: this.folderContext.workspaceFolder, - prefix: this.folderContext.name, + scope: resolveScope(this.folderContext.workspaceFolder), + packageName: packageName(this.folderContext), presentationOptions: { reveal: vscode.TaskRevealKind.Never }, }, this.folderContext.toolchain, diff --git a/src/commands/build.ts b/src/commands/build.ts index b74d9d011..a0d3ee7cd 100644 --- a/src/commands/build.ts +++ b/src/commands/build.ts @@ -19,6 +19,7 @@ import { debugLaunchConfig, getLaunchConfiguration } from "../debugger/launch"; import { executeTaskWithUI } from "./utilities"; import { FolderContext } from "../FolderContext"; import { Target } from "../SwiftPackage"; +import { packageName } from "../utilities/tasks"; /** * Executes a {@link vscode.Task task} to run swift target. @@ -56,7 +57,7 @@ export async function folderCleanBuild(folderContext: FolderContext) { { cwd: folderContext.folder, scope: folderContext.workspaceFolder, - prefix: folderContext.name, + packageName: packageName(folderContext), presentationOptions: { reveal: vscode.TaskRevealKind.Silent }, group: vscode.TaskGroup.Clean, }, @@ -111,7 +112,11 @@ export async function debugBuildWithOptions( const launchConfig = getLaunchConfiguration(target.name, current); if (launchConfig) { ctx.buildStarted(target.name, launchConfig, options); - const result = await debugLaunchConfig(current.workspaceFolder, launchConfig, options); + const result = await debugLaunchConfig( + vscode.workspace.workspaceFile ? undefined : current.workspaceFolder, + launchConfig, + options + ); ctx.buildFinished(target.name, launchConfig, options); return result; } diff --git a/src/commands/dependencies/edit.ts b/src/commands/dependencies/edit.ts index f49272547..40a79777b 100644 --- a/src/commands/dependencies/edit.ts +++ b/src/commands/dependencies/edit.ts @@ -16,6 +16,7 @@ import * as vscode from "vscode"; import { createSwiftTask } from "../../tasks/SwiftTaskProvider"; import { FolderOperation, WorkspaceContext } from "../../WorkspaceContext"; import { executeTaskWithUI } from "../utilities"; +import { packageName } from "../../utilities/tasks"; /** * Setup package dependency to be edited @@ -34,7 +35,7 @@ export async function editDependency(identifier: string, ctx: WorkspaceContext) { scope: currentFolder.workspaceFolder, cwd: currentFolder.folder, - prefix: currentFolder.name, + packageName: packageName(currentFolder), }, currentFolder.toolchain ); diff --git a/src/commands/dependencies/resolve.ts b/src/commands/dependencies/resolve.ts index 4348aa480..45e13bbbb 100644 --- a/src/commands/dependencies/resolve.ts +++ b/src/commands/dependencies/resolve.ts @@ -17,6 +17,7 @@ import { FolderContext } from "../../FolderContext"; import { createSwiftTask, SwiftTaskProvider } from "../../tasks/SwiftTaskProvider"; import { WorkspaceContext } from "../../WorkspaceContext"; import { executeTaskWithUI, updateAfterError } from "../utilities"; +import { packageName } from "../../utilities/tasks"; /** * Executes a {@link vscode.Task task} to resolve this package's dependencies. @@ -43,7 +44,7 @@ export async function resolveFolderDependencies( { cwd: folderContext.folder, scope: folderContext.workspaceFolder, - prefix: folderContext.name, + packageName: packageName(folderContext), presentationOptions: { reveal: vscode.TaskRevealKind.Silent }, }, folderContext.toolchain diff --git a/src/commands/dependencies/update.ts b/src/commands/dependencies/update.ts index c957a4dec..440eb2d59 100644 --- a/src/commands/dependencies/update.ts +++ b/src/commands/dependencies/update.ts @@ -17,6 +17,7 @@ import { FolderContext } from "../../FolderContext"; import { WorkspaceContext } from "../../WorkspaceContext"; import { createSwiftTask, SwiftTaskProvider } from "../../tasks/SwiftTaskProvider"; import { executeTaskWithUI, updateAfterError } from "./../utilities"; +import { packageName } from "../../utilities/tasks"; /** * Executes a {@link vscode.Task task} to update this package's dependencies. @@ -41,7 +42,7 @@ export async function updateFolderDependencies(folderContext: FolderContext) { { cwd: folderContext.folder, scope: folderContext.workspaceFolder, - prefix: folderContext.name, + packageName: packageName(folderContext), presentationOptions: { reveal: vscode.TaskRevealKind.Silent }, }, folderContext.toolchain diff --git a/src/commands/dependencies/useLocal.ts b/src/commands/dependencies/useLocal.ts index 63408f0f9..e8cc1db71 100644 --- a/src/commands/dependencies/useLocal.ts +++ b/src/commands/dependencies/useLocal.ts @@ -16,6 +16,7 @@ import * as vscode from "vscode"; import { FolderOperation, WorkspaceContext } from "../../WorkspaceContext"; import { createSwiftTask } from "../../tasks/SwiftTaskProvider"; import { executeTaskWithUI } from "../utilities"; +import { packageName } from "../../utilities/tasks"; /** * Use local version of package dependency @@ -61,7 +62,7 @@ export async function useLocalDependency( { scope: currentFolder.workspaceFolder, cwd: currentFolder.folder, - prefix: currentFolder.name, + packageName: packageName(currentFolder), }, currentFolder.toolchain ); diff --git a/src/commands/resetPackage.ts b/src/commands/resetPackage.ts index 988159044..dccada8c8 100644 --- a/src/commands/resetPackage.ts +++ b/src/commands/resetPackage.ts @@ -17,6 +17,7 @@ import { FolderContext } from "../FolderContext"; import { createSwiftTask, SwiftTaskProvider } from "../tasks/SwiftTaskProvider"; import { WorkspaceContext } from "../WorkspaceContext"; import { executeTaskWithUI } from "./utilities"; +import { packageName } from "../utilities/tasks"; /** * Executes a {@link vscode.Task task} to reset the complete cache/build directory. @@ -40,7 +41,7 @@ export async function folderResetPackage(folderContext: FolderContext) { { cwd: folderContext.folder, scope: folderContext.workspaceFolder, - prefix: folderContext.name, + packageName: packageName(folderContext), presentationOptions: { reveal: vscode.TaskRevealKind.Silent }, group: vscode.TaskGroup.Clean, }, @@ -58,7 +59,7 @@ export async function folderResetPackage(folderContext: FolderContext) { { cwd: folderContext.folder, scope: folderContext.workspaceFolder, - prefix: folderContext.name, + packageName: packageName(folderContext), presentationOptions: { reveal: vscode.TaskRevealKind.Silent }, }, folderContext.toolchain diff --git a/src/configuration.ts b/src/configuration.ts index 27615108b..450593ece 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -509,6 +509,13 @@ function computeVscodeVar(varName: string): string | null { const file = () => vscode.window.activeTextEditor?.document?.uri?.fsPath || ""; + const regex = /workspaceFolder:(.*)/gm; + const match = regex.exec(varName); + if (match) { + const name = match[1]; + return vscode.workspace.workspaceFolders?.find(f => f.name === name)?.uri.fsPath ?? null; + } + // https://code.visualstudio.com/docs/editor/variables-reference // Variables to be substituted should be added here. const supportedVariables: { [k: string]: () => string } = { diff --git a/src/debugger/buildConfig.ts b/src/debugger/buildConfig.ts index 94650c6d9..9cd930737 100644 --- a/src/debugger/buildConfig.ts +++ b/src/debugger/buildConfig.ts @@ -27,6 +27,7 @@ import { TestLibrary } from "../TestExplorer/TestRunner"; import { TestKind, isDebugging, isRelease } from "../TestExplorer/TestKind"; import { buildOptions } from "../tasks/SwiftTaskProvider"; import { updateLaunchConfigForCI } from "./lldb"; +import { packageName } from "../utilities/tasks"; export class BuildConfigurationFactory { public static buildAll( @@ -719,13 +720,14 @@ export function getFolderAndNameSuffix( ? ctx.workspaceFolder.uri.fsPath : `\${workspaceFolder:${ctx.workspaceFolder.name}}`; let folder: string; - let nameSuffix: string; - if (ctx.relativePath.length === 0) { + let nameSuffix; + const pkgName = packageName(ctx); + if (pkgName) { + folder = nodePath.join(workspaceFolder, ctx.relativePath); + nameSuffix = ` (${packageName(ctx)})`; + } else { folder = workspaceFolder; nameSuffix = ""; - } else { - folder = nodePath.join(workspaceFolder, ctx.relativePath); - nameSuffix = ` (${ctx.relativePath})`; } return { folder: folder, nameSuffix: nameSuffix }; } diff --git a/src/debugger/launch.ts b/src/debugger/launch.ts index e2121093a..b1760609e 100644 --- a/src/debugger/launch.ts +++ b/src/debugger/launch.ts @@ -50,7 +50,9 @@ export async function makeDebugConfigurations( return false; } - const wsLaunchSection = vscode.workspace.getConfiguration("launch", ctx.folder); + const wsLaunchSection = vscode.workspace.workspaceFile + ? vscode.workspace.getConfiguration("launch") + : vscode.workspace.getConfiguration("launch", ctx.folder); const launchConfigs = wsLaunchSection.get("configurations") || []; // Determine which launch configurations need updating/creating @@ -111,7 +113,9 @@ export async function makeDebugConfigurations( await wsLaunchSection.update( "configurations", launchConfigs, - vscode.ConfigurationTarget.WorkspaceFolder + vscode.workspace.workspaceFile + ? vscode.ConfigurationTarget.Workspace + : vscode.ConfigurationTarget.WorkspaceFolder ); return true; } @@ -121,7 +125,9 @@ export function getLaunchConfiguration( target: string, folderCtx: FolderContext ): vscode.DebugConfiguration | undefined { - const wsLaunchSection = vscode.workspace.getConfiguration("launch", folderCtx.workspaceFolder); + const wsLaunchSection = vscode.workspace.workspaceFile + ? vscode.workspace.getConfiguration("launch") + : vscode.workspace.getConfiguration("launch", folderCtx.workspaceFolder); const launchConfigs = wsLaunchSection.get("configurations") || []; const { folder } = getFolderAndNameSuffix(folderCtx); const targetPath = path.join( @@ -131,9 +137,10 @@ export function getLaunchConfiguration( ); // Users could be on different platforms with different path annotations, // so normalize before we compare. - return launchConfigs.find( + const launchConfig = launchConfigs.find( config => path.normalize(config.program) === path.normalize(targetPath) ); + return launchConfig; } // Return array of DebugConfigurations for executables based on what is in Package.swift @@ -201,7 +208,7 @@ export function createSnippetConfiguration( * @param workspaceFolder Workspace to run debugger in */ export async function debugLaunchConfig( - workspaceFolder: vscode.WorkspaceFolder, + workspaceFolder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, options: vscode.DebugSessionOptions = {} ) { diff --git a/src/tasks/SwiftPluginTaskProvider.ts b/src/tasks/SwiftPluginTaskProvider.ts index 7e68a6893..5cc58aaef 100644 --- a/src/tasks/SwiftPluginTaskProvider.ts +++ b/src/tasks/SwiftPluginTaskProvider.ts @@ -18,7 +18,7 @@ import { WorkspaceContext } from "../WorkspaceContext"; import { PackagePlugin } from "../SwiftPackage"; import { swiftRuntimeEnv } from "../utilities/utilities"; import { SwiftExecution } from "../tasks/SwiftExecution"; -import { resolveTaskCwd } from "../utilities/tasks"; +import { packageName, resolveTaskCwd } from "../utilities/tasks"; import configuration, { PluginPermissionConfiguration, substituteVariablesInString, @@ -31,7 +31,7 @@ interface TaskConfig { cwd: vscode.Uri; scope: vscode.WorkspaceFolder; presentationOptions?: vscode.TaskPresentationOptions; - prefix?: string; + packageName?: string; } /** @@ -62,6 +62,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { presentationOptions: { reveal: vscode.TaskRevealKind.Always, }, + packageName: packageName(folderContext), }) ); } @@ -150,13 +151,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider { }), [] ); - let prefix: string; - if (config.prefix) { - prefix = `(${config.prefix}) `; - } else { - prefix = ""; - } - task.detail = `${prefix}swift ${swiftArgs.join(" ")}`; + task.detail = `swift ${swiftArgs.join(" ")}`; task.presentationOptions = presentation; return task as SwiftTask; } diff --git a/src/tasks/SwiftTaskProvider.ts b/src/tasks/SwiftTaskProvider.ts index 10a0dda2e..9067ed5df 100644 --- a/src/tasks/SwiftTaskProvider.ts +++ b/src/tasks/SwiftTaskProvider.ts @@ -24,7 +24,7 @@ import { swiftRuntimeEnv } from "../utilities/utilities"; import { Version } from "../utilities/version"; import { SwiftToolchain } from "../toolchain/toolchain"; import { SwiftExecution } from "../tasks/SwiftExecution"; -import { resolveTaskCwd } from "../utilities/tasks"; +import { packageName, resolveScope, resolveTaskCwd } from "../utilities/tasks"; import { BuildConfigurationFactory } from "../debugger/buildConfig"; /** @@ -44,7 +44,7 @@ interface TaskConfig { scope: vscode.TaskScope | vscode.WorkspaceFolder; group?: vscode.TaskGroup; presentationOptions?: vscode.TaskPresentationOptions; - prefix?: string; + packageName?: string; disableTaskQueue?: boolean; dontTriggerTestDiscovery?: boolean; showBuildStatus?: ShowBuildStatusOptions; @@ -139,8 +139,9 @@ function buildAllTaskName(folderContext: FolderContext, release: boolean): strin let buildTaskName = release ? `${SwiftTaskProvider.buildAllName} - Release` : SwiftTaskProvider.buildAllName; - if (folderContext.relativePath.length > 0) { - buildTaskName += ` (${folderContext.relativePath})`; + const packageNamePostfix = packageName(folderContext); + if (packageNamePostfix) { + buildTaskName += ` (${packageNamePostfix})`; } return buildTaskName; } @@ -160,7 +161,7 @@ export async function createBuildAllTask( { group: vscode.TaskGroup.Build, cwd: folderContext.folder, - scope: folderContext.workspaceFolder, + scope: resolveScope(folderContext.workspaceFolder), presentationOptions: { reveal: getBuildRevealOption(), }, @@ -189,11 +190,17 @@ export async function getBuildAllTask( // search for build all task in task.json first, that are valid for folder const tasks = await vscode.tasks.fetchTasks(); const workspaceTasks = tasks.filter(task => { - if (task.source !== "Workspace" || task.scope !== folderContext.workspaceFolder) { + if (task.source !== "Workspace") { return false; } const swiftExecutionOptions = (task.execution as SwiftExecution).options; let cwd = swiftExecutionOptions?.cwd; + if (task.scope === vscode.TaskScope.Workspace) { + return cwd && substituteVariablesInString(cwd) === folderContext.folder.fsPath; + } + if (task.scope !== folderContext.workspaceFolder) { + return false; + } if (cwd === "${workspaceFolder}" || cwd === undefined) { cwd = folderWorkingDir; } @@ -232,22 +239,18 @@ export async function getBuildAllTask( */ function createBuildTasks(product: Product, folderContext: FolderContext): vscode.Task[] { const toolchain = folderContext.toolchain; - let buildTaskNameSuffix = ""; - if (folderContext.relativePath.length > 0) { - buildTaskNameSuffix = ` (${folderContext.relativePath})`; - } - - const buildDebugName = `Build Debug ${product.name}${buildTaskNameSuffix}`; + const buildDebugName = `Build Debug ${product.name}`; const buildDebugTask = createSwiftTask( ["build", "--product", product.name, ...buildOptions(toolchain)], buildDebugName, { group: vscode.TaskGroup.Build, cwd: folderContext.folder, - scope: folderContext.workspaceFolder, + scope: resolveScope(folderContext.workspaceFolder), presentationOptions: { reveal: getBuildRevealOption(), }, + packageName: packageName(folderContext), disableTaskQueue: true, dontTriggerTestDiscovery: true, }, @@ -255,10 +258,10 @@ function createBuildTasks(product: Product, folderContext: FolderContext): vscod ); const buildDebug = buildAllTaskCache.get(buildDebugName, folderContext, buildDebugTask); - const buildReleaseName = `Build Release ${product.name}${buildTaskNameSuffix}`; + const buildReleaseName = `Build Release ${product.name}`; const buildReleaseTask = createSwiftTask( ["build", "-c", "release", "--product", product.name, ...buildOptions(toolchain, false)], - `Build Release ${product.name}${buildTaskNameSuffix}`, + `Build Release ${product.name}`, { group: vscode.TaskGroup.Build, cwd: folderContext.folder, @@ -266,6 +269,7 @@ function createBuildTasks(product: Product, folderContext: FolderContext): vscod presentationOptions: { reveal: getBuildRevealOption(), }, + packageName: packageName(folderContext), disableTaskQueue: true, dontTriggerTestDiscovery: true, }, @@ -306,6 +310,9 @@ export function createSwiftTask( }*/ const env = { ...configuration.swiftEnvironmentVariables, ...swiftRuntimeEnv(), ...cmdEnv }; const presentation = config?.presentationOptions ?? {}; + if (config?.packageName) { + name += ` (${config?.packageName})`; + } const task = new vscode.Task( { type: "swift", @@ -334,14 +341,7 @@ export function createSwiftTask( ); // This doesn't include any quotes added by VS Code. // See also: https://github.com/microsoft/vscode/issues/137895 - - let prefix: string; - if (config?.prefix) { - prefix = `(${config.prefix}) `; - } else { - prefix = ""; - } - task.detail = `${prefix}swift ${args.join(" ")}`; + task.detail = `swift ${args.join(" ")}`; task.group = config?.group; task.presentationOptions = presentation; return task as SwiftTask; @@ -404,7 +404,7 @@ export class SwiftTaskProvider implements vscode.TaskProvider { type: "swift", args: [], }, - folderContext.workspaceFolder, + resolveScope(folderContext.workspaceFolder), buildTaskName, "swift", new vscode.CustomExecution(() => { diff --git a/src/ui/StatusItem.ts b/src/ui/StatusItem.ts index 3d78ec29a..a40858885 100644 --- a/src/ui/StatusItem.ts +++ b/src/ui/StatusItem.ts @@ -13,18 +13,12 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import * as path from "path"; export class RunningTask { constructor(public task: vscode.Task | string) {} get name(): string { if (typeof this.task !== "string") { - const folder = this.task.definition.cwd as string; - if (folder) { - return `${this.task.name} (${path.basename(folder)})`; - } else { - return this.task.name; - } + return this.task.name; } else { return this.task; } diff --git a/src/utilities/tasks.ts b/src/utilities/tasks.ts index 089c78be4..90bcddcc9 100644 --- a/src/utilities/tasks.ts +++ b/src/utilities/tasks.ts @@ -13,6 +13,8 @@ //===----------------------------------------------------------------------===// import * as path from "path"; import * as vscode from "vscode"; +import { substituteVariablesInString } from "../configuration"; +import { FolderContext } from "../FolderContext"; export const lineBreakRegex = /\r\n|\n|\r/gm; @@ -22,6 +24,10 @@ export function resolveTaskCwd(task: vscode.Task, cwd?: string): string | undefi return scopeWorkspaceFolder; } + if (/\$\{.*\}/g.test(cwd)) { + return substituteVariablesInString(cwd); + } + if (path.isAbsolute(cwd)) { return cwd; } else if (scopeWorkspaceFolder) { @@ -53,3 +59,18 @@ export function checkIfBuildComplete(line: string): boolean { } return false; } + +export function packageName(folderContext: FolderContext): string | undefined { + if (vscode.workspace.workspaceFile) { + return folderContext.name; + } else if (folderContext.relativePath.length > 0) { + return folderContext.relativePath; + } +} + +export function resolveScope(scope: vscode.WorkspaceFolder | vscode.TaskScope) { + if (vscode.workspace.workspaceFile) { + return vscode.TaskScope.Workspace; + } + return scope; +} diff --git a/test/integration-tests/WorkspaceContext.test.ts b/test/integration-tests/WorkspaceContext.test.ts index 1dab210f1..ab96f3582 100644 --- a/test/integration-tests/WorkspaceContext.test.ts +++ b/test/integration-tests/WorkspaceContext.test.ts @@ -20,9 +20,14 @@ import { FolderOperation, WorkspaceContext } from "../../src/WorkspaceContext"; import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { Version } from "../../src/utilities/version"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; -import { activateExtensionForSuite, updateSettings } from "./utilities/testutilities"; +import { + activateExtensionForSuite, + getRootWorkspaceFolder, + updateSettings, +} from "./utilities/testutilities"; import { FolderContext } from "../../src/FolderContext"; import { assertContains } from "./testexplorer/utilities"; +import { resolveScope } from "../../src/utilities/tasks"; function assertContainsArg(execution: SwiftExecution, arg: string) { assert(execution?.args.find(a => a === arg)); @@ -45,7 +50,7 @@ suite("WorkspaceContext Test Suite", () => { workspaceContext = ctx; }, // No default assets as we want to verify against a clean workspace. - testAssets: [], + testAssets: ["defaultPackage"], }); test("Add", async () => { @@ -60,7 +65,7 @@ suite("WorkspaceContext Test Suite", () => { recordedFolders.push(changedFolderRecord); }); - const workspaceFolder = vscode.workspace.workspaceFolders?.values().next().value; + const workspaceFolder = getRootWorkspaceFolder(); assert.ok(workspaceFolder, "No workspace folders found in workspace"); @@ -102,7 +107,7 @@ suite("WorkspaceContext Test Suite", () => { }); // Was hitting a timeout in suiteSetup during CI build once in a while - this.timeout(5000); + this.timeout(15000); test("Default Task values", async () => { const folder = workspaceContext.folders.find( @@ -120,7 +125,7 @@ suite("WorkspaceContext Test Suite", () => { assertContainsArg(execution, "--build-tests"); assertContainsArg(execution, "-Xswiftc"); assertContainsArg(execution, "-diagnostic-style=llvm"); - assert.strictEqual(buildAllTask.scope, folder.workspaceFolder); + assert.strictEqual(buildAllTask.scope, resolveScope(folder.workspaceFolder)); }); test('"default" diagnosticsStyle', async () => { @@ -138,7 +143,7 @@ suite("WorkspaceContext Test Suite", () => { assertContainsArg(execution, "build"); assertContainsArg(execution, "--build-tests"); assertNotContainsArg(execution, "-diagnostic-style"); - assert.strictEqual(buildAllTask.scope, folder.workspaceFolder); + assert.strictEqual(buildAllTask.scope, resolveScope(folder.workspaceFolder)); }); test('"swift" diagnosticsStyle', async () => { @@ -157,7 +162,7 @@ suite("WorkspaceContext Test Suite", () => { assertContainsArg(execution, "--build-tests"); assertContainsArg(execution, "-Xswiftc"); assertContainsArg(execution, "-diagnostic-style=swift"); - assert.strictEqual(buildAllTask.scope, folder.workspaceFolder); + assert.strictEqual(buildAllTask.scope, resolveScope(folder.workspaceFolder)); }); test("Build Settings", async () => { @@ -244,4 +249,4 @@ suite("WorkspaceContext Test Suite", () => { }); }).timeout(1000); }); -}).timeout(10000); +}).timeout(15000); diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 00730d114..03cecce83 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -70,7 +70,8 @@ suite("Build Commands @slow", function () { // NB: "stopped" is the exact command when debuggee has stopped due to break point, // but "stackTrace" is the deterministic sync point we will use to make sure we can execute continue const bpPromise = waitForDebugAdapterRequest( - "Debug PackageExe (defaultPackage)", + "Debug PackageExe (defaultPackage)" + + (vscode.workspace.workspaceFile ? " (workspace)" : ""), workspaceContext.globalToolchain.swiftVersion, "stackTrace" ); diff --git a/test/integration-tests/configuration.test.ts b/test/integration-tests/configuration.test.ts index 28f9e7277..5baaff6b7 100644 --- a/test/integration-tests/configuration.test.ts +++ b/test/integration-tests/configuration.test.ts @@ -12,9 +12,12 @@ // //===----------------------------------------------------------------------===// -import * as vscode from "vscode"; import * as path from "path"; -import { activateExtensionForSuite, updateSettings } from "./utilities/testutilities"; +import { + activateExtensionForSuite, + getRootWorkspaceFolder, + updateSettings, +} from "./utilities/testutilities"; import { expect } from "chai"; import { afterEach } from "mocha"; import configuration from "../../src/configuration"; @@ -47,7 +50,7 @@ suite("Configuration Test Suite", function () { expect(task.definition.args).to.not.be.undefined; const index = task.definition.args.indexOf("--scratch-path"); expect(task.definition.args[index + 1]).to.equal( - vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath + "/somepath" + getRootWorkspaceFolder()?.uri.fsPath + "/somepath" ); }); @@ -56,7 +59,7 @@ suite("Configuration Test Suite", function () { "swift.buildPath": "${workspaceFolder}${pathSeparator}${workspaceFolderBasename}", }); - const basePath = vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath; + const basePath = getRootWorkspaceFolder()?.uri.fsPath; const baseName = path.basename(basePath ?? ""); const sep = path.sep; expect(configuration.buildPath).to.equal(`${basePath}${sep}${baseName}`); diff --git a/test/integration-tests/extension.test.ts b/test/integration-tests/extension.test.ts index 346b34510..b3999cb7b 100644 --- a/test/integration-tests/extension.test.ts +++ b/test/integration-tests/extension.test.ts @@ -13,10 +13,11 @@ //===----------------------------------------------------------------------===// import * as assert from "assert"; +import * as vscode from "vscode"; import { WorkspaceContext } from "../../src/WorkspaceContext"; import { getBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; -import { activateExtensionForTest } from "./utilities/testutilities"; +import { activateExtensionForTest, findWorkspaceFolder } from "./utilities/testutilities"; import { expect } from "chai"; suite("Extension Test Suite", function () { @@ -47,13 +48,18 @@ suite("Extension Test Suite", function () { this.timeout(60000); /** Verify tasks.json is being loaded */ test("Tasks.json", async () => { - const folder = workspaceContext.folders.find(f => f.name === "test/defaultPackage"); + const folder = findWorkspaceFolder("defaultPackage", workspaceContext); assert(folder); const buildAllTask = await getBuildAllTask(folder); const execution = buildAllTask.execution as SwiftExecution; expect(buildAllTask.definition.type).to.equal("swift"); - expect(buildAllTask.name).to.include("Build All (defaultPackage)"); - for (const arg of ["build", "--build-tests", "--verbose"]) { + expect(buildAllTask.name).to.include( + "Build All (defaultPackage)" + + (vscode.workspace.workspaceFile ? " (workspace)" : "") + ); + for (const arg of ["build", "--build-tests", "--verbose"].concat([ + vscode.workspace.workspaceFile ? "-DBAR" : "-DFOO", + ])) { assert(execution?.args.find(item => item === arg)); } }).timeout(60000); diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index 293dc60fd..f7b693bb5 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -263,15 +263,27 @@ suite("SwiftPluginTaskProvider Test Suite", function () { }); suite("includes command plugin provided by tasks.json", () => { - let task: vscode.Task | undefined; + let task: SwiftTask | undefined; setup(async () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift-plugin" }); - task = tasks.find(t => t.name === "swift: command-plugin from tasks.json"); + task = tasks.find( + t => + t.name === + "swift: command-plugin from " + + (vscode.workspace.workspaceFile ? "code workspace" : "tasks.json") + ) as SwiftTask; }); test("provides", () => { - expect(task?.detail).to.include("swift package command_plugin --foo"); + expect(task?.execution.args).to.deep.equal( + folderContext.toolchain.buildFlags.withAdditionalFlags([ + "package", + "--disable-sandbox", + "command_plugin", + "--foo", + ]) + ); }); test("executes", async () => { diff --git a/test/integration-tests/tasks/SwiftTaskProvider.test.ts b/test/integration-tests/tasks/SwiftTaskProvider.test.ts index 1e0d3075a..19ed58650 100644 --- a/test/integration-tests/tasks/SwiftTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftTaskProvider.test.ts @@ -128,7 +128,12 @@ suite("SwiftTaskProvider Test Suite", () => { setup(async () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); - task = tasks.find(t => t.name === "swift: Build All from tasks.json"); + task = tasks.find( + t => + t.name === + "swift: Build All from " + + (vscode.workspace.workspaceFile ? "code workspace" : "tasks.json") + ); }); test("provided", async () => { diff --git a/test/integration-tests/tasks/TaskQueue.test.ts b/test/integration-tests/tasks/TaskQueue.test.ts index a72330f16..8e7e3297e 100644 --- a/test/integration-tests/tasks/TaskQueue.test.ts +++ b/test/integration-tests/tasks/TaskQueue.test.ts @@ -18,7 +18,7 @@ import { testAssetPath } from "../../fixtures"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { SwiftExecOperation, TaskOperation, TaskQueue } from "../../../src/tasks/TaskQueue"; import { waitForNoRunningTasks } from "../../utilities/tasks"; -import { activateExtensionForSuite } from "../utilities/testutilities"; +import { activateExtensionForSuite, findWorkspaceFolder } from "../utilities/testutilities"; suite("TaskQueue Test Suite", () => { let workspaceContext: WorkspaceContext; @@ -160,7 +160,7 @@ suite("TaskQueue Test Suite", () => { // check queuing task will return expected value test("swift exec", async () => { - const folder = workspaceContext.folders.find(f => f.name === "test/defaultPackage"); + const folder = findWorkspaceFolder("defaultPackage", workspaceContext); assert(folder); const operation = new SwiftExecOperation( ["--version"], @@ -177,7 +177,7 @@ suite("TaskQueue Test Suite", () => { // check queuing swift exec operation will throw expected error test("swift exec error", async () => { - const folder = workspaceContext.folders.find(f => f.name === "test/defaultPackage"); + const folder = findWorkspaceFolder("defaultPackage", workspaceContext); assert(folder); const operation = new SwiftExecOperation( ["--version"], diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index a06711733..e4d957d71 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -16,8 +16,8 @@ import * as vscode from "vscode"; import * as assert from "assert"; import * as mocha from "mocha"; import { Api } from "../../../src/extension"; -import { testAssetUri } from "../../fixtures"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { testAssetPath, testAssetUri } from "../../fixtures"; +import { FolderOperation, WorkspaceContext } from "../../../src/WorkspaceContext"; import { FolderContext } from "../../../src/FolderContext"; import { waitForNoRunningTasks } from "../../utilities/tasks"; import { closeAllEditors } from "../../utilities/commands"; @@ -27,7 +27,7 @@ import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; import configuration from "../../../src/configuration"; import { resetBuildAllTaskCache } from "../../../src/tasks/SwiftTaskProvider"; -function getRootWorkspaceFolder(): vscode.WorkspaceFolder { +export function getRootWorkspaceFolder(): vscode.WorkspaceFolder { const result = vscode.workspace.workspaceFolders?.at(0); assert(result, "No workspace folders were opened for the tests to use"); return result; @@ -230,10 +230,39 @@ const extensionBootstrapper = (() => { } // Add assets required for the suite/test to the workspace. - const workspaceFolder = getRootWorkspaceFolder(); - for (const asset of testAssets ?? []) { - const packageFolder = testAssetUri(asset); - await workspaceContext.addPackageFolder(packageFolder, workspaceFolder); + const expectedAssets = testAssets ?? ["defaultPackage"]; + if (!vscode.workspace.workspaceFile) { + for (const asset of expectedAssets) { + await folderInRootWorkspace(asset, workspaceContext); + } + } else if (expectedAssets.length > 0) { + await new Promise(res => { + const found: string[] = []; + for (const f of workspaceContext.folders) { + if (found.includes(f.name) || !expectedAssets.includes(f.name)) { + continue; + } + found.push(f.name); + } + if (expectedAssets.length === found.length) { + res(); + return; + } + const disposable = workspaceContext.onDidChangeFolders(e => { + if ( + e.operation !== FolderOperation.add || + found.includes(e.folder!.name) || + !expectedAssets.includes(e.folder!.name) + ) { + return; + } + found.push(e.folder!.name); + if (expectedAssets.length === found.length) { + res(); + disposable.dispose(); + } + }); + }); } return workspaceContext; @@ -339,6 +368,13 @@ export const folderInRootWorkspace = async ( return folder; }; +export function findWorkspaceFolder( + name: string, + workspaceContext: WorkspaceContext +): FolderContext | undefined { + return workspaceContext.folders.find(f => f.folder.fsPath === testAssetPath(name)); +} + export type SettingsMap = { [key: string]: unknown }; /**