diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index c849a9f78e5..2652b5c2fbb 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,25 +1,5 @@ -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- +ARG VARIANT=preview +FROM mcr.microsoft.com/devcontainers/dotnet:${VARIANT} -FROM mcr.microsoft.com/powershell/test-deps:ubuntu-20.04@sha256:d1609c57d2426b9cfffa3a3ab7bda5ebc4448700f8ba8ef377692c4a70e64b8c - -# Avoid warnings by switching to noninteractive -ENV DEBIAN_FRONTEND=noninteractive - -# Configure apt and install packages -RUN apt-get update \ - && apt-get -y upgrade \ - && apt-get -y install --no-install-recommends apt-utils 2>&1 \ - # - # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed - && apt-get -y install --no-install-recommends git procps lsb-release \ - # - # Clean up - && apt-get autoremove -y \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* - -# Switch back to dialog for any ad-hoc use of apt-get -ENV DEBIAN_FRONTEND=dialog +# This is a workaround for https://github.com/microsoft/vscode-remote-release/issues/10934 +ADD ./scripts /devcontainer/scripts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index eded2d1bdec..82d44321e0f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,22 +1,99 @@ -// See https://aka.ms/vscode-remote/devcontainer.json for format details. +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet { - "name": ".NET Core 6.0, including pwsh (Ubuntu 18.04)", - "dockerFile": "Dockerfile", + "name": "PowerShell Dev", + "build": { + "dockerfile": "Dockerfile", + "args": { + // NOTE: This should be kept up to date to match the .NET SDK currently in use on the master branch. + // but not mandatory as we fetch the appropriate .NET SDK as needed during build. + // See: https://mcr.microsoft.com/en-us/artifact/mar/devcontainers/dotnet/tags for available tags. + "VARIANT": "1-10.0-preview-noble" + } + }, + "hostRequirements": { + "cpus": 4 // .NET compliation is intensive so we want more cores by default + }, + "capAdd": [ + "NET_RAW" // Needed for Pester tests that require ping + ], + + // This path is symlinked in the onCreateCommand to the actual workspaceFolder + // We use it for convenience and consistency with the settings that require absolute paths. + // NOTE: This is ignored when using a named volume devcontainer but will still work fine. + "remoteUser": "vscode", + "containerUser": "vscode", + + // This is required if the dotnet is updated to a later version than what is in the image. + // So that the C# extension can detect it. Doing it in .bashrc is not enough. + "remoteEnv": { + "PATH": "/home/vscode/.dotnet:${containerEnv:PATH}" + }, - "workspaceMount": "source=${localWorkspaceFolder},target=/PowerShell,type=bind", - "workspaceFolder": "/PowerShell", + // Container lifecycle scripts + // Docs: https://containers.dev/implementors/json_reference/#lifecycle-scripts - // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "cd src/powershell-unix && dotnet restore", + // NOTE: We don't use initializeCommand because that runs on the host and we may not know what OS it is. + "onCreateCommand": [ + "/usr/bin/pwsh", + "-nop", + "-noni", + "-f", + "/devcontainer/scripts/1-onCreate.ps1" + ], + "updateContentCommand": [ + "/usr/bin/pwsh", + "-nop", + "-noni", + "-f", + "/devcontainer/scripts/2-updateContent.ps1" + ], + // These lifecycle scripts are non-blocking to loading the codespace and UI by default + "postCreateCommand": [ + "/usr/bin/pwsh", + "-nop", + "-noni", + "-f", + "/devcontainer/scripts/3-postCreate.ps1" + ], + "postStartCommand": [ + "/usr/bin/pwsh", + "-nop", + "-noni", + "-f", + "/devcontainer/scripts/4-postStart.ps1" + ], + // This runs in a vscode terminal after startup and is generally not needed but here for reference. + // "postAttachCommand": ["/usr/bin/pwsh", "-nop", "-noni", "-f", "/devcontainer/scripts/5-postAttach.ps1"], + + // Configure tool-specific properties. "customizations": { "vscode": { + "settings": { + "dotnet.defaultSolution": "PowerShell.sln", + "dotnet.testWindow.useTestingPlatformProtocol": true, + "csharp.experimental.debug.hotReload": true, + "powershell.powerShellAdditionalExePaths": { + "PowerShell-Dev": "${workspaceFolder}/debug/pwsh" + }, + "terminal.integrated.profiles.linux": { + "pwsh dev": { + "path": "/powershell/debug/pwsh", + "icon": "terminal-powershell", + "color": "terminal.ansiMagenta" + } + } + }, "extensions": [ - "ms-azure-devops.azure-pipelines", "ms-dotnettools.csharp", - "ms-vscode.powershell", + "ms-dotnettools.csdevkit", + "ms-vscode.PowerShell", + "GitHub.vscode-pull-request-github", + "ms-azure-devops.azure-pipelines", + "editorconfig.editorconfig", "DavidAnson.vscode-markdownlint", - "vitaliymaz.vscode-svg-previewer" + "esbenp.prettier-vscode" ] } } diff --git a/.devcontainer/scripts/1-onCreate.ps1 b/.devcontainer/scripts/1-onCreate.ps1 new file mode 100644 index 00000000000..07dfff8919d --- /dev/null +++ b/.devcontainer/scripts/1-onCreate.ps1 @@ -0,0 +1,31 @@ +#Docs: https://containers.dev/implementors/json_reference/#lifecycle-scripts +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +# The workspace folder name can be variable, we need a stable target for some settings like terminal path +$absolutePath = '/powershell' +. $PSScriptRoot/shared.ps1 + +if ($PWD -ne $absolutePath) { + log "Linking $SCRIPT:WorkspaceFolder to $absolutePath" + sudo ln -s $SCRIPT:WorkspaceFolder /powershell + log "Adding $absolutePath to git safe directories" + git config --global --add safe.directory $absolutePath +} + +log "Adding $SCRIPT:WorkspaceFolder to git safe directories" +git config --global --add safe.directory $SCRIPT:WorkspaceFolder + + +# NOTE: We override the Azure Devops private feed as it may not be up to date with the required packages +# This is only for development and not builds so any potential vulnerabilities will be caught at CI time +# If you want to restore private restore behavior for testing, perform the following: +# PS> dotnet nuget disable source nuget.org;dotnet nuget enable source powershell + +#Because several PowerSHell build steps request you to use -UseNugetOrg, these files get changed when you do that. We ignore this in the codespaces so they do not get accidentally committed to PRs. You can always use --no-skip-worktree after build if you intentionally want to modify these files. +log 'Ignoring nuget.config changes' +git update-index --skip-worktree nuget.config src/Modules/nuget.config test/tools/Modules/nuget.config + +log 'Switching to Nuget.Org Packages Only for Codespaces Development' +Import-Module ./build.psm1 +Switch-PSNugetConfig -Source NuGetOnly diff --git a/.devcontainer/scripts/2-updateContent.ps1 b/.devcontainer/scripts/2-updateContent.ps1 new file mode 100644 index 00000000000..308fc39a7e0 --- /dev/null +++ b/.devcontainer/scripts/2-updateContent.ps1 @@ -0,0 +1,40 @@ +#Docs: https://containers.dev/implementors/json_reference/#lifecycle-scripts +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +. $PSScriptRoot/shared.ps1 +Import-Module -Force ./build.psm1 + +#If doing a clone into devcontainer, VSCode defaults to a shallow clone. The build process does not support shallow +#clones due to the use of git tag describe, so we must "unshallow" it if this occurs +if ((git rev-parse --is-shallow-repository) -ne 'false') { + log 'Shallow Clone detected, this is not supported by the PowerShell build process. Unshallowing...' + git fetch --unshallow +} + +# Fetch upstream tags. The PowerShell dotnet restore requires this (GetPSCoreVersionFromGit) +log 'Syncing PowerShell Git Tags from Remote' +Sync-PSTags -AddRemoteIfMissing + +log 'Bootstrap PowerShell Build Prerequisites' + +Start-PSBootstrap -Scenario DotNet + +#Ping is needed for tests but is not included in the .NET SDK devcontainer and PSBootstrap doesn't cover it +log 'Installing iputils (for the ping utility)' +sudo apt install iputils-ping -y + +# Perform an initial build of PowerShell, this is needed so the "pwsh dev" terminal and debug launch tasks are available on first run +log "Building PowerShell" +Start-PSBuild -UseNugetOrg -Clean -PublishLinkPath debug + +# Prebuild more if in a codespace, otherwise leave this to the user to optimize local startup time +if ($ENV:CODESPACES) { + log 'Prebuilding Tests' + dotnet build test/xUnit + log 'Fetching Pester for Tests' + Restore-PSPester + log 'Build Testing Tools' + Publish-PSTestTools + Publish-CustomConnectionTestModule +} diff --git a/.devcontainer/scripts/3-postCreate.ps1 b/.devcontainer/scripts/3-postCreate.ps1 new file mode 100644 index 00000000000..945161a2970 --- /dev/null +++ b/.devcontainer/scripts/3-postCreate.ps1 @@ -0,0 +1 @@ +#Docs: https://containers.dev/implementors/json_reference/#lifecycle-scripts diff --git a/.devcontainer/scripts/4-postStart.ps1 b/.devcontainer/scripts/4-postStart.ps1 new file mode 100644 index 00000000000..945161a2970 --- /dev/null +++ b/.devcontainer/scripts/4-postStart.ps1 @@ -0,0 +1 @@ +#Docs: https://containers.dev/implementors/json_reference/#lifecycle-scripts diff --git a/.devcontainer/scripts/5-postAttach.ps1 b/.devcontainer/scripts/5-postAttach.ps1 new file mode 100644 index 00000000000..945161a2970 --- /dev/null +++ b/.devcontainer/scripts/5-postAttach.ps1 @@ -0,0 +1 @@ +#Docs: https://containers.dev/implementors/json_reference/#lifecycle-scripts diff --git a/.devcontainer/scripts/shared.ps1 b/.devcontainer/scripts/shared.ps1 new file mode 100644 index 00000000000..dba4a34b801 --- /dev/null +++ b/.devcontainer/scripts/shared.ps1 @@ -0,0 +1,19 @@ +function log ([string[]]$message) { + $message = $message -join ' ' + Write-Host -ForegroundColor Cyan $message +} + +#Devcontainer scripts should have their CWD set to the workspace folder, so we use this for our needed paths +$SCRIPT:WorkspaceFolder = $PWD + +# Suppresses ANSI Output in Codespaces Build Output +# $env:CODESPACES is a "magic variable" in the codespaces CI that can be used to detect it +if ($env:CODESPACES) { + [Environment]::SetEnvironmentVariable('TERM', 'dumb', [EnvironmentVariableTarget]::User) + [Environment]::SetEnvironmentVariable('TERM', 'dumb', [EnvironmentVariableTarget]::Process) + $ENV:TERM = 'dumb' + $ENV:NO_COLOR = $true + $ENV:DOTNET_CLI_CONTEXT_ANSI_PASS_THRU = $false + $ENV:DOTNET_CLI_CONTEXT_VERBOSE = $false + $PSStyle.OutputRendering = 'PlainText' +} diff --git a/.gitignore b/.gitignore index ccadde27182..8163add2aec 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ obj/ project.lock.json *-tests.xml /debug/ +/debug /staging/ /Packages/ *.nuget.props diff --git a/.vscode/extensions.json b/.vscode/extensions.json index fa227cd9944..ad9512dd114 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,13 +2,13 @@ // See https://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "editorconfig.editorconfig", - "ms-azure-devops.azure-pipelines", - "ms-vscode.cpptools", "ms-dotnettools.csharp", + "ms-dotnettools.csdevkit", "ms-vscode.PowerShell", - "twxs.cmake", + "GitHub.vscode-pull-request-github", + "ms-azure-devops.azure-pipelines", + "editorconfig.editorconfig", "DavidAnson.vscode-markdownlint", - "vitaliymaz.vscode-svg-previewer" + "esbenp.prettier-vscode" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 4ba261df08c..6d6bf3ffb72 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,14 +2,17 @@ "version": "0.2.0", "configurations": [ { - "name": ".NET Core Launch", + "name": "PowerShell Dev Console", "type": "coreclr", "request": "launch", - "justMyCode": false, - "stopAtEntry": true, "program": "${workspaceRoot}/debug/pwsh", - "preLaunchTask": "Build", - "externalConsole": true, + "console": "integratedTerminal", + "justMyCode": false, + "stopAtEntry": false, + "logging": { + "moduleLoad": false, + "exceptions": false // PS throws a lot of caught exceptions and this clutters output view. + }, "cwd": "${workspaceRoot}" }, { diff --git a/.vscode/settings.json b/.vscode/settings.json index ad298823d10..c85666d7538 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,29 @@ // Place your settings in this file to overwrite default and user settings. { + // This is the default. EditorConfig *should* override this on a per language basis. "editor.tabSize": 4, "editor.insertSpaces": true, - "files.insertFinalNewline": true, + // These are defaults but explicitly specified to override any user local preferences + "omnisharp.enableEditorConfigSupport": true, + "prettier.useEditorConfig": true, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + + // Only format changed code, as lots of code may not meet current format standards. + "editor.formatOnSaveMode": "modifications", + + // We leave this one up to the user + // "editor.codeActionsOnSave": {} + + // Configure Prettier for formatting + // PowerShell and C# use their respective extension formatters + // Markdown uses markdownlint + "[json][jsonc][yaml][shellscript][html][javascript][typescript][properties]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + // Based on current .markdownlist.json settings: // https://github.com/PowerShell/PowerShell/blob/master/.markdownlint.json "markdownlint.config": { @@ -33,8 +52,5 @@ "powershell.codeFormatting.whitespaceAroundOperator": true, // Adds a space after a separator (',' and ';'). - "powershell.codeFormatting.whitespaceAfterSeparator": true, - - // Omnisharp : Enable EditorConfig support - "omnisharp.enableEditorConfigSupport": true + "powershell.codeFormatting.whitespaceAfterSeparator": true } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b92aaddeb0d..6bdfd9319b7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,64 +1,68 @@ { "version": "2.0.0", - - "windows": { - "options": { - "shell": { - "executable": "pwsh.exe", - "args": [ - "-NoProfile", - "-ExecutionPolicy", - "Bypass", - "-Command" - ] - } - } - }, - "linux": { - "options": { - "shell": { - "executable": "/usr/bin/pwsh", - "args": [ - "-NoProfile", - "-Command" - ] - } + "options": { + "shell": { + "executable": "pwsh", + "args": [ + "-NoProfile", + "-NonInteractive", + "-Command" + ] } }, - "osx": { - "options": { - "shell": { - "executable": "/usr/local/bin/pwsh", - "args": [ - "-NoProfile", - "-Command" - ] - } - } - }, - "tasks": [ { - "label": "Bootstrap", + "label": "Build - Clean", "type": "shell", - "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBootstrap", - "problemMatcher": [] + "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBuild -Clean -PublishLinkPath debug", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$msCompile" }, { - "label": "Clean Build", + "label": "Build", "type": "shell", - "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBuild -Clean -Output (Join-Path '${workspaceFolder}' debug)", + "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBuild", + "group": { + "kind": "build", + }, "problemMatcher": "$msCompile" }, { - "label": "Build", + "label": "Bootstrap", "type": "shell", - "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBuild -Output (Join-Path '${workspaceFolder}' debug)", + "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBootstrap -Scenario dotnet", + "problemMatcher": [] + }, + { + "label": "Test", + "dependsOn": [ + "Test - Pester", + "Test - xUnit" + ], "group": { - "kind": "build", + "kind": "test", "isDefault": true + } + }, + { + "label": "Test - Pester", + "type": "shell", + "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSPester", + "group": { + "kind": "test", }, - "problemMatcher": "$msCompile" + }, + { + "label": "Test - xUnit", + "type": "shell", + "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSXUnit", + "group": { + "kind": "test" + } } ] + } diff --git a/build.psm1 b/build.psm1 index 34d79670fb4..3721e18a2ad 100644 --- a/build.psm1 +++ b/build.psm1 @@ -341,7 +341,9 @@ function Start-PSBuild { [switch]$Detailed, [switch]$InteractiveAuth, [switch]$SkipRoslynAnalyzers, - [string]$PSOptionsPath + [string]$PSOptionsPath, + # When specified, a symbolic link to the pwsh publish directory will be created to the resultant publish directory. This is helpful for tools that require a stable path to the pwsh executable. + [string]$PublishLinkPath ) if ($ReleaseTag -and $ReleaseTag -notmatch "^v\d+\.\d+\.\d+(-(preview|rc)(\.\d{1,2})?)?$") { @@ -610,6 +612,37 @@ Fix steps: Pop-Location } + if ($PublishLinkPath -and (Test-Path $publishPath)) { + + $linkPath = Join-Path $PSScriptRoot $PublishLinkPath + try { + if (Test-Path $linkPath) { + if (-not ((Get-Item $linkPath).LinkType -eq 'SymbolicLink')) { + throw "PublishLinkPath specified but $linkPath exists and is not a symbolic link. Please remove it manually." + } + + $linkTarget = (Get-Item $linkPath).Target + if ($linkTarget -ne $publishPath) { + Write-Log -message "Removing existing symbolic link at $linkPath, which points to $linkTarget instead of $publishPath" + Remove-Item -Force $linkPath -ErrorAction Stop + } else { + Write-Log -message "PublishLinkPath $linkPath already points to $publishPath. Nothing to do." + } + } + + if (-not (Test-Path $linkPath)) { + Write-Log -message "Creating symbolic link at $linkPath to $publishPath" + $result = New-Item -Force -ItemType SymbolicLink -Path $linkPath -Value $publishPath -ErrorAction Stop + if (-not $result) { + throw "Failed to create symbolic link at $linkPath, New-Item was run but nothing was returned. This is probably a bug." + } + } + } catch { + Write-Warning "Failed to create PublishLinkPath at $linkPath`: $_. If on Windows, ensure you are in Developer Mode" + return + } + } + # No extra post-building task will run if '-SMAOnly' is specified, because its purpose is for a quick update of S.M.A.dll after full build. if ($SMAOnly) { return