diff --git a/.github/instructions/instructions.md b/.github/instructions/instructions.md index 85488bbb1..8f890d7e9 100644 --- a/.github/instructions/instructions.md +++ b/.github/instructions/instructions.md @@ -20,16 +20,16 @@ The DSC project is built using Rust and PowerShell. The build system automatical ### Required 1. **PowerShell**: Version 7.2 or later (cross-platform) - - Download from: https://github.com/PowerShell/PowerShell + - Download from: - The build script is invoked with `pwsh` 2. **Rust**: Latest stable version - The build script can install/update Rust automatically - - Manual installation: https://rustup.rs/ + - Manual installation: ### Automatically Installed by Build Script -The `build.new.ps1` script automatically installs or verifies the following dependencies: +The `build.ps1` script automatically installs or verifies the following dependencies: - **Clippy**: Rust linting tool (when using `-Clippy` flag) - **Node.js**: Required for tree-sitter grammar generation @@ -39,14 +39,17 @@ The `build.new.ps1` script automatically installs or verifies the following depe ### Platform-Specific Requirements #### Windows + - Visual Studio 2019 or later with C++ build tools (automatically checked) - MakeAppx.exe (for creating MSIX packages) #### Linux + - GCC or Clang - Standard development tools (make, pkg-config) #### macOS + - Xcode Command Line Tools ## Quick Start @@ -55,30 +58,30 @@ For a quick build and test on your current platform: ```powershell # Build the project in debug mode -./build.new.ps1 +./build.ps1 # Build with linting (recommended) -./build.new.ps1 -Clippy +./build.ps1 -Clippy # Build and run all tests -./build.new.ps1 -Clippy -Test +./build.ps1 -Clippy -Test # Build in release mode (optimized) -./build.new.ps1 -Release +./build.ps1 -Release ``` ## Building the Project -The main build script is `build.new.ps1`, which orchestrates the entire build process. +The main build script is `build.ps1`, which orchestrates the entire build process. ### Basic Build ```powershell # Debug build (default) -./build.new.ps1 +./build.ps1 # Release build (optimized, slower compile time) -./build.new.ps1 -Release +./build.ps1 -Release ``` ### Build with Linting @@ -87,10 +90,10 @@ It's recommended to lint before building to catch issues early: ```powershell # Build with Clippy linting -./build.new.ps1 -Clippy +./build.ps1 -Clippy # Build release version with linting -./build.new.ps1 -Clippy -Release +./build.ps1 -Clippy -Release ``` ### Cross-Platform Builds @@ -99,53 +102,54 @@ Build for a specific architecture: ```powershell # Windows on ARM -./build.new.ps1 -Architecture aarch64-pc-windows-msvc +./build.ps1 -Architecture aarch64-pc-windows-msvc # Windows on x64 -./build.new.ps1 -Architecture x86_64-pc-windows-msvc +./build.ps1 -Architecture x86_64-pc-windows-msvc # macOS on ARM (Apple Silicon) -./build.new.ps1 -Architecture aarch64-apple-darwin +./build.ps1 -Architecture aarch64-apple-darwin # macOS on x64 (Intel) -./build.new.ps1 -Architecture x86_64-apple-darwin +./build.ps1 -Architecture x86_64-apple-darwin # Linux on ARM with glibc -./build.new.ps1 -Architecture aarch64-unknown-linux-gnu +./build.ps1 -Architecture aarch64-unknown-linux-gnu # Linux on x64 with glibc -./build.new.ps1 -Architecture x86_64-unknown-linux-gnu +./build.ps1 -Architecture x86_64-unknown-linux-gnu # Linux on x64 with musl (static linking) -./build.new.ps1 -Architecture x86_64-unknown-linux-musl +./build.ps1 -Architecture x86_64-unknown-linux-musl ``` ### Build Specific Projects ```powershell # Build only specific projects -./build.new.ps1 -Project dsc,y2j +./build.ps1 -Project dsc,y2j ``` ### Clean Build ```powershell # Clean and rebuild -./build.new.ps1 -Clean +./build.ps1 -Clean ``` ### Skip Build (Useful for Testing Only) ```powershell # Skip building, only run tests -./build.new.ps1 -SkipBuild -Test +./build.ps1 -SkipBuild -Test ``` ### Build Artifacts After a successful build, artifacts are located in: -- **bin/**: Compiled binaries and executables -- **target/**: Rust build artifacts (debug or release) + +- `bin/`: Compiled binaries and executables +- `target/`: Rust build artifacts (debug or release) ## Running Tests @@ -158,30 +162,30 @@ The DSC project includes two types of tests: ```powershell # Build and run all tests -./build.new.ps1 -Test +./build.ps1 -Test # Run all tests with linting -./build.new.ps1 -Clippy -Test +./build.ps1 -Clippy -Test ``` ### Run Only Rust Tests ```powershell # Build and run Rust tests only -./build.new.ps1 -Test -ExcludePesterTests +./build.ps1 -Test -ExcludePesterTests # Skip build, run Rust tests only -./build.new.ps1 -SkipBuild -Test -ExcludePesterTests +./build.ps1 -SkipBuild -Test -ExcludePesterTests ``` ### Run Only Pester Tests ```powershell # Build and run Pester tests only -./build.new.ps1 -Test -ExcludeRustTests +./build.ps1 -Test -ExcludeRustTests # Skip build, run Pester tests only -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests +./build.ps1 -SkipBuild -Test -ExcludeRustTests ``` ### Run Specific Pester Test Groups @@ -190,30 +194,30 @@ Pester tests are organized into groups: ```powershell # Run tests for DSC CLI -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc +./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc # Run tests for adapters -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup adapters +./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup adapters # Run tests for extensions -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup extensions +./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup extensions # Run tests for resources -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup resources +./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup resources # Run tests for grammars -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup grammars +./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup grammars # Run multiple test groups -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc,resources +./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc,resources ``` ### Test Documentation ```powershell # Generate and test Rust documentation -./build.new.ps1 -RustDocs -./build.new.ps1 -SkipBuild -RustDocs -Test -ExcludeRustTests -ExcludePesterTests +./build.ps1 -RustDocs +./build.ps1 -SkipBuild -RustDocs -Test -ExcludeRustTests -ExcludePesterTests ``` ## Linting @@ -224,10 +228,10 @@ Clippy is the recommended linter for Rust code: ```powershell # Lint and build -./build.new.ps1 -Clippy +./build.ps1 -Clippy # Lint without building (faster) -./build.new.ps1 -SkipBuild -Clippy +./build.ps1 -SkipBuild -Clippy ``` Clippy checks are enforced in CI and must pass before merging pull requests. @@ -237,7 +241,7 @@ Clippy checks are enforced in CI and must pass before merging pull requests. Run cargo audit to check for security vulnerabilities: ```powershell -./build.new.ps1 -Audit +./build.ps1 -Audit ``` ## Creating Packages @@ -254,33 +258,33 @@ You must specify a specific architecture (not `current`) when packaging. ```powershell # Create MSIX package (Windows only) -./build.new.ps1 -PackageType msix -Architecture x86_64-pc-windows-msvc -Release +./build.ps1 -PackageType msix -Architecture x86_64-pc-windows-msvc -Release # Create MSIX private package -./build.new.ps1 -PackageType msix-private -Architecture x86_64-pc-windows-msvc -Release +./build.ps1 -PackageType msix-private -Architecture x86_64-pc-windows-msvc -Release # Create MSIX bundle (builds both x64 and ARM64) -./build.new.ps1 -PackageType msixbundle -Release +./build.ps1 -PackageType msixbundle -Release # Create ZIP package -./build.new.ps1 -PackageType zip -Architecture x86_64-pc-windows-msvc -Release +./build.ps1 -PackageType zip -Architecture x86_64-pc-windows-msvc -Release ``` #### Linux/macOS Packages ```powershell # Create tar.gz package for Linux -./build.new.ps1 -PackageType tgz -Architecture x86_64-unknown-linux-gnu -Release +./build.ps1 -PackageType tgz -Architecture x86_64-unknown-linux-gnu -Release # Create tar.gz package for macOS -./build.new.ps1 -PackageType tgz -Architecture aarch64-apple-darwin -Release +./build.ps1 -PackageType tgz -Architecture aarch64-apple-darwin -Release ``` ### Get Package Version ```powershell # Get the current version from Cargo.toml -./build.new.ps1 -GetPackageVersion +./build.ps1 -GetPackageVersion ``` ## CI/CD Workflows @@ -327,23 +331,24 @@ To simulate the CI workflow locally: ```powershell # Install prerequisites and build with Clippy (matches CI) -./build.new.ps1 -SkipBuild -Clippy -Verbose -./build.new.ps1 -Clippy -Verbose +./build.ps1 -SkipBuild -Clippy -Verbose +./build.ps1 -Clippy -Verbose # Run Rust tests (matches CI) -./build.new.ps1 -SkipBuild -Test -ExcludePesterTests -Verbose +./build.ps1 -SkipBuild -Test -ExcludePesterTests -Verbose # Run Pester tests for a specific group (matches CI) -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc -Verbose +./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc -Verbose # Test documentation (matches CI docs job) -./build.new.ps1 -RustDocs -Verbose -./build.new.ps1 -SkipBuild -RustDocs -Test -ExcludeRustTests -ExcludePesterTests -Verbose +./build.ps1 -RustDocs -Verbose +./build.ps1 -SkipBuild -RustDocs -Test -ExcludeRustTests -ExcludePesterTests -Verbose ``` ### Winget Workflow: winget.yml The `winget.yml` workflow publishes releases to Windows Package Manager (WinGet): + - Triggered on release publication or manual workflow dispatch - Only processes stable releases (not pre-releases) - Creates WinGet package submission @@ -355,6 +360,7 @@ The `winget.yml` workflow publishes releases to Windows Package Manager (WinGet) #### Rust Not Found If Rust is not installed: + ```powershell # The build script will attempt to install Rust automatically # Or manually install from: https://rustup.rs/ @@ -363,26 +369,29 @@ If Rust is not installed: #### Build Fails on Windows Ensure Visual Studio C++ build tools are installed: + ```powershell # The build script checks and provides guidance -./build.new.ps1 -Verbose +./build.ps1 -Verbose ``` #### Tests Fail Check that the build completed successfully: + ```powershell # Ensure clean build before testing -./build.new.ps1 -Clean -./build.new.ps1 -Clippy -Test +./build.ps1 -Clean +./build.ps1 -Clippy -Test ``` #### Node.js or tree-sitter Missing The build script automatically installs these. If issues persist: + ```powershell # Re-run with verbose output -./build.new.ps1 -Verbose +./build.ps1 -Verbose ``` ### Build Script Parameters Reference @@ -414,14 +423,15 @@ The build script automatically installs these. If issues persist: ### Verbose Output For detailed build information: + ```powershell -./build.new.ps1 -Verbose +./build.ps1 -Verbose ``` ### Getting Help - Review error messages from the build script -- Check GitHub Issues: https://github.com/PowerShell/DSC/issues +- Check GitHub Issues: - See CONTRIBUTING.md for contribution guidelines ## Validating Changes @@ -430,20 +440,21 @@ Before submitting a pull request, validate your changes with the following comma ```powershell # 1. Clean build with linting -./build.new.ps1 -Clean -Clippy -Release +./build.ps1 -Clean -Clippy -Release # 2. Run all tests -./build.new.ps1 -SkipBuild -Test +./build.ps1 -SkipBuild -Test # 3. Check for security vulnerabilities -./build.new.ps1 -Audit +./build.ps1 -Audit # 4. Generate and test documentation (optional) -./build.new.ps1 -RustDocs -./build.new.ps1 -SkipBuild -RustDocs -Test -ExcludeRustTests -ExcludePesterTests +./build.ps1 -RustDocs +./build.ps1 -SkipBuild -RustDocs -Test -ExcludeRustTests -ExcludePesterTests ``` This ensures your changes: + - Build successfully with optimizations - Pass all linting checks - Pass all tests @@ -456,4 +467,5 @@ This ensures your changes: - **Contributing Guide**: [CONTRIBUTING.md](CONTRIBUTING.md) - **Documentation**: [docs/](docs/) - **Build Helper Module**: [build.helpers.psm1](build.helpers.psm1) -- **Legacy Build Script**: [build.ps1](build.ps1) (deprecated, use build.new.ps1) +- **Legacy Build Script**: [packaging.ps1](packaging.ps1) - legacy packaging script, retained for + release packaging and certain package types (`rpm`, `deb`). diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5db949703..082b19003 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -27,9 +27,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install prerequisites - run: ./build.new.ps1 -SkipBuild -Clippy -Verbose + run: ./build.ps1 -SkipBuild -Clippy -Verbose - name: Generate documentation - run: ./build.new.ps1 -RustDocs -Verbose + run: ./build.ps1 -RustDocs -Verbose - name: Test documentation run: |- $testParams = @{ @@ -40,17 +40,17 @@ jobs: ExcludePesterTests = $true Verbose = $true } - ./build.new.ps1 @testParams + ./build.ps1 @testParams linux-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Install prerequisites - run: ./build.new.ps1 -SkipBuild -Clippy -Verbose + run: ./build.ps1 -SkipBuild -Clippy -Verbose - name: Build - run: ./build.new.ps1 -Clippy -Verbose + run: ./build.ps1 -Clippy -Verbose - name: Run rust tests - run: ./build.new.ps1 -SkipBuild -Test -ExcludePesterTests -Verbose + run: ./build.ps1 -SkipBuild -Test -ExcludePesterTests -Verbose - name: Prepare build artifact run: tar -cvf bin.tar bin/ - name: Upload build artifact @@ -81,18 +81,18 @@ jobs: ExcludeRustTests = $true Verbose = $true } - ./build.new.ps1 @params -PesterTestGroup ${{matrix.group}} + ./build.ps1 @params -PesterTestGroup ${{matrix.group}} macos-build: runs-on: macos-latest steps: - uses: actions/checkout@v5 - name: Install prerequisites - run: ./build.new.ps1 -SkipBuild -Clippy -Verbose + run: ./build.ps1 -SkipBuild -Clippy -Verbose - name: Build - run: ./build.new.ps1 -Clippy -Verbose + run: ./build.ps1 -Clippy -Verbose - name: Run rust tests - run: ./build.new.ps1 -SkipBuild -Test -ExcludePesterTests -Verbose + run: ./build.ps1 -SkipBuild -Test -ExcludePesterTests -Verbose - name: Prepare build artifact run: tar -cvf bin.tar bin/ - name: Upload build artifact @@ -123,7 +123,7 @@ jobs: ExcludeRustTests = $true Verbose = $true } - ./build.new.ps1 @params -PesterTestGroup ${{matrix.group}} + ./build.ps1 @params -PesterTestGroup ${{matrix.group}} # Windows windows-build: @@ -131,11 +131,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install prerequisites - run: ./build.new.ps1 -SkipBuild -Clippy -Verbose + run: ./build.ps1 -SkipBuild -Clippy -Verbose - name: Build - run: ./build.new.ps1 -Clippy -Verbose + run: ./build.ps1 -Clippy -Verbose - name: Run rust tests - run: ./build.new.ps1 -SkipBuild -Test -ExcludePesterTests -Verbose + run: ./build.ps1 -SkipBuild -Test -ExcludePesterTests -Verbose - name: List bin folder files run: Get-ChildItem bin - name: Prepare build artifact @@ -168,4 +168,4 @@ jobs: ExcludeRustTests = $true Verbose = $true } - ./build.new.ps1 @params -PesterTestGroup ${{matrix.group}} + ./build.ps1 @params -PesterTestGroup ${{matrix.group}} diff --git a/.pipelines/DSC-Official.yml b/.pipelines/DSC-Official.yml index 21e07df58..c3046d81a 100644 --- a/.pipelines/DSC-Official.yml +++ b/.pipelines/DSC-Official.yml @@ -91,7 +91,7 @@ extends: - checkout: self - pwsh: | Write-Verbose -Verbose (Get-ChildItem '$(repoRoot)' | Out-String) - $packageVersion = $(repoRoot)/build.ps1 -GetPackageVersion + $packageVersion = $(repoRoot)/packaging.ps1 -GetPackageVersion $vstsCommandString = "vso[task.setvariable variable=Version;isoutput=true]$packageVersion" Write-Host ("sending " + $vstsCommandString) Write-Host "##$vstsCommandString" @@ -181,7 +181,7 @@ extends: $null = New-Item -ItemType Directory -Path "./bin/msix" -Force -ErrorAction Ignore Copy-Item "$(Pipeline.Workspace)/drop_BuildAndSign_BuildWin_x64/*.msix" ./bin/msix -Verbose Copy-Item "$(Pipeline.Workspace)/drop_BuildAndSign_BuildWin_arm64/*.msix" ./bin/msix -Verbose - ./build.ps1 -PackageType msixbundle + ./packaging.ps1 -PackageType msixbundle Copy-Item "$(System.DefaultWorkingDirectory)/DSC/bin/*.msixbundle" "$(ob_outputDirectory)" displayName: 'Create msixbundle' condition: succeeded() @@ -323,10 +323,10 @@ extends: $header = "Bearer $(AzToken)" $env:CARGO_REGISTRIES_POWERSHELL_TOKEN = $header $env:CARGO_REGISTRIES_POWERSHELL_CREDENTIAL_PROVIDER = 'cargo:token' - ./build.ps1 -Release -Architecture x86_64-unknown-linux-musl - ./build.ps1 -PackageType tgz -Architecture x86_64-unknown-linux-musl -Release - ./build.ps1 -PackageType rpm -Architecture x86_64-unknown-linux-musl -Release - ./build.ps1 -PackageType deb -Architecture x86_64-unknown-linux-musl -Release + ./packaging.ps1 -Release -Architecture x86_64-unknown-linux-musl + ./packaging.ps1 -PackageType tgz -Architecture x86_64-unknown-linux-musl -Release + ./packaging.ps1 -PackageType rpm -Architecture x86_64-unknown-linux-musl -Release + ./packaging.ps1 -PackageType deb -Architecture x86_64-unknown-linux-musl -Release Copy-Item ./bin/*.tar.gz "$(ob_outputDirectory)" Copy-Item ./bin/*.rpm "$(ob_outputDirectory)" Copy-Item ./bin/*.deb "$(ob_outputDirectory)" @@ -386,10 +386,10 @@ extends: $header = "Bearer $(AzToken)" $env:CARGO_REGISTRIES_POWERSHELL_TOKEN = $header $env:CARGO_REGISTRIES_POWERSHELL_CREDENTIAL_PROVIDER = 'cargo:token' - ./build.ps1 -Release -Architecture aarch64-unknown-linux-musl - ./build.ps1 -PackageType tgz -Architecture aarch64-unknown-linux-musl -Release - ./build.ps1 -PackageType rpm -Architecture aarch64-unknown-linux-musl -Release - ./build.ps1 -PackageType deb -Architecture aarch64-unknown-linux-musl -Release + ./packaging.ps1 -Release -Architecture aarch64-unknown-linux-musl + ./packaging.ps1 -PackageType tgz -Architecture aarch64-unknown-linux-musl -Release + ./packaging.ps1 -PackageType rpm -Architecture aarch64-unknown-linux-musl -Release + ./packaging.ps1 -PackageType deb -Architecture aarch64-unknown-linux-musl -Release Copy-Item ./bin/*.tar.gz "$(ob_outputDirectory)" Copy-Item ./bin/*.rpm "$(ob_outputDirectory)" Copy-Item ./bin/*.deb "$(ob_outputDirectory)" @@ -436,8 +436,8 @@ extends: $env:CARGO_REGISTRIES_POWERSHELL_TOKEN = $header $env:CARGO_REGISTRIES_POWERSHELL_CREDENTIAL_PROVIDER = 'cargo:token' Write-Verbose -Verbose "Building for $(buildName)" - ./build.ps1 -Release -Architecture $(buildName) - ./build.ps1 -PackageType tgz -Architecture $(buildName) -Release + ./packaging.ps1 -Release -Architecture $(buildName) + ./packaging.ps1 -PackageType tgz -Architecture $(buildName) -Release Copy-Item ./bin/*.tar.gz "$(ob_outputDirectory)" Write-Host "##vso[artifact.upload containerfolder=release;artifactname=release]$(ob_outputDirectory)/DSC-$(PackageVersion)-$(buildName).tar.gz" displayName: 'Build $(buildName)' diff --git a/.pipelines/DSC-Windows.yml b/.pipelines/DSC-Windows.yml index d7580ccae..a6588e9e9 100644 --- a/.pipelines/DSC-Windows.yml +++ b/.pipelines/DSC-Windows.yml @@ -50,7 +50,7 @@ steps: $env:PATH += ";$LLVMBIN" write-verbose -verbose (gcm clang.exe | out-string) Write-Verbose -Verbose "Building for ${{ parameters.buildName }}" - ./build.ps1 -Release -Architecture ${{ parameters.buildName }} -SkipLinkCheck + ./packaging.ps1 -Release -Architecture ${{ parameters.buildName }} -SkipLinkCheck displayName: 'Build ${{ parameters.buildName }}' env: ob_restore_phase: true @@ -95,13 +95,13 @@ steps: OverWrite: true - pwsh: | Set-Location "$(Build.SourcesDirectory)/DSC" - ./build.ps1 -PackageType zip -Architecture ${{ parameters.buildName }} -Release + ./packaging.ps1 -PackageType zip -Architecture ${{ parameters.buildName }} -Release Copy-Item ./bin/*.zip "$(Build.ArtifactStagingDirectory)" -Verbose displayName: 'Zip ${{ parameters.buildName }}' condition: succeeded() - pwsh: | Set-Location "$(Build.SourcesDirectory)/DSC" - ./build.ps1 -PackageType msix -Architecture ${{ parameters.buildName }} -Release -UseX64MakeAppx + ./packaging.ps1 -PackageType msix -Architecture ${{ parameters.buildName }} -Release -UseX64MakeAppx Copy-Item ./bin/msix/*.msix "$(Build.ArtifactStagingDirectory)" -Verbose displayName: 'Create msix for ${{ parameters.buildName }}' condition: succeeded() diff --git a/.vscode/schemas/definitions.json b/.vscode/schemas/definitions.json index d787b8fbd..12d9a33b8 100644 --- a/.vscode/schemas/definitions.json +++ b/.vscode/schemas/definitions.json @@ -3,7 +3,7 @@ "$defs": { "BuildDataFile": { "title": "Build Data", - "markdownDescription": "Defines metadata required for building, testing, and packaging DSC and all related projects with the `build.new.ps1` build script.", + "markdownDescription": "Defines metadata required for building, testing, and packaging DSC and all related projects with the `build.ps1` build script.", "type": "object", "required": ["PackageFiles", "Projects"], "unevaluatedProperties": false, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2d1581035..63c512ce0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,7 @@ "label": "DSC: Build", "detail": "Compiles the Rust crates and copies all build artifacts into the `bin` folder.", "type": "shell", - "command": "${workspaceFolder}/build.new.ps1", + "command": "${workspaceFolder}/build.ps1", "args": [ "-Clippy:${input:Clippy}", "-Verbose", @@ -22,7 +22,7 @@ "label": "DSC: Test (All)", "detail": "Tests every project with optional skipping of building the projects, Clippy linting, Rust tests, and Pester tests.", "type": "shell", - "command": "${workspaceFolder}/build.new.ps1", + "command": "${workspaceFolder}/build.ps1", "args": [ "-SkipBuild:${input:SkipBuild}", "-Test", @@ -40,7 +40,7 @@ "label": "DSC: Test Rust", "detail": "Tests the Rust projects with optional skipping of building the projects and Clippy linting.", "type": "shell", - "command": "${workspaceFolder}/build.new.ps1", + "command": "${workspaceFolder}/build.ps1", "args": [ "-SkipBuild:${input:SkipBuild}", "-Test", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5be3863db..f13dfc44a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,7 +90,7 @@ For more information, see [Contributor License Agreements][01]. Building the DSC project requires PowerShell 7.2 or later to execute the build script. For more information about installing PowerShell, see [Install PowerShell on Windows, Linux, and macOS][02]. -DSC requires other tools for building the project. The build script (`build.new.ps1`) installs the +DSC requires other tools for building the project. The build script (`build.ps1`) installs the tools in the following table if they aren't already installed on your system: | Tool | Version | Platforms | Projects | @@ -118,16 +118,16 @@ tools in the following table if they aren't already installed on your system: ```powershell # Build the project -./build.new.ps1 +./build.ps1 # Build with linting (recommended) -./build.new.ps1 -Clippy +./build.ps1 -Clippy # Build and run all tests -./build.new.ps1 -Clippy -Test +./build.ps1 -Clippy -Test # Build in release mode (optimized) -./build.new.ps1 -Release +./build.ps1 -Release ``` #### Running tests @@ -136,16 +136,16 @@ DSCv3 includes Rust unit tests and PowerShell Pester tests: ```powershell # Run all tests -./build.new.ps1 -Test +./build.ps1 -Test # Run only Rust tests -./build.new.ps1 -Test -ExcludePesterTests +./build.ps1 -Test -ExcludePesterTests # Run only Pester tests -./build.new.ps1 -Test -ExcludeRustTests +./build.ps1 -Test -ExcludeRustTests # Run specific Pester test groups -./build.new.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc +./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc ``` Available Pester test groups include: @@ -163,13 +163,13 @@ for other platforms and architectures with the `-Architecture` parameter: ```powershell # Windows ARM -./build.new.ps1 -Architecture aarch64-pc-windows-msvc +./build.ps1 -Architecture aarch64-pc-windows-msvc # macOS Apple Silicon -./build.new.ps1 -Architecture aarch64-apple-darwin +./build.ps1 -Architecture aarch64-apple-darwin # Linux x64 -./build.new.ps1 -Architecture x86_64-unknown-linux-gnu +./build.ps1 -Architecture x86_64-unknown-linux-gnu ``` #### Additional Information diff --git a/build.comparison.ps1 b/build.comparison.ps1 deleted file mode 100644 index 1927cf8ff..000000000 --- a/build.comparison.ps1 +++ /dev/null @@ -1,341 +0,0 @@ -#!/usr/bin/env pwsh - -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -<# - .SYNOPSIS - Compares build performance for the existing and new build scripts. - - .DESCRIPTION - This is a temporary comparison tool to help us determine when we're ready to switch from the - legacy build script to the new one. It primarily currently focuses on performance, but can be - extended to cover correctness as well. - - Currently, the only correctness check is for comparing output files in the bin folder, expecting - both builds to produce the same artifacts. - - In local testing (only building, excluding tests and packaging): - - - Average cold builds show an improvement of ~2x, averaging 4-5m for new and 8-10m for legacy. - - Average warm builds show an improvement of ~20x, averaging 10-15s for new and 180-240s for legacy. - - Average hot builds show an improvement of ~10x, averaging 10-15s for new and 120-150s for legacy. - - Note that the difference between hot and warm is minimal with the new script because it takes - advantage of the shared dependencies and concurrent compilation. For Legacy, the difference is - more noticeable. Even for cold builds, the new script cuts build times in half. - - Except for ComparisonKind and Quiet, all other parameters are the same as for the legacy build - script. - - .PARAMETER ComparisonKind - Defines how to compare the builds. Valid options are: - - - `Cold` (default): Before invoking either build script, delete both the `bin` and `target` - folders to ensure no use of cached build artifacts. - - `Warm`: Initially build the project to cache build artifacts in `target`. Then clean the - `bin` directory before invoking each build script. - - `Hot`: Same as `Warm`, but with `sccache` enabled (only tested on Windows). - - .PARAMETER Quiet - Defines whether to run the comparison with build output or not. In Quiet mode, only the progress - bars for the scripts are shown. All output for the build scripts is redirected to null. -#> -[CmdletBinding()] -param( - [ValidateSet('Cold', 'Warm', 'Hot')] - [string]$ComparisonKind = 'Cold', - [switch]$Quiet, - [switch]$Release, - [ValidateSet( - 'current', - 'aarch64-pc-windows-msvc', - 'x86_64-pc-windows-msvc', - 'aarch64-apple-darwin', - 'x86_64-apple-darwin', - 'aarch64-unknown-linux-gnu', - 'aarch64-unknown-linux-musl', - 'x86_64-unknown-linux-gnu', - 'x86_64-unknown-linux-musl' - )] - $Architecture = 'current', - [switch]$Clippy, - [switch]$SkipBuild, - [ValidateSet( - 'msix', - 'msix-private', - 'msixbundle', - 'tgz', - 'zip' - )] - $PackageType, - [switch]$Test, - [switch]$GetPackageVersion, - [switch]$SkipLinkCheck, - [switch]$UseX64MakeAppx, - [switch]$UseCFS, - [switch]$UpdateLockFile, - [switch]$Audit, - [switch]$UseCFSAuth, - [switch]$Clean -) - -begin { - $buildParams = [hashtable]$PSBoundParameters - if ($buildParams.ContainsKey('ComparisonKind')) { - $buildParams.Remove('ComparisonKind') - } - if ($buildParams.ContainsKey('Quiet')) { - $buildParams.Remove('Quiet') - } - - function Show-BuildParameters { - [cmdletbinding()] - param( - [hashtable]$BuildParams - ) - $sb = [System.Text.StringBuilder]::new() - $sb.Append("[") > $null - $padTo = 0 - foreach ($key in $BuildParams.Keys) { - if ($key.Length -ge $padTo) { - $padTo = $key.Length + 1 - } - } - foreach ($key in $BuildParams.Keys) { - $sb.Append("`n`t"). - Append($key). - Append((' ' * ($padTo - $key.Length))). - Append(': '). - Append($BuildParams[$key].ToString()) > $null - } - - $sb.Append("`n]") > $null - $sb.ToString() - } - - function Write-WithTimeStamp { - [cmdletbinding()] - param( - [string]$Message, - [System.ConsoleColor]$Color = 'Cyan' - ) - - $timestampFormat = "yyyy-MM-ddTHH:mm:ss.fff" - $Message = '{0} - {1}' -f @( - (Get-Date -AsUTC).ToString($timestampFormat) - $Message - ) - Write-Host -ForegroundColor $Color $Message - } - function Write-Divider { - param( - [System.ConsoleColor]$Color = 'Cyan' - ) - - Write-Host -ForegroundColor $Color ('-' * 80) - } - - function Measure-Build { - [CmdletBinding()] - param( - [ValidateSet('Cold', 'Warm', 'Hot')] - [string]$ComparisonKind = 'Cold', - [hashtable]$BuildParams, - [switch]$Quiet - ) - begin { - # Define variables - $durationFormat = "mm\:ss\.fff" - $legacyScript = Join-Path $PSScriptRoot "build.ps1" - $legacyBuildParams = $BuildParams.Clone() - $newScript = Join-Path $PSScriptRoot "build.new.ps1" - $newBuildParams = $BuildParams.Clone() - $binFolder = Join-Path $PSScriptRoot "bin" - $targetFolder = Join-Path $PSScriptRoot "target" - # Prerun steps - Write-Divider - Write-WithTimeStamp "Starting $ComparisonKind comparison" - Write-Divider - - $priorWrapper = $env:RUSTC_WRAPPER - $usingWrapper = -not [string]::IsNullOrEmpty($priorWrapper) - if ($ComparisonKind -eq 'Hot' -and -not $usingWrapper) { - if ($sccache = Get-Command -Name sccache -CommandType Application -ErrorAction Ignore) { - Write-WithTimeStamp "Using sccache for Hot comparison" - $env:RUSTC_WRAPPER = $sccache.Source - Write-WithTimeStamp "Set RUSTC_WRAPPER to: $env:RUSTC_WRAPPER" - } else { - Write-WithTimeStamp -Color Magenta (@( - "Unable to use sccache for Hot comparison, sccache not found." - 'Install `sccache` and ensure it is available in PATH.' - 'Continuing as a Warm comparison instead.' - ) -join ("`n" + (' ' * 25))) - $ComparisonKind = 'Warm' - } - } elseif ($ComparisonKind -eq 'Hot' -and $usingWrapper) { - Write-WithTimeStamp "Using previously configured cache wrapper for Hot comparison" - Write-WithTimeStamp "Set RUSTC_WRAPPER to: $env:RUSTC_WRAPPER" - } elseif ($usingWrapper) { - Write-WithTimeStamp "Temporarily disabling previously configured cache wrapper for $ComparisonKind comparison" - $env:RUSTC_WRAPPER = $null - Write-WithTimeStamp "Set RUSTC_WRAPPER to null." - } - - if ($ComparisonKind -eq 'Cold') { - Write-WithTimeStamp "Ensuring clean build conditions before comparison..." - if (Test-Path $binFolder) { - Remove-Item $binFolder -Recurse -Force - Write-WithTimeStamp "Removed bin folder." - } - if (Test-Path $targetFolder) { - Remove-Item $targetFolder -Recurse -Force - Write-WithTimeStamp "Removed target folder." - } - } else { - Write-WithTimeStamp "Pre-building project before $ComparisonKind comparison..." - Write-Divider - if ($Quiet) { - & $newScript @newBuildParams *>$null - } else { - & $newScript @newBuildParams - } - Write-Divider - Write-WithTimeStamp "Finished pre-build" - Write-Divider - } - } - - process { - #region New script - Write-Divider -Color Yellow - $message = 'Building with new script with parameters: {0}' -f (Show-BuildParameters $newBuildParams) - Write-WithTimeStamp -Color Yellow $message - Write-Divider -Color Yellow - $newStart = Get-Date -AsUTC - if ($Quiet) { - & $newScript @newBuildParams *>$null - } else { - & $newScript @newBuildParams - } - $newEnd = Get-Date -AsUTC - $newRun = $newEnd - $newStart - Write-Divider -Color Yellow - $message = 'Built with new script ({0})' -f $newRun.ToString($durationFormat) - Write-WithTimeStamp -Color Yellow $message - Write-Divider -Color Yellow - $newBinFiles = Get-ChildItem $binFolder -Recurse -File | ForEach-Object { - Resolve-Path -Relative -RelativeBasePath $binFolder -Path $_ - } - #endregion new script - #region Between executions - Write-Divider - if ($ComparisonKind -eq 'Cold') { - - Write-WithTimeStamp "Ensuring clean build conditions before for Cold comparison..." - if (Test-Path $binFolder) { - Remove-Item $binFolder -Recurse -Force > $null - Write-WithTimeStamp "Removed bin folder." - } - if (Test-Path $targetFolder) { - Remove-Item $targetFolder -Recurse -Force > $null - Write-WithTimeStamp "Removed target folder." - } - } else { - Write-WithTimeStamp "Removing bin folder before $ComparisonKind comparison..." - if (Test-Path $binFolder) { - Remove-Item $binFolder -Recurse -Force > $null - Write-WithTimeStamp "Removed bin folder." - } - } - Write-Divider - #endregion Between executions - - #region Legacy script - Write-Divider -Color Yellow - $message = 'Building with legacy script with parameters: {0}' -f (Show-BuildParameters $legacyBuildParams) - Write-WithTimeStamp -Color Yellow $message - Write-Divider -Color Yellow - $legacyStart = Get-Date -AsUTC - if ($Quiet) { - & $legacyScript @legacyBuildParams *>$null - } else { - & $legacyScript @legacyBuildParams - } - $legacyEnd = Get-Date -AsUTC - $legacyRun = $legacyEnd - $legacyStart - Write-Divider -Color Yellow - $message = 'Built with legacy script ({0})' -f $newRun.ToString($durationFormat) - Write-WithTimeStamp -Color Yellow $message - Write-Divider -Color Yellow - $legacyBinFiles = Get-ChildItem $binFolder -Recurse -File | ForEach-Object { - Resolve-Path -Relative -RelativeBasePath $binFolder -Path $_ - } - #endregion Legacy script - - #region Comparison - Write-Divider -Color Green - Write-WithTimeStamp -Color Green "Reporting on builds" - Write-Divider -Color Green - $info = if ($newRun -lt $legacyRun) { - [pscustomobject]@{ - FasterBuild = 'New' - SlowerBuild = 'Legacy' - ComparisonKind = $ComparisonKind - DifferenceDuration = $legacyRun - $newRun - DifferenceMultiplier = $legacyRun / $newRun - NewDuration = $newRun - LegacyDuration = $legacyRun - SameBinFiles = $null -eq (Compare-Object $newBinFiles $legacyBinFiles) - NewBinFiles = $newBinFiles - LegacyBinFiles = $legacyBinFiles - } - } else { - [pscustomobject]@{ - FasterBuild = 'Legacy' - SlowerBuild = 'New' - ComparisonKind = $ComparisonKind - DifferenceDuration = $newRun - $legacyRun - DifferenceMultiplier = $newRun / $legacyRun - NewDuration = $newRun - LegacyDuration = $legacyRun - SameBinFiles = $null -eq (Compare-Object $newBinFiles $legacyBinFiles) - NewBinFiles = $newBinFiles - LegacyBinFiles = $legacyBinFiles - } - } - Write-WithTimeStamp -Color Green ('{0} build script was {1:n2}x faster than {2}' -f @( - $info.FasterBuild - $info.DifferenceMultiplier - $info.SlowerBuild - )) - - Write-WithTimeStamp -Color Green "Details:`n`n$(($info | Format-List * -Force | Out-String).Trim())`n" - Write-Divider -Color Green - #endregion Comparison - } - - clean { - if ($priorWrapper -or $ComparisonKind -eq 'Hot') { - Write-Divider - Write-WithTimeStamp "Cleaning up" - Write-Divider - if ($priorWrapper) { - Write-WithTimeStamp "Resetting RUSTC_WRAPPER to prior wrapper" - $env:RUSTC_WRAPPER = $priorWrapper - Write-WithTimeStamp "Reset RUStC_WRAPPER to '$env:RUSTC_WRAPPER'" - } elseif ($ComparisonKind -eq 'Hot') { - Write-WithTimeStamp "Removing RUSTC_WRAPPER" - $env:RUSTC_WRAPPER = $null - Write-WithTimeStamp "Removed RUSTC_WRAPPER" - } - Write-Divider - Write-WithTimeStamp "Cleanup Finished" - Write-Divider - } - } - } -} - -process { - Measure-Build -ComparisonKind $ComparisonKind -BuildParams $buildParams -Quiet:$Quiet -} \ No newline at end of file diff --git a/build.new.ps1 b/build.new.ps1 deleted file mode 100644 index 8d5d25bec..000000000 --- a/build.new.ps1 +++ /dev/null @@ -1,318 +0,0 @@ -#!/usr/bin/env pwsh - -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -using module ./build.helpers.psm1 - -<# - .SYNOPSIS - DSC project build script - - .DESCRIPTION - - .PARAMETER Release - Determines whether to compile the Rust projects with the release profile. The release profile - uses significant optimizations for runtime size and speed but compiles much more slowly. - - .PARAMETER Architecture - Determines which platform architecture to compile for. The default architecture is `current`, - meaning the current operating system. You can specify an alternate architecture to compile for, - as Rust supports cross-compilation. - - Valid values are: - - - `current` (default) - The platform matching the current operating system. - - `aarch64-pc-windows-msvc` - Windows on ARM with MSVC build chain. - - `x86_64-pc-windows-msvc` - Windows on x86 with MSVC build chain. - - `aarch64-apple-darwin` - macOS on ARM. - - `x86_64-apple-darwin` - macOS on x86. - - `aarch64-unknown-linux-gnu` - Linux on ARM with the glibc build chain. - - `aarch64-unknown-linux-musl` - Linux on ARM with the musl build chain. - - `x86_64-unknown-linux-gnu` - Linux on x86 with the glibc build chain. - - `x86_64-unknown-linux-musl` - Linux on x86 with the musl build chain. - - When packaging, you _must_ specify a specific architecture. - - .PARAMETER Clippy - Determines whether to lint the Rust projects with Clippy. When you specify this parameter, the - build script lints the Rust projects before building them. Unlike the legacy script, it still - produces build artifacts unless a crate fails the linting. - - .PARAMETER SkipBuild - Determines whether to skip building the project. - - .PARAMETER Test - Determines whether to run Rust and Pester tests for the project. - - .PARAMETER GetPackageVersion - Short circuits the build to return the current version of the DSC CLI crate. - - .PARAMETER PackageType - Determines which package type to create. Must specify a single package type at a time. Valid - package types are: - - - `msix` - MSIX package, requires a specific architecture. - - `msix-private` - MSIX private package, requires a specific architecture. - - `msixbundle` - MSIX bundle package, builds for both Windows targets. - - `tgz` - Packages the project as a `.tar.gz` file, only for Linux/macOS. - - `zip` - Packages the project as a `.zip` file, only for Windows. -#> -[CmdletBinding()] -param( - [switch]$Release, - [ValidateSet( - 'current', - 'aarch64-pc-windows-msvc', - 'x86_64-pc-windows-msvc', - 'aarch64-apple-darwin', - 'x86_64-apple-darwin', - 'aarch64-unknown-linux-gnu', - 'aarch64-unknown-linux-musl', - 'x86_64-unknown-linux-gnu', - 'x86_64-unknown-linux-musl' - )] - $Architecture = 'current', - [switch]$Clippy, - [switch]$SkipBuild, - [ValidateSet( - 'msix', - 'msix-private', - 'msixbundle', - 'tgz', - 'zip' - )] - $PackageType, - [switch]$Test, - [string[]]$Project, - [switch]$ExcludeRustTests, - [switch]$ExcludePesterTests, - [ValidateSet("dsc", "adapters", "extensions", "grammars", "resources")] - [string[]]$PesterTestGroup, - [switch]$GetPackageVersion, - [switch]$SkipLinkCheck, - [switch]$UseX64MakeAppx, - [switch]$UseCFS, - [switch]$UpdateLockFile, - [switch]$Audit, - [switch]$UseCFSAuth, - [switch]$Clean, - [switch]$CacheRustBuild, - [switch]$RustDocs, - [switch]$Quiet -) - -begin { - if ($Quiet) { - $VerbosePreference = 'SilentlyContinue' - $InformationPreference = 'SilentlyContinue' - $ProgressPreference = 'Continue' - } else { - $InformationPreference = 'Continue' - $ProgressPreference = 'SilentlyContinue' - } - - Import-Module ./build.helpers.psm1 -Force -Verbose:$false - $usingADO = ($null -ne $env:TF_BUILD) - if ($usingADO -or $UseCFSAuth) { - $UseCFS = $true - } - # Import the build data - $BuildData = Import-DscBuildData -RefreshProjects - # Filter projects if needed. - if ($Project.Count -ge 1) { - $BuildData.Projects = $BuildData.Projects | Where-Object -FilterScript { - $_.Name -in $Project - } - } - $VerboseParam = @{} - if ($VerbosePreference -eq 'Continue' -and -not $Quiet) { - $VerboseParam.Verbose = $true - } - - function Write-BuildProgress { - [cmdletbinding()] - param( - [string]$Activity, - [string]$Status, - [switch]$Completed, - [switch]$Quiet - ) - - process { - if ($Quiet) { - $params = [hashtable]$PSBoundParameters - $params.Remove('Quiet') > $null - Write-Progress @params - } elseif ($Completed) { - Write-Information "Finished build script" - } else { - $message = "BUILD: $Activity" - if (-not [string]::IsNullOrEmpty($Status)) { - $message += "::$Status" - } - Write-Information $message - } - } - } -} - -process { - trap { - Write-Error "An error occurred: $($_ | Out-String)" - exit 1 - } - - if ($GetPackageVersion) { - return Get-DscCliVersion @VerboseParam - } - $progressParams = @{ - Activity = "Executing build script" - Quiet = $Quiet - } - Write-BuildProgress @progressParams - - #region Setup - $progressParams.Activity = 'Performing setup steps' - Write-BuildProgress @progressParams - Write-BuildProgress @progressParams -Status "Determining rustup info" - $rustup, $channel = Get-RustUp @VerboseParam - - if ($null -ne $PackageType) { - $SkipBuild = $true - } else { - Write-BuildProgress @progressParams -Status 'Configuring Rust environment' - [hashtable]$priorRustEnvironment = Set-RustEnvironment -CacheRustBuild:$CacheRustBuild @VerboseParam - Write-BuildProgress @progressParams -Status 'Configuring Cargo environment' - Set-CargoEnvironment @VerboseParam - - # Install or update rust - if (!$usingADO) { - Write-BuildProgress @progressParams -Status 'Ensuring Rust is up-to-date' - Update-Rust @VerboseParam - } - - if (!$usingADO) { - Write-BuildProgress @progressParams -Status 'Setting RustUp to default channel' - $rustup, $channel = Get-RustUp @VerboseParam - & $rustup default stable - } - - if ($Clippy) { - Write-BuildProgress @progressParams -Status 'Ensuring Clippy is available and updated' - Install-Clippy -UseCFS:$UseCFS -Architecture $Architecture @VerboseParam - } - - if (-not ($SkipBuild -and $Test -and $ExcludeRustTests)) { - # Install Node if needed - Write-BuildProgress @progressParams -Status 'Ensuring Node.JS is available' - Install-NodeJS @VerboseParam - - # Ensure tree-sitter is installed - Write-BuildProgress @progressParams -Status 'Ensuring tree-sitter is available' - Install-TreeSitter -UseCFS:$UseCFS @VerboseParam - } - } - - if (!$SkipBuild -and !$SkipLinkCheck -and $IsWindows) { - Write-BuildProgress @progressParams -Status "Ensuring Windows C++ build tools are available" - Install-WindowsCPlusPlusBuildTools @VerboseParam - } - #endregion Setup - - if (!$SkipBuild) { - $progressParams.Activity = 'Building the projects' - Write-BuildProgress @progressParams - Write-BuildProgress @progressParams -Status 'Generating grammar bindings' - Export-GrammarBinding -Project $BuildData.Projects @VerboseParam - - if ($RustDocs) { - $progressParams.Activity = 'Generating Rust documentation' - Write-BuildProgress @progressParams - - $docsParams = @{ - Project = $BuildData.Projects - Architecture = $Architecture - Release = $Release - } - Export-RustDocs @docsParams @VerboseParam - } else { - $buildParams = @{ - Project = $BuildData.Projects - Architecture = $Architecture - Release = $Release - Clean = $Clean - } - Write-BuildProgress @progressParams -Status 'Compiling Rust' - Build-RustProject @buildParams -Audit:$Audit -Clippy:$Clippy @VerboseParam - Write-BuildProgress @progressParams -Status "Copying build artifacts" - Copy-BuildArtifact @buildParams -ExecutableFile $BuildData.PackageFiles.Executable @VerboseParam - } - } - - # Ensure PATH includes the output artifacts after building and before testing. - if ((!$Clippy -and !$SkipBuild) -or $Test) { - $progressParams.Activity = 'Updating environment variables' - Write-BuildProgress @progressParams - Update-PathEnvironment -Architecture $Architecture -Release:$Release @VerboseParam - } - - if ($Test) { - $progressParams.Activity = 'Testing projects' - Write-BuildProgress @progressParams - - if (-not $ExcludeRustTests) { - $rustTestParams = @{ - Project = $BuildData.Projects - Architecture = $Architecture - Release = $Release - } - Write-BuildProgress @progressParams -Status "Testing Rust projects" - Test-RustProject @rustTestParams @VerboseParam - } - if ($RustDocs) { - $docTestParams = @{ - Project = $BuildData.Projects - Architecture = $Architecture - Release = $Release - Docs = $true - } - Write-BuildProgress @progressParams -Status "Testing documentation for Rust projects" - Test-RustProject @docTestParams @VerboseParam - } - if (-not $ExcludePesterTests) { - $installParams = @{ - UsingADO = $usingADO - } - $pesterParams = @{ - UsingADO = $usingADO - } - if ($null -ne $PesterTestGroup) { - $pesterParams.Group = $PesterTestGroup - } - Write-BuildProgress @progressParams -Status "Installing PowerShell test prerequisites" - Install-PowerShellTestPrerequisite @installParams @VerboseParam - Write-BuildProgress @progressParams -Status "Invoking pester" - Test-ProjectWithPester @pesterParams @VerboseParam - } - } - - if (-not [string]::IsNullOrEmpty($PackageType)) { - $progressParams.Activity = "Packaging" - $packageParams = @{ - BuildData = $BuildData - PackageType = $PackageType - Architecture = $Architecture - Release = $Release - } - Write-BuildProgress @progressParams - Build-DscPackage @packageParams @VerboseParam - } -} - -clean { - $progressParams.Activity = 'Cleaning up' - Write-BuildProgress @progressParams - Write-BuildProgress @progressParams -Status "Restoring rust environment" - Reset-RustEnvironment -PriorEnvironment $priorRustEnvironment @VerboseParam - Write-BuildProgress -Completed -} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 old mode 100755 new mode 100644 index cf94a1ac9..8d5d25bec --- a/build.ps1 +++ b/build.ps1 @@ -2,16 +2,92 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. - +using module ./build.helpers.psm1 + +<# + .SYNOPSIS + DSC project build script + + .DESCRIPTION + + .PARAMETER Release + Determines whether to compile the Rust projects with the release profile. The release profile + uses significant optimizations for runtime size and speed but compiles much more slowly. + + .PARAMETER Architecture + Determines which platform architecture to compile for. The default architecture is `current`, + meaning the current operating system. You can specify an alternate architecture to compile for, + as Rust supports cross-compilation. + + Valid values are: + + - `current` (default) - The platform matching the current operating system. + - `aarch64-pc-windows-msvc` - Windows on ARM with MSVC build chain. + - `x86_64-pc-windows-msvc` - Windows on x86 with MSVC build chain. + - `aarch64-apple-darwin` - macOS on ARM. + - `x86_64-apple-darwin` - macOS on x86. + - `aarch64-unknown-linux-gnu` - Linux on ARM with the glibc build chain. + - `aarch64-unknown-linux-musl` - Linux on ARM with the musl build chain. + - `x86_64-unknown-linux-gnu` - Linux on x86 with the glibc build chain. + - `x86_64-unknown-linux-musl` - Linux on x86 with the musl build chain. + + When packaging, you _must_ specify a specific architecture. + + .PARAMETER Clippy + Determines whether to lint the Rust projects with Clippy. When you specify this parameter, the + build script lints the Rust projects before building them. Unlike the legacy script, it still + produces build artifacts unless a crate fails the linting. + + .PARAMETER SkipBuild + Determines whether to skip building the project. + + .PARAMETER Test + Determines whether to run Rust and Pester tests for the project. + + .PARAMETER GetPackageVersion + Short circuits the build to return the current version of the DSC CLI crate. + + .PARAMETER PackageType + Determines which package type to create. Must specify a single package type at a time. Valid + package types are: + + - `msix` - MSIX package, requires a specific architecture. + - `msix-private` - MSIX private package, requires a specific architecture. + - `msixbundle` - MSIX bundle package, builds for both Windows targets. + - `tgz` - Packages the project as a `.tar.gz` file, only for Linux/macOS. + - `zip` - Packages the project as a `.zip` file, only for Windows. +#> +[CmdletBinding()] param( [switch]$Release, - [ValidateSet('current','aarch64-pc-windows-msvc','x86_64-pc-windows-msvc','aarch64-apple-darwin','x86_64-apple-darwin','aarch64-unknown-linux-gnu','aarch64-unknown-linux-musl','x86_64-unknown-linux-gnu','x86_64-unknown-linux-musl')] - $architecture = 'current', + [ValidateSet( + 'current', + 'aarch64-pc-windows-msvc', + 'x86_64-pc-windows-msvc', + 'aarch64-apple-darwin', + 'x86_64-apple-darwin', + 'aarch64-unknown-linux-gnu', + 'aarch64-unknown-linux-musl', + 'x86_64-unknown-linux-gnu', + 'x86_64-unknown-linux-musl' + )] + $Architecture = 'current', [switch]$Clippy, [switch]$SkipBuild, - [ValidateSet('msix','msix-private','msixbundle','tgz','zip','rpm','deb')] - $packageType, + [ValidateSet( + 'msix', + 'msix-private', + 'msixbundle', + 'tgz', + 'zip' + )] + $PackageType, [switch]$Test, + [string[]]$Project, + [switch]$ExcludeRustTests, + [switch]$ExcludePesterTests, + [ValidateSet("dsc", "adapters", "extensions", "grammars", "resources")] + [string[]]$PesterTestGroup, [switch]$GetPackageVersion, [switch]$SkipLinkCheck, [switch]$UseX64MakeAppx, @@ -20,1013 +96,223 @@ param( [switch]$Audit, [switch]$UseCFSAuth, [switch]$Clean, - [switch]$Verbose -) - -trap { - Write-Error "An error occurred: $($_ | Out-String)" - exit 1 -} - -$env:RUSTC_LOG=$null -$usingADO = ($null -ne $env:TF_BUILD) -if ($usingADO -or $UseCFSAuth) { - $UseCFS = $true -} - -if ($Verbose) { - $env:RUSTC_LOG='rustc_codegen_ssa::back::link=info' -} - -if ($GetPackageVersion) { - $match = Select-String -Path $PSScriptRoot/dsc/Cargo.toml -Pattern '^version\s*=\s*"(?.*?)"$' - if ($null -eq $match) { - throw 'Unable to find version in Cargo.toml' - } - - return $match.Matches.Groups[1].Value -} - -$filesForWindowsPackage = @( - 'appx.dsc.extension.json', - 'appx-discover.ps1', - 'bicep.dsc.extension.json', - 'dsc.exe', - 'dsc_default.settings.json', - 'dsc.settings.json', - 'dscecho.exe', - 'echo.dsc.resource.json', - 'assertion.dsc.resource.json', - 'group.dsc.resource.json', - 'include.dsc.resource.json', - 'NOTICE.txt', - 'osinfo.exe', - 'osinfo.dsc.resource.json', - 'powershell.dsc.resource.json', - 'psDscAdapter/', - 'psscript.ps1', - 'psscript.dsc.resource.json', - 'winpsscript.dsc.resource.json', - 'reboot_pending.dsc.resource.json', - 'reboot_pending.resource.ps1', - 'registry.dsc.resource.json', - 'registry.exe', - 'RunCommandOnSet.dsc.resource.json', - 'RunCommandOnSet.exe', - 'sshdconfig.exe', - 'sshd-windows.dsc.resource.json', - 'sshd_config.dsc.resource.json', - 'windowspowershell.dsc.resource.json', - 'wmi.dsc.resource.json', - 'wmi.resource.ps1', - 'wmiAdapter.psd1', - 'wmiAdapter.psm1', - 'windows_baseline.dsc.yaml', - 'windows_inventory.dsc.yaml' -) - -$filesForLinuxPackage = @( - 'bicep.dsc.extension.json', - 'dsc', - 'dsc_default.settings.json', - 'dsc.settings.json' - 'dscecho', - 'echo.dsc.resource.json', - 'assertion.dsc.resource.json', - 'apt.dsc.resource.json', - 'apt.dsc.resource.sh', - 'group.dsc.resource.json', - 'include.dsc.resource.json', - 'NOTICE.txt', - 'osinfo', - 'osinfo.dsc.resource.json', - 'powershell.dsc.resource.json', - 'psDscAdapter/', - 'psscript.ps1', - 'psscript.dsc.resource.json', - 'RunCommandOnSet.dsc.resource.json', - 'runcommandonset', - 'sshdconfig', - 'sshd_config.dsc.resource.json' -) - -$filesForMacPackage = @( - 'bicep.dsc.extension.json', - 'dsc', - 'dsc_default.settings.json', - 'dsc.settings.json' - 'dscecho', - 'echo.dsc.resource.json', - 'assertion.dsc.resource.json', - 'brew.dsc.resource.json', - 'brew.dsc.resource.sh', - 'group.dsc.resource.json', - 'include.dsc.resource.json', - 'NOTICE.txt', - 'osinfo', - 'osinfo.dsc.resource.json', - 'powershell.dsc.resource.json', - 'psDscAdapter/', - 'psscript.ps1', - 'psscript.dsc.resource.json', - 'RunCommandOnSet.dsc.resource.json', - 'runcommandonset', - 'sshdconfig', - 'sshd_config.dsc.resource.json' + [switch]$CacheRustBuild, + [switch]$RustDocs, + [switch]$Quiet ) -# the list of files other than the binaries which need to be executable -$filesToBeExecutable = @( - 'apt.dsc.resource.sh', - 'brew.dsc.resource.sh' -) - -function Find-LinkExe { - try { - # this helper may not be needed anymore, but keeping in case the install doesn't work for everyone - Write-Verbose -Verbose "Finding link.exe" - Push-Location $BuildToolsPath - Set-Location "$(Get-ChildItem -Directory | Sort-Object name -Descending | Select-Object -First 1)\bin\Host$($env:PROCESSOR_ARCHITECTURE)\x64" -ErrorAction Stop - $linkexe = (Get-Location).Path - Write-Verbose -Verbose "Using $linkexe" - $linkexe - } - finally { - Pop-Location - } -} - -$channel = 'stable' -if ($null -ne (Get-Command msrustup -CommandType Application -ErrorAction Ignore)) { - Write-Verbose -Verbose "Using msrustup" - $rustup = 'msrustup' - $channel = 'ms-stable' - if ($architecture -eq 'current') { - $env:MSRUSTUP_TOOLCHAIN = "$architecture" - } -} elseif ($null -ne (Get-Command rustup -CommandType Application -ErrorAction Ignore)) { - $rustup = 'rustup' -} - -if ($null -ne $packageType) { - $SkipBuild = $true -} else { - if ($UseCFS) { - Write-Host "Using CFS for cargo source replacement" - ${env:CARGO_SOURCE_crates-io_REPLACE_WITH} = $null - $env:CARGO_REGISTRIES_CRATESIO_INDEX = $null - - if ($UseCFSAuth) { - if ($null -eq (Get-Command 'az' -ErrorAction Ignore)) { - throw "Azure CLI not found" - } - - Write-Host "Getting token" - $accessToken = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv - if ($LASTEXITCODE -ne 0) { - Write-Warning "Failed to get access token, use 'az login' first, or use '-useCratesIO' to use crates.io. Proceeding with anonymous access." - } else { - $header = "Bearer $accessToken" - $env:CARGO_REGISTRIES_POWERSHELL_TOKEN = $header - $env:CARGO_REGISTRIES_POWERSHELL_CREDENTIAL_PROVIDER = 'cargo:token' - $env:CARGO_REGISTRIES_POWERSHELL_INDEX = "sparse+https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/powershell~force-auth/Cargo/index/" - } - } +begin { + if ($Quiet) { + $VerbosePreference = 'SilentlyContinue' + $InformationPreference = 'SilentlyContinue' + $ProgressPreference = 'Continue' } else { - # this will override the config.toml - Write-Host "Setting CARGO_SOURCE_crates-io_REPLACE_WITH to 'crates-io'" - ${env:CARGO_SOURCE_crates-io_REPLACE_WITH} = 'CRATESIO' - $env:CARGO_REGISTRIES_CRATESIO_INDEX = 'sparse+https://index.crates.io/' - } - - ## Test if Rust is installed - if (!$usingADO -and !(Get-Command 'cargo' -ErrorAction Ignore)) { - Write-Verbose -Verbose "Rust not found, installing..." - if (!$IsWindows) { - curl https://sh.rustup.rs -sSf | sh -s -- -y - $env:PATH += ":$env:HOME/.cargo/bin" - } - else { - Invoke-WebRequest 'https://static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe' -OutFile 'temp:/rustup-init.exe' - Write-Verbose -Verbose "Use the default settings to ensure build works" - & 'temp:/rustup-init.exe' -y - $env:PATH += ";$env:USERPROFILE\.cargo\bin" - Remove-Item temp:/rustup-init.exe -ErrorAction Ignore - } - if ($LASTEXITCODE -ne 0) { - throw "Failed to install Rust" - } - } - elseif (!$usingADO) { - Write-Verbose -Verbose "Rust found, updating..." - & $rustup update - } - - if ($IsWindows) { - $BuildToolsPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC" - } - - if (!$usingADO) { - & $rustup default stable - } - - if ($Clippy) { - Write-Verbose -Verbose "Installing clippy..." - if ($UseCFS) { - cargo install clippy --config .cargo/config.toml - } else { - if ($architecture -ne 'current') { - write-verbose -verbose "Installing clippy for $architecture" - rustup component add clippy --target $architecture + $InformationPreference = 'Continue' + $ProgressPreference = 'SilentlyContinue' + } + + Import-Module ./build.helpers.psm1 -Force -Verbose:$false + $usingADO = ($null -ne $env:TF_BUILD) + if ($usingADO -or $UseCFSAuth) { + $UseCFS = $true + } + # Import the build data + $BuildData = Import-DscBuildData -RefreshProjects + # Filter projects if needed. + if ($Project.Count -ge 1) { + $BuildData.Projects = $BuildData.Projects | Where-Object -FilterScript { + $_.Name -in $Project + } + } + $VerboseParam = @{} + if ($VerbosePreference -eq 'Continue' -and -not $Quiet) { + $VerboseParam.Verbose = $true + } + + function Write-BuildProgress { + [cmdletbinding()] + param( + [string]$Activity, + [string]$Status, + [switch]$Completed, + [switch]$Quiet + ) + + process { + if ($Quiet) { + $params = [hashtable]$PSBoundParameters + $params.Remove('Quiet') > $null + Write-Progress @params + } elseif ($Completed) { + Write-Information "Finished build script" } else { - write-verbose -verbose "Installing clippy for current architecture" - rustup component add clippy - } - } - if ($LASTEXITCODE -ne 0) { - throw "Failed to install clippy" - } - } - - ## Test if Node is installed - ## Skipping upgrade as users may have a specific version they want to use - if (!(Get-Command 'node' -ErrorAction Ignore)) { - Write-Verbose -Verbose "Node.js not found, installing..." - if (!$IsWindows) { - if (Get-Command 'brew' -ErrorAction Ignore) { - brew install node@24 - } else { - Write-Warning "Homebrew not found, please install Node.js manually" - } - } - else { - if (Get-Command 'winget' -ErrorAction Ignore) { - Write-Verbose -Verbose "Using winget to install Node.js" - winget install OpenJS.NodeJS --accept-source-agreements --accept-package-agreements --source winget --silent - } else { - Write-Warning "winget not found, please install Node.js manually" - } - } - if ($LASTEXITCODE -ne 0) { - throw "Failed to install Node.js" - } - } - - ## Test if tree-sitter is installed - if ($null -eq (Get-Command tree-sitter -ErrorAction Ignore)) { - Write-Verbose -Verbose "tree-sitter not found, installing..." - if ($UseCFS) { - cargo install tree-sitter-cli --config .cargo/config.toml - } else { - cargo install tree-sitter-cli - } - if ($LASTEXITCODE -ne 0) { - throw "Failed to install tree-sitter-cli" - } - } -} - -if (!$SkipBuild -and !$SkipLinkCheck -and $IsWindows -and !(Get-Command 'link.exe' -ErrorAction Ignore)) { - if (!(Test-Path $BuildToolsPath)) { - Write-Verbose -Verbose "link.exe not found, installing C++ build tools" - Invoke-WebRequest 'https://aka.ms/vs/17/release/vs_BuildTools.exe' -OutFile 'temp:/vs_buildtools.exe' - $arg = @('--passive','--add','Microsoft.VisualStudio.Workload.VCTools','--includerecommended') - if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { - $arg += '--add','Microsoft.VisualStudio.Component.VC.Tools.ARM64' - } - Start-Process -FilePath 'temp:/vs_buildtools.exe' -ArgumentList $arg -Wait - Remove-Item temp:/vs_installer.exe -ErrorAction Ignore - Write-Verbose -Verbose "Updating env vars" - $machineEnv = [environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::Machine).Split(';') - $userEnv = [environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::User).Split(';') - $pathEnv = ($env:PATH).Split(';') - foreach ($env in $machineEnv) { - if ($pathEnv -notcontains $env) { - $pathEnv += $env - } - } - foreach ($env in $userEnv) { - if ($pathEnv -notcontains $env) { - $pathEnv += $env - } - } - $env:PATH = $pathEnv -join ';' - } - - #$linkexe = Find-LinkExe - #$env:PATH += ";$linkexe" -} - -$configuration = $Release ? 'release' : 'debug' -$flags = @($Release ? '-r' : $null) -if ($architecture -eq 'current') { - $path = Join-Path $PSScriptRoot "target" $configuration - $target = Join-Path $PSScriptRoot 'bin' $configuration -} -else { - $flags += '--target' - $flags += $architecture - $path = Join-Path $PSScriptRoot 'target' $architecture $configuration - $target = Join-Path $PSScriptRoot 'bin' $architecture $configuration -} - -if (!$SkipBuild) { - if ($architecture -ne 'Current' -and !$usingADO) { - & $rustup target add --toolchain $channel $architecture - } - - if (Test-Path $target) { - Remove-Item $target -Recurse -ErrorAction Ignore - } - New-Item -ItemType Directory $target -ErrorAction Ignore > $null - - # make sure dependencies are built first so clippy runs correctly - $windows_projects = @("lib/dsc-lib-pal", "lib/dsc-lib-registry", "resources/registry", "resources/reboot_pending", "adapters/wmi", "configurations/windows", 'extensions/appx') - $macOS_projects = @("resources/brew") - $linux_projects = @("resources/apt") - - # projects are in dependency order - $projects = @( - ".", - "grammars/tree-sitter-dscexpression", - "grammars/tree-sitter-ssh-server-config", - "lib/dsc-lib-jsonschema", - "lib/dsc-lib-security_context", - "lib/dsc-lib-osinfo", - "lib/dsc-lib", - "dsc", - "resources/dscecho", - "extensions/bicep", - "resources/osinfo", - "adapters/powershell", - 'resources/PSScript', - "resources/process", - "resources/runcommandonset", - "resources/sshdconfig", - "tools/dsctest", - "tools/test_group_resource", - "y2j" - ) - $pedantic_unclean_projects = @() - $clippy_unclean_projects = @("grammars/tree-sitter-dscexpression", "grammars/tree-sitter-ssh-server-config") - $skip_test_projects_on_windows = @("grammars/tree-sitter-dscexpression", "grammars/tree-sitter-ssh-server-config") - - if ($IsWindows) { - $projects += $windows_projects - } - - if ($IsMacOS) { - $projects += $macOS_projects - } - - if ($IsLinux) { - $projects += $linux_projects - } - - $failed = $false - foreach ($project in $projects) { - ## Build format_json - Write-Host -ForegroundColor Cyan "Building '$project' for $architecture" - try { - Push-Location "$PSScriptRoot/$project" -ErrorAction Stop - Write-Verbose -Verbose "Current directory is $(Get-Location)" - - # check if the project is either tree-sitter-dscexpression or tree-sitter-ssh-server-config - if (($project -match 'tree-sitter-dscexpression$') -or ($project -match 'tree-sitter-ssh-server-config$')) { - if ($UpdateLockFile) { - cargo generate-lockfile - } - else { - if ($Audit) { - if ($null -eq (Get-Command cargo-audit -ErrorAction Ignore)) { - if ($UseCFS) { - cargo install cargo-audit --features=fix --config .cargo/config.toml - } else { - cargo install cargo-audit --features=fix - } - } - - cargo audit fix - } - - Write-Verbose -Verbose "Running build.ps1 for $project" - ./build.ps1 - } - } - - if ((Test-Path "./Cargo.toml")) - { - $isRepoRoot = $pwd.Path -eq $PSScriptRoot - if ($Clippy -and -not $isRepoRoot) { - if ($clippy_unclean_projects -contains $project) { - Write-Verbose -Verbose "Skipping clippy for $project" - } - elseif ($pedantic_unclean_projects -contains $project) { - Write-Verbose -Verbose "Running clippy for $project" - cargo clippy @flags -- -Dwarnings --no-deps - } - else { - Write-Verbose -Verbose "Running clippy with pedantic for $project" - cargo clippy @flags --% -- -Dwarnings -Dclippy::pedantic --no-deps - } - } - else { - if ($UpdateLockFile -and $isRepoRoot) { - cargo generate-lockfile - } - else { - if ($Audit) { - if ($null -eq (Get-Command cargo-audit -ErrorAction Ignore)) { - if ($UseCFS) { - cargo install cargo-audit --features=fix --config .cargo/config.toml - } else { - cargo install cargo-audit --features=fix - } - } - - cargo audit fix - } - - if ($Clean -and $isRepoRoot) { - cargo clean - } - - if (-not $isRepoRoot) { - Write-Verbose -Verbose "Building $project" - cargo build @flags - } - } + $message = "BUILD: $Activity" + if (-not [string]::IsNullOrEmpty($Status)) { + $message += "::$Status" } + Write-Information $message } - - if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { - Write-Error "Last exit code is $LASTEXITCODE, build failed for '$project'" - $failed = $true - break - } - - $binary = Split-Path $project -Leaf - - if ($IsWindows) { - Copy-Item "$path/$binary.exe" $target -ErrorAction Ignore -Verbose - Copy-Item "$path/$binary.pdb" $target -ErrorAction Ignore -Verbose - } - else { - Copy-Item "$path/$binary" $target -ErrorAction Ignore -Verbose - } - - if (Test-Path "./copy_files.txt") { - Get-Content "./copy_files.txt" | ForEach-Object { - # if the line contains a '\' character, throw an error - if ($_ -match '\\') { - throw "copy_files.txt should use '/' as the path separator" - } - # copy the file to the target directory, creating the directory path if needed - $fileCopyPath = $_.split('/') - if ($fileCopyPath.Length -gt 1) { - $fileCopyPath = $fileCopyPath[0..($fileCopyPath.Length - 2)] - $fileCopyPath = $fileCopyPath -join '/' - New-Item -ItemType Directory -Path "$target/$fileCopyPath" -Force -ErrorAction Ignore | Out-Null - } - Copy-Item $_ "$target/$_" -Force -ErrorAction Ignore - } - } - - if ($IsWindows) { - Copy-Item "*.dsc.resource.*","*.dsc.manifests.*" $target -Force -ErrorAction Ignore - } - else { # don't copy WindowsPowerShell resource manifest - $exclude = @('windowspowershell.dsc.resource.json', 'winpsscript.dsc.resource.json') - Copy-Item "*.dsc.resource.*","*.dsc.manifests.*" $target -Exclude $exclude -Force -ErrorAction Ignore - } - - # be sure that the files that should be executable are executable - if ($IsLinux -or $IsMacOS) { - foreach ($exeFile in $filesToBeExecutable) { - $exePath = "$target/$exeFile" - if (test-path $exePath) { - chmod +x $exePath - } - } - } - } catch { - Write-Error "Failed to build $project : $($_ | Out-String)" - $failed = $true - break - } finally { - Pop-Location } } - - if ($failed) { - Write-Host -ForegroundColor Red "Build failed" - exit 1 - } -} - -if (!$Clippy -and !$SkipBuild) { - $relative = Resolve-Path $target -Relative - Write-Host -ForegroundColor Green "`nEXE's are copied to $target ($relative)" - - # remove the other target in case switching between them - $dirSeparator = [System.IO.Path]::DirectorySeparatorChar - if ($Release) { - $oldTarget = $target.Replace($dirSeparator + 'release', $dirSeparator + 'debug') - } - else { - $oldTarget = $target.Replace($dirSeparator + 'debug', $dirSeparator + 'release') - } - $env:PATH = $env:PATH.Replace($oldTarget, '') - - $paths = $env:PATH.Split([System.IO.Path]::PathSeparator) - $found = $false - foreach ($path in $paths) { - if ($path -eq $target) { - $found = $true - break - } - } - - # remove empty entries from path - $env:PATH = [string]::Join([System.IO.Path]::PathSeparator, $env:PATH.Split([System.IO.Path]::PathSeparator, [StringSplitOptions]::RemoveEmptyEntries)) - - if (!$found) { - Write-Host -ForegroundCOlor Yellow "Adding $target to `$env:PATH" - $env:PATH = $target + [System.IO.Path]::PathSeparator + $env:PATH - } } -if ($Test) { - $failed = $false - $repository = 'PSGallery' - - if ($usingADO) { - $repository = 'CFS' - if ($null -eq (Get-PSResourceRepository -Name CFS -ErrorAction Ignore)) { - "Registering CFS repository" - Register-PSResourceRepository -uri 'https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/powershell/nuget/v2' -Name CFS -Trusted - } - } - - if ($IsWindows) { - # PSDesiredStateConfiguration module is needed for Microsoft.Windows/WindowsPowerShell adapter - $FullyQualifiedName = @{ModuleName="PSDesiredStateConfiguration";ModuleVersion="2.0.7"} - if (-not(Get-Module -ListAvailable -FullyQualifiedName $FullyQualifiedName)) - { - Install-PSResource -Name PSDesiredStateConfiguration -Version 2.0.7 -Repository $repository -TrustRepository - } - } - - if (-not(Get-Module -ListAvailable -Name Pester)) - { "Installing module Pester" - Install-PSResource Pester -WarningAction Ignore -Repository $repository -TrustRepository - } - - foreach ($project in $projects) { - # Skip repository root, otherwise it tests all workspace members, failing on not-Windows - if ($project -eq '.') { - continue - } - if ($IsWindows -and $skip_test_projects_on_windows -contains $project) { - Write-Verbose -Verbose "Skipping test for $project on Windows" - continue - } - - Write-Host -ForegroundColor Cyan "Testing $project ..." - try { - Push-Location "$PSScriptRoot/$project" - if (Test-Path "./Cargo.toml") - { - cargo test - - if ($LASTEXITCODE -ne 0) { - $failed = $true - } - } - } finally { - Pop-Location - } - } - - if ($failed) { - throw "Test failed" - } - - "PSModulePath is:" - $env:PSModulePath - "Pester module located in:" - (Get-Module -Name Pester -ListAvailable).Path - - # On Windows disable duplicated WinPS resources that break PSDesiredStateConfiguration module - if ($IsWindows) { - $a = $env:PSModulePath -split ";" | ? { $_ -notmatch 'WindowsPowerShell' } - $env:PSModulePath = $a -join ';' - - "Updated PSModulePath is:" - $env:PSModulePath - - if (-not(Get-Module -ListAvailable -Name Pester)) - { "Installing module Pester" - $InstallTargetDir = ($env:PSModulePath -split ";")[0] - Find-PSResource -Name 'Pester' -Repository $repository | Save-PSResource -Path $InstallTargetDir -TrustRepository - } - - "Updated Pester module location:" - (Get-Module -Name Pester -ListAvailable).Path - } - - Invoke-Pester -Output Detailed -ErrorAction Stop -} - -function Find-MakeAppx() { - $makeappx = Get-Command makeappx -CommandType Application -ErrorAction Ignore - if ($null -eq $makeappx) { - # try to find - if (!$UseX64MakeAppx -and $architecture -eq 'aarch64-pc-windows-msvc') { - $arch = 'arm64' - } - else { - $arch = 'x64' - } - - $makeappx = Get-ChildItem -Recurse -Path (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin\*\' $arch) -Filter makeappx.exe | Sort-Object FullName -Descending | Select-Object -First 1 - if ($null -eq $makeappx) { - throw "makeappx not found, please install Windows SDK" - } +process { + trap { + Write-Error "An error occurred: $($_ | Out-String)" + exit 1 } - $makeappx -} - -$productVersion = (((Get-Content $PSScriptRoot/dsc/Cargo.toml) -match '^version\s*=\s*') -replace 'version\s*=\s*"(.*?)"', '$1').Trim() - -if ($packageType -eq 'msixbundle') { - if (!$IsWindows) { - throw "MsixBundle is only supported on Windows" + if ($GetPackageVersion) { + return Get-DscCliVersion @VerboseParam } - - $packageName = "DSC-$productVersion-Win" - $makeappx = Find-MakeAppx - $msixPath = Join-Path $PSScriptRoot 'bin' 'msix' - & $makeappx bundle /d $msixPath /p "$PSScriptRoot\bin\$packageName.msixbundle" - return -} elseif ($packageType -eq 'msix' -or $packageType -eq 'msix-private') { - if (!$IsWindows) { - throw "MSIX is only supported on Windows" + $progressParams = @{ + Activity = "Executing build script" + Quiet = $Quiet } + Write-BuildProgress @progressParams - if ($architecture -eq 'current') { - throw 'MSIX requires a specific architecture' - } + #region Setup + $progressParams.Activity = 'Performing setup steps' + Write-BuildProgress @progressParams + Write-BuildProgress @progressParams -Status "Determining rustup info" + $rustup, $channel = Get-RustUp @VerboseParam - $isPrivate = $packageType -eq 'msix-private' + if ($null -ne $PackageType) { + $SkipBuild = $true + } else { + Write-BuildProgress @progressParams -Status 'Configuring Rust environment' + [hashtable]$priorRustEnvironment = Set-RustEnvironment -CacheRustBuild:$CacheRustBuild @VerboseParam + Write-BuildProgress @progressParams -Status 'Configuring Cargo environment' + Set-CargoEnvironment @VerboseParam - $makeappx = Find-MakeAppx - $makepri = Get-Item (Join-Path $makeappx.Directory "makepri.exe") -ErrorAction Stop - $displayName = "DesiredStateConfiguration" - $isPreview = $productVersion -like '*-*' - $productName = "DesiredStateConfiguration" - if ($isPreview) { - Write-Verbose -Verbose "Preview version detected" - if ($isPrivate) { - $productName += "-Private" - } - else { - $productName += "-Preview" + # Install or update rust + if (!$usingADO) { + Write-BuildProgress @progressParams -Status 'Ensuring Rust is up-to-date' + Update-Rust @VerboseParam } - # save preview number - $previewNumber = [int]($productVersion -replace '.*?-[a-z]+\.([0-9]+)', '$1' | Out-String) - $productLabel = $productVersion.Split('-')[1] - if ($productLabel.StartsWith('rc')) { - # if RC, we increment by 100 to ensure it's newer than the last preview - $previewNumber += 100 - } - # remove label from version - $productVersion = $productVersion.Split('-')[0] - # replace revision number with preview number - $productVersion = $productVersion -replace '(\d+)$', "$previewNumber.0" - if ($isPrivate) { - $displayName += "-Private" - } - else { - $displayName += "-Preview" + if (!$usingADO) { + Write-BuildProgress @progressParams -Status 'Setting RustUp to default channel' + $rustup, $channel = Get-RustUp @VerboseParam + & $rustup default stable } - } - else { - # appx requires a version in the format of major.minor.build.revision with revision being 0 - $productVersion += ".0" - } - Write-Verbose -Verbose "Product version is $productVersion" - $arch = if ($architecture -eq 'aarch64-pc-windows-msvc') { 'arm64' } else { 'x64' } - - # Appx manifest needs to be in root of source path, but the embedded version needs to be updated - # cp-459155 is 'CN=Microsoft Windows Store Publisher (Store EKU), O=Microsoft Corporation, L=Redmond, S=Washington, C=US' - # authenticodeFormer is 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' - $releasePublisher = 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' - - $appxManifest = Get-Content "$PSScriptRoot\packaging\msix\AppxManifest.xml" -Raw - $appxManifest = $appxManifest.Replace('$VERSION$', $ProductVersion).Replace('$ARCH$', $Arch).Replace('$PRODUCTNAME$', $productName).Replace('$DISPLAYNAME$', $displayName).Replace('$PUBLISHER$', $releasePublisher) - $msixTarget = Join-Path $PSScriptRoot 'bin' $architecture 'msix' - if (Test-Path $msixTarget) { - Remove-Item $msixTarget -Recurse -ErrorAction Stop -Force - } - - New-Item -ItemType Directory $msixTarget > $null - Set-Content -Path "$msixTarget\AppxManifest.xml" -Value $appxManifest -Force - - foreach ($file in $filesForWindowsPackage) { - if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { - Copy-Item "$target\$file" "$msixTarget\$file" -Recurse -ErrorAction Stop - } else { - Copy-Item "$target\$file" $msixTarget -ErrorAction Stop + if ($Clippy) { + Write-BuildProgress @progressParams -Status 'Ensuring Clippy is available and updated' + Install-Clippy -UseCFS:$UseCFS -Architecture $Architecture @VerboseParam } - } - - # Necessary image assets need to be in source assets folder - $assets = @( - 'Square150x150Logo' - 'Square64x64Logo' - 'Square44x44Logo' - 'Square44x44Logo.targetsize-48' - 'Square44x44Logo.targetsize-48_altform-unplated' - 'StoreLogo' - ) - New-Item -ItemType Directory "$msixTarget\assets" > $null - foreach ($asset in $assets) { - Copy-Item "$PSScriptRoot\packaging\assets\$asset.png" "$msixTarget\assets" -ErrorAction Stop - } - - Write-Verbose "Creating priconfig.xml" -Verbose - & $makepri createconfig /o /cf (Join-Path $msixTarget "priconfig.xml") /dq en-US - if ($LASTEXITCODE -ne 0) { - throw "Failed to create priconfig.xml" - } - - Write-Verbose "Creating resources.pri" -Verbose - Push-Location $msixTarget - & $makepri new /v /o /pr $msixTarget /cf (Join-Path $msixTarget "priconfig.xml") - Pop-Location - if ($LASTEXITCODE -ne 0) { - throw "Failed to create resources.pri" - } - - Write-Verbose "Creating msix package" -Verbose - - $targetFolder = Join-Path $PSScriptRoot 'bin' 'msix' - if (Test-Path $targetFolder) { - Remove-Item $targetFolder -Recurse -ErrorAction Stop -Force - } else { - New-Item -ItemType Directory $targetFolder > $null - } - - $packageName = Join-Path $targetFolder "$productName-$productVersion-$arch.msix" - & $makeappx pack /o /v /h SHA256 /d $msixTarget /p $packageName - if ($LASTEXITCODE -ne 0) { - throw "Failed to create msix package" - } - - Write-Host -ForegroundColor Green "`nMSIX package is created at $packageName" -} elseif ($packageType -eq 'zip') { - $zipTarget = Join-Path $PSScriptRoot 'bin' $architecture 'zip' - if (Test-Path $zipTarget) { - Remove-Item $zipTarget -Recurse -ErrorAction Stop -Force - } - - New-Item -ItemType Directory $zipTarget > $null - - foreach ($file in $filesForWindowsPackage) { - if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { - Copy-Item "$target\$file" "$zipTarget\$file" -Recurse -ErrorAction Stop - } else { - Copy-Item "$target\$file" $zipTarget -ErrorAction Stop + if (-not ($SkipBuild -and $Test -and $ExcludeRustTests)) { + # Install Node if needed + Write-BuildProgress @progressParams -Status 'Ensuring Node.JS is available' + Install-NodeJS @VerboseParam + + # Ensure tree-sitter is installed + Write-BuildProgress @progressParams -Status 'Ensuring tree-sitter is available' + Install-TreeSitter -UseCFS:$UseCFS @VerboseParam } } - $packageName = "DSC-$productVersion-$architecture.zip" - $zipFile = Join-Path $PSScriptRoot 'bin' $packageName - Compress-Archive -Path "$zipTarget/*" -DestinationPath $zipFile -Force - Write-Host -ForegroundColor Green "`nZip file is created at $zipFile" -} elseif ($packageType -eq 'tgz') { - $tgzTarget = Join-Path $PSScriptRoot 'bin' $architecture 'tgz' - if (Test-Path $tgzTarget) { - Remove-Item $tgzTarget -Recurse -ErrorAction Stop -Force + if (!$SkipBuild -and !$SkipLinkCheck -and $IsWindows) { + Write-BuildProgress @progressParams -Status "Ensuring Windows C++ build tools are available" + Install-WindowsCPlusPlusBuildTools @VerboseParam } + #endregion Setup - New-Item -ItemType Directory $tgzTarget > $null + if (!$SkipBuild) { + $progressParams.Activity = 'Building the projects' + Write-BuildProgress @progressParams + Write-BuildProgress @progressParams -Status 'Generating grammar bindings' + Export-GrammarBinding -Project $BuildData.Projects @VerboseParam - if ($IsLinux) { - $filesForPackage = $filesForLinuxPackage - } elseif ($IsMacOS) { - $filesForPackage = $filesForMacPackage - } else { - Write-Error "Unsupported platform for tgz package" - } + if ($RustDocs) { + $progressParams.Activity = 'Generating Rust documentation' + Write-BuildProgress @progressParams - foreach ($file in $filesForPackage) { - if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { - Copy-Item "$target\$file" "$tgzTarget\$file" -Recurse -ErrorAction Stop + $docsParams = @{ + Project = $BuildData.Projects + Architecture = $Architecture + Release = $Release + } + Export-RustDocs @docsParams @VerboseParam } else { - Copy-Item "$target\$file" $tgzTarget -ErrorAction Stop + $buildParams = @{ + Project = $BuildData.Projects + Architecture = $Architecture + Release = $Release + Clean = $Clean + } + Write-BuildProgress @progressParams -Status 'Compiling Rust' + Build-RustProject @buildParams -Audit:$Audit -Clippy:$Clippy @VerboseParam + Write-BuildProgress @progressParams -Status "Copying build artifacts" + Copy-BuildArtifact @buildParams -ExecutableFile $BuildData.PackageFiles.Executable @VerboseParam } } - # for Linux, we only build musl as its statically linked, so we remove the musl suffix - $productArchitecture = if ($architecture -eq 'aarch64-unknown-linux-musl') { - 'aarch64-linux' - } elseif ($architecture -eq 'x86_64-unknown-linux-musl') { - 'x86_64-linux' - } else { - $architecture - } - - Write-Verbose -Verbose "Creating tar.gz file" - $packageName = "DSC-$productVersion-$productArchitecture.tar.gz" - $tarFile = Join-Path $PSScriptRoot 'bin' $packageName - tar -czvf $tarFile -C $tgzTarget . - if ($LASTEXITCODE -ne 0) { - throw "Failed to create tar.gz file" + # Ensure PATH includes the output artifacts after building and before testing. + if ((!$Clippy -and !$SkipBuild) -or $Test) { + $progressParams.Activity = 'Updating environment variables' + Write-BuildProgress @progressParams + Update-PathEnvironment -Architecture $Architecture -Release:$Release @VerboseParam } - # check it's valid - $out = file $tarFile - if ($out -notmatch 'gzip compressed data') { - throw "Invalid tar.gz file" - } - - Write-Host -ForegroundColor Green "`ntar.gz file is created at $tarFile" -} elseif ($packageType -eq 'rpm') { - if (!$IsLinux) { - throw "RPM package creation is only supported on Linux" - } - - # Check if rpmbuild is available - if ($null -eq (Get-Command rpmbuild -ErrorAction Ignore)) { - throw "rpmbuild not found. Please install rpm-build package (e.g., 'sudo apt install rpm build-essential' or 'sudo dnf install rpm-build')" - } - - $rpmTarget = Join-Path $PSScriptRoot 'bin' $architecture 'rpm' - if (Test-Path $rpmTarget) { - Remove-Item $rpmTarget -Recurse -ErrorAction Stop -Force - } - - New-Item -ItemType Directory $rpmTarget > $null - - # Create RPM build directories - $rpmBuildRoot = Join-Path $rpmTarget 'rpmbuild' - $rpmDirs = @('BUILD', 'RPMS', 'SOURCES', 'SPECS', 'SRPMS') - foreach ($dir in $rpmDirs) { - New-Item -ItemType Directory -Path (Join-Path $rpmBuildRoot $dir) -Force > $null - } - - # Create a staging directory for the files - $stagingDir = Join-Path $rpmBuildRoot 'SOURCES' 'dsc_files' - New-Item -ItemType Directory $stagingDir > $null - - $filesForPackage = $filesForLinuxPackage - - foreach ($file in $filesForPackage) { - if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { - Copy-Item "$target\$file" "$stagingDir\$file" -Recurse -ErrorAction Stop - } else { - Copy-Item "$target\$file" $stagingDir -ErrorAction Stop - } - } - - # Determine RPM architecture - $rpmArch = if ($architecture -eq 'current') { - # Detect current system architecture - $currentArch = uname -m - if ($currentArch -eq 'x86_64') { - 'x86_64' - } elseif ($currentArch -eq 'aarch64') { - 'aarch64' - } else { - throw "Unsupported current architecture for RPM: $currentArch" + if ($Test) { + $progressParams.Activity = 'Testing projects' + Write-BuildProgress @progressParams + + if (-not $ExcludeRustTests) { + $rustTestParams = @{ + Project = $BuildData.Projects + Architecture = $Architecture + Release = $Release + } + Write-BuildProgress @progressParams -Status "Testing Rust projects" + Test-RustProject @rustTestParams @VerboseParam + } + if ($RustDocs) { + $docTestParams = @{ + Project = $BuildData.Projects + Architecture = $Architecture + Release = $Release + Docs = $true + } + Write-BuildProgress @progressParams -Status "Testing documentation for Rust projects" + Test-RustProject @docTestParams @VerboseParam } - } elseif ($architecture -eq 'aarch64-unknown-linux-musl' -or $architecture -eq 'aarch64-unknown-linux-gnu') { - 'aarch64' - } elseif ($architecture -eq 'x86_64-unknown-linux-musl' -or $architecture -eq 'x86_64-unknown-linux-gnu') { - 'x86_64' - } else { - throw "Unsupported architecture for RPM: $architecture" - } - - # Read the spec template and replace placeholders - $specTemplate = Get-Content "$PSScriptRoot/packaging/rpm/dsc.spec" -Raw - $specContent = $specTemplate.Replace('VERSION_PLACEHOLDER', $productVersion.Replace('-','~')).Replace('ARCH_PLACEHOLDER', $rpmArch) - $specFile = Join-Path $rpmBuildRoot 'SPECS' 'dsc.spec' - Set-Content -Path $specFile -Value $specContent - - Write-Verbose -Verbose "Building RPM package" - $rpmPackageName = "dsc-$productVersion-1.$rpmArch.rpm" - - # Build the RPM - rpmbuild -v -bb --define "_topdir $rpmBuildRoot" --buildroot "$rpmBuildRoot/BUILDROOT" $specFile 2>&1 > $rpmTarget/rpmbuild.log - - if ($LASTEXITCODE -ne 0) { - Write-Error (Get-Content $rpmTarget/rpmbuild.log -Raw) - throw "Failed to create RPM package" - } - - # Copy the RPM to the bin directory - $builtRpm = Get-ChildItem -Path (Join-Path $rpmBuildRoot 'RPMS') -Recurse -Filter '*.rpm' | Select-Object -First 1 - if ($null -eq $builtRpm) { - throw "RPM package was not created" - } - - $finalRpmPath = Join-Path $PSScriptRoot 'bin' $builtRpm.Name - Copy-Item $builtRpm.FullName $finalRpmPath -Force - - Write-Host -ForegroundColor Green "`nRPM package is created at $finalRpmPath" -} elseif ($packageType -eq 'deb') { - if (!$IsLinux) { - throw "DEB package creation is only supported on Linux" - } - - # Check if dpkg-deb is available - if ($null -eq (Get-Command dpkg-deb -ErrorAction Ignore)) { - throw "dpkg-deb not found. Please install dpkg package (e.g., 'sudo apt install dpkg' or 'sudo dnf install dpkg')" - } - - $debTarget = Join-Path $PSScriptRoot 'bin' $architecture 'deb' - if (Test-Path $debTarget) { - Remove-Item $debTarget -Recurse -ErrorAction Stop -Force - } - - New-Item -ItemType Directory $debTarget > $null - - # Create DEB package structure - $debBuildRoot = Join-Path $debTarget 'dsc' - $debDirs = @('DEBIAN', 'opt/dsc', 'usr/bin') - foreach ($dir in $debDirs) { - New-Item -ItemType Directory -Path (Join-Path $debBuildRoot $dir) -Force > $null - } - - # Copy files to the package directory - $filesForPackage = $filesForLinuxPackage - $stagingDir = Join-Path $debBuildRoot 'opt' 'dsc' - - foreach ($file in $filesForPackage) { - if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { - Copy-Item "$target\$file" "$stagingDir\$file" -Recurse -ErrorAction Stop - } else { - Copy-Item "$target\$file" $stagingDir -ErrorAction Stop + if (-not $ExcludePesterTests) { + $installParams = @{ + UsingADO = $usingADO + } + $pesterParams = @{ + UsingADO = $usingADO + } + if ($null -ne $PesterTestGroup) { + $pesterParams.Group = $PesterTestGroup + } + Write-BuildProgress @progressParams -Status "Installing PowerShell test prerequisites" + Install-PowerShellTestPrerequisite @installParams @VerboseParam + Write-BuildProgress @progressParams -Status "Invoking pester" + Test-ProjectWithPester @pesterParams @VerboseParam } } - # Create symlink in usr/bin - $symlinkPath = Join-Path $debBuildRoot 'usr' 'bin' 'dsc' - New-Item -ItemType SymbolicLink -Path $symlinkPath -Target '/opt/dsc/dsc' -Force > $null - - # Determine DEB architecture - $debArch = if ($architecture -eq 'current') { - # Detect current system architecture - $currentArch = uname -m - if ($currentArch -eq 'x86_64') { - 'amd64' - } elseif ($currentArch -eq 'aarch64') { - 'arm64' - } else { - throw "Unsupported current architecture for DEB: $currentArch" + if (-not [string]::IsNullOrEmpty($PackageType)) { + $progressParams.Activity = "Packaging" + $packageParams = @{ + BuildData = $BuildData + PackageType = $PackageType + Architecture = $Architecture + Release = $Release } - } elseif ($architecture -eq 'aarch64-unknown-linux-musl' -or $architecture -eq 'aarch64-unknown-linux-gnu') { - 'arm64' - } elseif ($architecture -eq 'x86_64-unknown-linux-musl' -or $architecture -eq 'x86_64-unknown-linux-gnu') { - 'amd64' - } else { - throw "Unsupported architecture for DEB: $architecture" - } - - # Read the control template and replace placeholders - $controlTemplate = Get-Content "$PSScriptRoot/packaging/deb/control" -Raw - $controlContent = $controlTemplate.Replace('VERSION_PLACEHOLDER', $productVersion).Replace('ARCH_PLACEHOLDER', $debArch) - $controlFile = Join-Path $debBuildRoot 'DEBIAN' 'control' - Set-Content -Path $controlFile -Value $controlContent - - Write-Verbose -Verbose "Building DEB package" - $debPackageName = "dsc_$productVersion-1_$debArch.deb" - - # Build the DEB - dpkg-deb --build $debBuildRoot 2>&1 > $debTarget/debbuild.log - - if ($LASTEXITCODE -ne 0) { - Write-Error (Get-Content $debTarget/debbuild.log -Raw) - throw "Failed to create DEB package" + Write-BuildProgress @progressParams + Build-DscPackage @packageParams @VerboseParam } - - # Move the DEB to the bin directory with the correct name - $builtDeb = "$debBuildRoot.deb" - if (!(Test-Path $builtDeb)) { - throw "DEB package was not created" - } - - $finalDebPath = Join-Path $PSScriptRoot 'bin' $debPackageName - Move-Item $builtDeb $finalDebPath -Force - - Write-Host -ForegroundColor Green "`nDEB package is created at $finalDebPath" } -$env:RUST_BACKTRACE=1 +clean { + $progressParams.Activity = 'Cleaning up' + Write-BuildProgress @progressParams + Write-BuildProgress @progressParams -Status "Restoring rust environment" + Reset-RustEnvironment -PriorEnvironment $priorRustEnvironment @VerboseParam + Write-BuildProgress -Completed +} \ No newline at end of file diff --git a/packaging.ps1 b/packaging.ps1 new file mode 100755 index 000000000..b94f9c492 --- /dev/null +++ b/packaging.ps1 @@ -0,0 +1,1032 @@ +#!/usr/bin/env pwsh + +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +param( + [switch]$Release, + [ValidateSet('current','aarch64-pc-windows-msvc','x86_64-pc-windows-msvc','aarch64-apple-darwin','x86_64-apple-darwin','aarch64-unknown-linux-gnu','aarch64-unknown-linux-musl','x86_64-unknown-linux-gnu','x86_64-unknown-linux-musl')] + $architecture = 'current', + [switch]$Clippy, + [switch]$SkipBuild, + [ValidateSet('msix','msix-private','msixbundle','tgz','zip','rpm','deb')] + $packageType, + [switch]$Test, + [switch]$GetPackageVersion, + [switch]$SkipLinkCheck, + [switch]$UseX64MakeAppx, + [switch]$UseCFS, + [switch]$UpdateLockFile, + [switch]$Audit, + [switch]$UseCFSAuth, + [switch]$Clean, + [switch]$Verbose +) + +trap { + Write-Error "An error occurred: $($_ | Out-String)" + exit 1 +} + +$env:RUSTC_LOG=$null +$usingADO = ($null -ne $env:TF_BUILD) +if ($usingADO -or $UseCFSAuth) { + $UseCFS = $true +} + +if ($Verbose) { + $env:RUSTC_LOG='rustc_codegen_ssa::back::link=info' +} + +if ($GetPackageVersion) { + $match = Select-String -Path $PSScriptRoot/dsc/Cargo.toml -Pattern '^version\s*=\s*"(?.*?)"$' + if ($null -eq $match) { + throw 'Unable to find version in Cargo.toml' + } + + return $match.Matches.Groups[1].Value +} + +$filesForWindowsPackage = @( + 'appx.dsc.extension.json', + 'appx-discover.ps1', + 'bicep.dsc.extension.json', + 'dsc.exe', + 'dsc_default.settings.json', + 'dsc.settings.json', + 'dscecho.exe', + 'echo.dsc.resource.json', + 'assertion.dsc.resource.json', + 'group.dsc.resource.json', + 'include.dsc.resource.json', + 'NOTICE.txt', + 'osinfo.exe', + 'osinfo.dsc.resource.json', + 'powershell.dsc.resource.json', + 'psDscAdapter/', + 'psscript.ps1', + 'psscript.dsc.resource.json', + 'winpsscript.dsc.resource.json', + 'reboot_pending.dsc.resource.json', + 'reboot_pending.resource.ps1', + 'registry.dsc.resource.json', + 'registry.exe', + 'RunCommandOnSet.dsc.resource.json', + 'RunCommandOnSet.exe', + 'sshdconfig.exe', + 'sshd-windows.dsc.resource.json', + 'sshd_config.dsc.resource.json', + 'windowspowershell.dsc.resource.json', + 'wmi.dsc.resource.json', + 'wmi.resource.ps1', + 'wmiAdapter.psd1', + 'wmiAdapter.psm1', + 'windows_baseline.dsc.yaml', + 'windows_inventory.dsc.yaml' +) + +$filesForLinuxPackage = @( + 'bicep.dsc.extension.json', + 'dsc', + 'dsc_default.settings.json', + 'dsc.settings.json', + 'dscecho', + 'echo.dsc.resource.json', + 'assertion.dsc.resource.json', + 'apt.dsc.resource.json', + 'apt.dsc.resource.sh', + 'group.dsc.resource.json', + 'include.dsc.resource.json', + 'NOTICE.txt', + 'osinfo', + 'osinfo.dsc.resource.json', + 'powershell.dsc.resource.json', + 'psDscAdapter/', + 'psscript.ps1', + 'psscript.dsc.resource.json', + 'RunCommandOnSet.dsc.resource.json', + 'runcommandonset', + 'sshdconfig', + 'sshd_config.dsc.resource.json' +) + +$filesForMacPackage = @( + 'bicep.dsc.extension.json', + 'dsc', + 'dsc_default.settings.json', + 'dsc.settings.json', + 'dscecho', + 'echo.dsc.resource.json', + 'assertion.dsc.resource.json', + 'brew.dsc.resource.json', + 'brew.dsc.resource.sh', + 'group.dsc.resource.json', + 'include.dsc.resource.json', + 'NOTICE.txt', + 'osinfo', + 'osinfo.dsc.resource.json', + 'powershell.dsc.resource.json', + 'psDscAdapter/', + 'psscript.ps1', + 'psscript.dsc.resource.json', + 'RunCommandOnSet.dsc.resource.json', + 'runcommandonset', + 'sshdconfig', + 'sshd_config.dsc.resource.json' +) + +# the list of files other than the binaries which need to be executable +$filesToBeExecutable = @( + 'apt.dsc.resource.sh', + 'brew.dsc.resource.sh' +) + +function Find-LinkExe { + try { + # this helper may not be needed anymore, but keeping in case the install doesn't work for everyone + Write-Verbose -Verbose "Finding link.exe" + Push-Location $BuildToolsPath + Set-Location "$(Get-ChildItem -Directory | Sort-Object name -Descending | Select-Object -First 1)\bin\Host$($env:PROCESSOR_ARCHITECTURE)\x64" -ErrorAction Stop + $linkexe = (Get-Location).Path + Write-Verbose -Verbose "Using $linkexe" + $linkexe + } + finally { + Pop-Location + } +} + +$channel = 'stable' +if ($null -ne (Get-Command msrustup -CommandType Application -ErrorAction Ignore)) { + Write-Verbose -Verbose "Using msrustup" + $rustup = 'msrustup' + $channel = 'ms-stable' + if ($architecture -eq 'current') { + $env:MSRUSTUP_TOOLCHAIN = "$architecture" + } +} elseif ($null -ne (Get-Command rustup -CommandType Application -ErrorAction Ignore)) { + $rustup = 'rustup' +} + +if ($null -ne $packageType) { + $SkipBuild = $true +} else { + if ($UseCFS) { + Write-Host "Using CFS for cargo source replacement" + ${env:CARGO_SOURCE_crates-io_REPLACE_WITH} = $null + $env:CARGO_REGISTRIES_CRATESIO_INDEX = $null + + if ($UseCFSAuth) { + if ($null -eq (Get-Command 'az' -ErrorAction Ignore)) { + throw "Azure CLI not found" + } + + Write-Host "Getting token" + $accessToken = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv + if ($LASTEXITCODE -ne 0) { + Write-Warning "Failed to get access token, use 'az login' first, or use '-useCratesIO' to use crates.io. Proceeding with anonymous access." + } else { + $header = "Bearer $accessToken" + $env:CARGO_REGISTRIES_POWERSHELL_TOKEN = $header + $env:CARGO_REGISTRIES_POWERSHELL_CREDENTIAL_PROVIDER = 'cargo:token' + $env:CARGO_REGISTRIES_POWERSHELL_INDEX = "sparse+https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/powershell~force-auth/Cargo/index/" + } + } + } else { + # this will override the config.toml + Write-Host "Setting CARGO_SOURCE_crates-io_REPLACE_WITH to 'crates-io'" + ${env:CARGO_SOURCE_crates-io_REPLACE_WITH} = 'CRATESIO' + $env:CARGO_REGISTRIES_CRATESIO_INDEX = 'sparse+https://index.crates.io/' + } + + ## Test if Rust is installed + if (!$usingADO -and !(Get-Command 'cargo' -ErrorAction Ignore)) { + Write-Verbose -Verbose "Rust not found, installing..." + if (!$IsWindows) { + curl https://sh.rustup.rs -sSf | sh -s -- -y + $env:PATH += ":$env:HOME/.cargo/bin" + } + else { + Invoke-WebRequest 'https://static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe' -OutFile 'temp:/rustup-init.exe' + Write-Verbose -Verbose "Use the default settings to ensure build works" + & 'temp:/rustup-init.exe' -y + $env:PATH += ";$env:USERPROFILE\.cargo\bin" + Remove-Item temp:/rustup-init.exe -ErrorAction Ignore + } + if ($LASTEXITCODE -ne 0) { + throw "Failed to install Rust" + } + } + elseif (!$usingADO) { + Write-Verbose -Verbose "Rust found, updating..." + & $rustup update + } + + if ($IsWindows) { + $BuildToolsPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC" + } + + if (!$usingADO) { + & $rustup default stable + } + + if ($Clippy) { + Write-Verbose -Verbose "Installing clippy..." + if ($UseCFS) { + cargo install clippy --config .cargo/config.toml + } else { + if ($architecture -ne 'current') { + write-verbose -verbose "Installing clippy for $architecture" + rustup component add clippy --target $architecture + } else { + write-verbose -verbose "Installing clippy for current architecture" + rustup component add clippy + } + } + if ($LASTEXITCODE -ne 0) { + throw "Failed to install clippy" + } + } + + ## Test if Node is installed + ## Skipping upgrade as users may have a specific version they want to use + if (!(Get-Command 'node' -ErrorAction Ignore)) { + Write-Verbose -Verbose "Node.js not found, installing..." + if (!$IsWindows) { + if (Get-Command 'brew' -ErrorAction Ignore) { + brew install node@24 + } else { + Write-Warning "Homebrew not found, please install Node.js manually" + } + } + else { + if (Get-Command 'winget' -ErrorAction Ignore) { + Write-Verbose -Verbose "Using winget to install Node.js" + winget install OpenJS.NodeJS --accept-source-agreements --accept-package-agreements --source winget --silent + } else { + Write-Warning "winget not found, please install Node.js manually" + } + } + if ($LASTEXITCODE -ne 0) { + throw "Failed to install Node.js" + } + } + + ## Test if tree-sitter is installed + if ($null -eq (Get-Command tree-sitter -ErrorAction Ignore)) { + Write-Verbose -Verbose "tree-sitter not found, installing..." + if ($UseCFS) { + cargo install tree-sitter-cli --config .cargo/config.toml + } else { + cargo install tree-sitter-cli + } + if ($LASTEXITCODE -ne 0) { + throw "Failed to install tree-sitter-cli" + } + } +} + +if (!$SkipBuild -and !$SkipLinkCheck -and $IsWindows -and !(Get-Command 'link.exe' -ErrorAction Ignore)) { + if (!(Test-Path $BuildToolsPath)) { + Write-Verbose -Verbose "link.exe not found, installing C++ build tools" + Invoke-WebRequest 'https://aka.ms/vs/17/release/vs_BuildTools.exe' -OutFile 'temp:/vs_buildtools.exe' + $arg = @('--passive','--add','Microsoft.VisualStudio.Workload.VCTools','--includerecommended') + if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { + $arg += '--add','Microsoft.VisualStudio.Component.VC.Tools.ARM64' + } + Start-Process -FilePath 'temp:/vs_buildtools.exe' -ArgumentList $arg -Wait + Remove-Item temp:/vs_installer.exe -ErrorAction Ignore + Write-Verbose -Verbose "Updating env vars" + $machineEnv = [environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::Machine).Split(';') + $userEnv = [environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::User).Split(';') + $pathEnv = ($env:PATH).Split(';') + foreach ($env in $machineEnv) { + if ($pathEnv -notcontains $env) { + $pathEnv += $env + } + } + foreach ($env in $userEnv) { + if ($pathEnv -notcontains $env) { + $pathEnv += $env + } + } + $env:PATH = $pathEnv -join ';' + } + + #$linkexe = Find-LinkExe + #$env:PATH += ";$linkexe" +} + +$configuration = $Release ? 'release' : 'debug' +$flags = @($Release ? '-r' : $null) +if ($architecture -eq 'current') { + $path = Join-Path $PSScriptRoot "target" $configuration + $target = Join-Path $PSScriptRoot 'bin' $configuration +} +else { + $flags += '--target' + $flags += $architecture + $path = Join-Path $PSScriptRoot 'target' $architecture $configuration + $target = Join-Path $PSScriptRoot 'bin' $architecture $configuration +} + +if (!$SkipBuild) { + if ($architecture -ne 'Current' -and !$usingADO) { + & $rustup target add --toolchain $channel $architecture + } + + if (Test-Path $target) { + Remove-Item $target -Recurse -ErrorAction Ignore + } + New-Item -ItemType Directory $target -ErrorAction Ignore > $null + + # make sure dependencies are built first so clippy runs correctly + $windows_projects = @("lib/dsc-lib-pal", "lib/dsc-lib-registry", "resources/registry", "resources/reboot_pending", "adapters/wmi", "configurations/windows", 'extensions/appx') + $macOS_projects = @("resources/brew") + $linux_projects = @("resources/apt") + + # projects are in dependency order + $projects = @( + ".", + "grammars/tree-sitter-dscexpression", + "grammars/tree-sitter-ssh-server-config", + "lib/dsc-lib-jsonschema", + "lib/dsc-lib-security_context", + "lib/dsc-lib-osinfo", + "lib/dsc-lib", + "dsc", + "resources/dscecho", + "extensions/bicep", + "resources/osinfo", + "adapters/powershell", + 'resources/PSScript', + "resources/process", + "resources/runcommandonset", + "resources/sshdconfig", + "tools/dsctest", + "tools/test_group_resource", + "y2j" + ) + $pedantic_unclean_projects = @() + $clippy_unclean_projects = @("grammars/tree-sitter-dscexpression", "grammars/tree-sitter-ssh-server-config") + $skip_test_projects_on_windows = @("grammars/tree-sitter-dscexpression", "grammars/tree-sitter-ssh-server-config") + + if ($IsWindows) { + $projects += $windows_projects + } + + if ($IsMacOS) { + $projects += $macOS_projects + } + + if ($IsLinux) { + $projects += $linux_projects + } + + $failed = $false + foreach ($project in $projects) { + ## Build format_json + Write-Host -ForegroundColor Cyan "Building '$project' for $architecture" + try { + Push-Location "$PSScriptRoot/$project" -ErrorAction Stop + Write-Verbose -Verbose "Current directory is $(Get-Location)" + + # check if the project is either tree-sitter-dscexpression or tree-sitter-ssh-server-config + if (($project -match 'tree-sitter-dscexpression$') -or ($project -match 'tree-sitter-ssh-server-config$')) { + if ($UpdateLockFile) { + cargo generate-lockfile + } + else { + if ($Audit) { + if ($null -eq (Get-Command cargo-audit -ErrorAction Ignore)) { + if ($UseCFS) { + cargo install cargo-audit --features=fix --config .cargo/config.toml + } else { + cargo install cargo-audit --features=fix + } + } + + cargo audit fix + } + + Write-Verbose -Verbose "Running build.ps1 for $project" + ./build.ps1 + } + } + + if ((Test-Path "./Cargo.toml")) + { + $isRepoRoot = $pwd.Path -eq $PSScriptRoot + if ($Clippy -and -not $isRepoRoot) { + if ($clippy_unclean_projects -contains $project) { + Write-Verbose -Verbose "Skipping clippy for $project" + } + elseif ($pedantic_unclean_projects -contains $project) { + Write-Verbose -Verbose "Running clippy for $project" + cargo clippy @flags -- -Dwarnings --no-deps + } + else { + Write-Verbose -Verbose "Running clippy with pedantic for $project" + cargo clippy @flags --% -- -Dwarnings -Dclippy::pedantic --no-deps + } + } + else { + if ($UpdateLockFile -and $isRepoRoot) { + cargo generate-lockfile + } + else { + if ($Audit) { + if ($null -eq (Get-Command cargo-audit -ErrorAction Ignore)) { + if ($UseCFS) { + cargo install cargo-audit --features=fix --config .cargo/config.toml + } else { + cargo install cargo-audit --features=fix + } + } + + cargo audit fix + } + + if ($Clean -and $isRepoRoot) { + cargo clean + } + + if (-not $isRepoRoot) { + Write-Verbose -Verbose "Building $project" + cargo build @flags + } + } + } + } + + if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { + Write-Error "Last exit code is $LASTEXITCODE, build failed for '$project'" + $failed = $true + break + } + + $binary = Split-Path $project -Leaf + + if ($IsWindows) { + Copy-Item "$path/$binary.exe" $target -ErrorAction Ignore -Verbose + Copy-Item "$path/$binary.pdb" $target -ErrorAction Ignore -Verbose + } + else { + Copy-Item "$path/$binary" $target -ErrorAction Ignore -Verbose + } + + if (Test-Path "./copy_files.txt") { + Get-Content "./copy_files.txt" | ForEach-Object { + # if the line contains a '\' character, throw an error + if ($_ -match '\\') { + throw "copy_files.txt should use '/' as the path separator" + } + # copy the file to the target directory, creating the directory path if needed + $fileCopyPath = $_.split('/') + if ($fileCopyPath.Length -gt 1) { + $fileCopyPath = $fileCopyPath[0..($fileCopyPath.Length - 2)] + $fileCopyPath = $fileCopyPath -join '/' + New-Item -ItemType Directory -Path "$target/$fileCopyPath" -Force -ErrorAction Ignore | Out-Null + } + Copy-Item $_ "$target/$_" -Force -ErrorAction Ignore + } + } + + if ($IsWindows) { + Copy-Item "*.dsc.resource.*","*.dsc.manifests.*" $target -Force -ErrorAction Ignore + } + else { # don't copy WindowsPowerShell resource manifest + $exclude = @('windowspowershell.dsc.resource.json', 'winpsscript.dsc.resource.json') + Copy-Item "*.dsc.resource.*","*.dsc.manifests.*" $target -Exclude $exclude -Force -ErrorAction Ignore + } + + # be sure that the files that should be executable are executable + if ($IsLinux -or $IsMacOS) { + foreach ($exeFile in $filesToBeExecutable) { + $exePath = "$target/$exeFile" + if (test-path $exePath) { + chmod +x $exePath + } + } + } + } catch { + Write-Error "Failed to build $project : $($_ | Out-String)" + $failed = $true + break + } finally { + Pop-Location + } + } + + if ($failed) { + Write-Host -ForegroundColor Red "Build failed" + exit 1 + } +} + +if (!$Clippy -and !$SkipBuild) { + $relative = Resolve-Path $target -Relative + Write-Host -ForegroundColor Green "`nEXE's are copied to $target ($relative)" + + # remove the other target in case switching between them + $dirSeparator = [System.IO.Path]::DirectorySeparatorChar + if ($Release) { + $oldTarget = $target.Replace($dirSeparator + 'release', $dirSeparator + 'debug') + } + else { + $oldTarget = $target.Replace($dirSeparator + 'debug', $dirSeparator + 'release') + } + $env:PATH = $env:PATH.Replace($oldTarget, '') + + $paths = $env:PATH.Split([System.IO.Path]::PathSeparator) + $found = $false + foreach ($path in $paths) { + if ($path -eq $target) { + $found = $true + break + } + } + + # remove empty entries from path + $env:PATH = [string]::Join([System.IO.Path]::PathSeparator, $env:PATH.Split([System.IO.Path]::PathSeparator, [StringSplitOptions]::RemoveEmptyEntries)) + + if (!$found) { + Write-Host -ForegroundCOlor Yellow "Adding $target to `$env:PATH" + $env:PATH = $target + [System.IO.Path]::PathSeparator + $env:PATH + } +} + +if ($Test) { + $failed = $false + $repository = 'PSGallery' + + if ($usingADO) { + $repository = 'CFS' + if ($null -eq (Get-PSResourceRepository -Name CFS -ErrorAction Ignore)) { + "Registering CFS repository" + Register-PSResourceRepository -uri 'https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/powershell/nuget/v2' -Name CFS -Trusted + } + } + + if ($IsWindows) { + # PSDesiredStateConfiguration module is needed for Microsoft.Windows/WindowsPowerShell adapter + $FullyQualifiedName = @{ModuleName="PSDesiredStateConfiguration";ModuleVersion="2.0.7"} + if (-not(Get-Module -ListAvailable -FullyQualifiedName $FullyQualifiedName)) + { + Install-PSResource -Name PSDesiredStateConfiguration -Version 2.0.7 -Repository $repository -TrustRepository + } + } + + if (-not(Get-Module -ListAvailable -Name Pester)) + { "Installing module Pester" + Install-PSResource Pester -WarningAction Ignore -Repository $repository -TrustRepository + } + + foreach ($project in $projects) { + # Skip repository root, otherwise it tests all workspace members, failing on not-Windows + if ($project -eq '.') { + continue + } + if ($IsWindows -and $skip_test_projects_on_windows -contains $project) { + Write-Verbose -Verbose "Skipping test for $project on Windows" + continue + } + + Write-Host -ForegroundColor Cyan "Testing $project ..." + try { + Push-Location "$PSScriptRoot/$project" + if (Test-Path "./Cargo.toml") + { + cargo test + + if ($LASTEXITCODE -ne 0) { + $failed = $true + } + } + } finally { + Pop-Location + } + } + + if ($failed) { + throw "Test failed" + } + + "PSModulePath is:" + $env:PSModulePath + "Pester module located in:" + (Get-Module -Name Pester -ListAvailable).Path + + # On Windows disable duplicated WinPS resources that break PSDesiredStateConfiguration module + if ($IsWindows) { + $a = $env:PSModulePath -split ";" | ? { $_ -notmatch 'WindowsPowerShell' } + $env:PSModulePath = $a -join ';' + + "Updated PSModulePath is:" + $env:PSModulePath + + if (-not(Get-Module -ListAvailable -Name Pester)) + { "Installing module Pester" + $InstallTargetDir = ($env:PSModulePath -split ";")[0] + Find-PSResource -Name 'Pester' -Repository $repository | Save-PSResource -Path $InstallTargetDir -TrustRepository + } + + "Updated Pester module location:" + (Get-Module -Name Pester -ListAvailable).Path + } + + Invoke-Pester -Output Detailed -ErrorAction Stop +} + +function Find-MakeAppx() { + $makeappx = Get-Command makeappx -CommandType Application -ErrorAction Ignore + if ($null -eq $makeappx) { + # try to find + if (!$UseX64MakeAppx -and $architecture -eq 'aarch64-pc-windows-msvc') { + $arch = 'arm64' + } + else { + $arch = 'x64' + } + + $makeappx = Get-ChildItem -Recurse -Path (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin\*\' $arch) -Filter makeappx.exe | Sort-Object FullName -Descending | Select-Object -First 1 + if ($null -eq $makeappx) { + throw "makeappx not found, please install Windows SDK" + } + } + + $makeappx +} + +$productVersion = (((Get-Content $PSScriptRoot/dsc/Cargo.toml) -match '^version\s*=\s*') -replace 'version\s*=\s*"(.*?)"', '$1').Trim() + +if ($packageType -eq 'msixbundle') { + if (!$IsWindows) { + throw "MsixBundle is only supported on Windows" + } + + $packageName = "DSC-$productVersion-Win" + $makeappx = Find-MakeAppx + $msixPath = Join-Path $PSScriptRoot 'bin' 'msix' + & $makeappx bundle /d $msixPath /p "$PSScriptRoot\bin\$packageName.msixbundle" + return +} elseif ($packageType -eq 'msix' -or $packageType -eq 'msix-private') { + if (!$IsWindows) { + throw "MSIX is only supported on Windows" + } + + if ($architecture -eq 'current') { + throw 'MSIX requires a specific architecture' + } + + $isPrivate = $packageType -eq 'msix-private' + + $makeappx = Find-MakeAppx + $makepri = Get-Item (Join-Path $makeappx.Directory "makepri.exe") -ErrorAction Stop + $displayName = "DesiredStateConfiguration" + $isPreview = $productVersion -like '*-*' + $productName = "DesiredStateConfiguration" + if ($isPreview) { + Write-Verbose -Verbose "Preview version detected" + if ($isPrivate) { + $productName += "-Private" + } + else { + $productName += "-Preview" + } + # save preview number + $previewNumber = [int]($productVersion -replace '.*?-[a-z]+\.([0-9]+)', '$1' | Out-String) + $productLabel = $productVersion.Split('-')[1] + if ($productLabel.StartsWith('rc')) { + # if RC, we increment by 100 to ensure it's newer than the last preview + $previewNumber += 100 + } + # remove label from version + $productVersion = $productVersion.Split('-')[0] + # replace revision number with preview number + $productVersion = $productVersion -replace '(\d+)$', "$previewNumber.0" + + if ($isPrivate) { + $displayName += "-Private" + } + else { + $displayName += "-Preview" + } + } + else { + # appx requires a version in the format of major.minor.build.revision with revision being 0 + $productVersion += ".0" + } + + Write-Verbose -Verbose "Product version is $productVersion" + $arch = if ($architecture -eq 'aarch64-pc-windows-msvc') { 'arm64' } else { 'x64' } + + # Appx manifest needs to be in root of source path, but the embedded version needs to be updated + # cp-459155 is 'CN=Microsoft Windows Store Publisher (Store EKU), O=Microsoft Corporation, L=Redmond, S=Washington, C=US' + # authenticodeFormer is 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' + $releasePublisher = 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' + + $appxManifest = Get-Content "$PSScriptRoot\packaging\msix\AppxManifest.xml" -Raw + $appxManifest = $appxManifest.Replace('$VERSION$', $ProductVersion).Replace('$ARCH$', $Arch).Replace('$PRODUCTNAME$', $productName).Replace('$DISPLAYNAME$', $displayName).Replace('$PUBLISHER$', $releasePublisher) + $msixTarget = Join-Path $PSScriptRoot 'bin' $architecture 'msix' + if (Test-Path $msixTarget) { + Remove-Item $msixTarget -Recurse -ErrorAction Stop -Force + } + + New-Item -ItemType Directory $msixTarget > $null + Set-Content -Path "$msixTarget\AppxManifest.xml" -Value $appxManifest -Force + + foreach ($file in $filesForWindowsPackage) { + if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { + Copy-Item "$target\$file" "$msixTarget\$file" -Recurse -ErrorAction Stop + } else { + Copy-Item "$target\$file" $msixTarget -ErrorAction Stop + } + } + + # Necessary image assets need to be in source assets folder + $assets = @( + 'Square150x150Logo' + 'Square64x64Logo' + 'Square44x44Logo' + 'Square44x44Logo.targetsize-48' + 'Square44x44Logo.targetsize-48_altform-unplated' + 'StoreLogo' + ) + + New-Item -ItemType Directory "$msixTarget\assets" > $null + foreach ($asset in $assets) { + Copy-Item "$PSScriptRoot\packaging\assets\$asset.png" "$msixTarget\assets" -ErrorAction Stop + } + + Write-Verbose "Creating priconfig.xml" -Verbose + & $makepri createconfig /o /cf (Join-Path $msixTarget "priconfig.xml") /dq en-US + if ($LASTEXITCODE -ne 0) { + throw "Failed to create priconfig.xml" + } + + Write-Verbose "Creating resources.pri" -Verbose + Push-Location $msixTarget + & $makepri new /v /o /pr $msixTarget /cf (Join-Path $msixTarget "priconfig.xml") + Pop-Location + if ($LASTEXITCODE -ne 0) { + throw "Failed to create resources.pri" + } + + Write-Verbose "Creating msix package" -Verbose + + $targetFolder = Join-Path $PSScriptRoot 'bin' 'msix' + if (Test-Path $targetFolder) { + Remove-Item $targetFolder -Recurse -ErrorAction Stop -Force + } else { + New-Item -ItemType Directory $targetFolder > $null + } + + $packageName = Join-Path $targetFolder "$productName-$productVersion-$arch.msix" + & $makeappx pack /o /v /h SHA256 /d $msixTarget /p $packageName + if ($LASTEXITCODE -ne 0) { + throw "Failed to create msix package" + } + + Write-Host -ForegroundColor Green "`nMSIX package is created at $packageName" +} elseif ($packageType -eq 'zip') { + $zipTarget = Join-Path $PSScriptRoot 'bin' $architecture 'zip' + if (Test-Path $zipTarget) { + Remove-Item $zipTarget -Recurse -ErrorAction Stop -Force + } + + New-Item -ItemType Directory $zipTarget > $null + + foreach ($file in $filesForWindowsPackage) { + if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { + Copy-Item "$target\$file" "$zipTarget\$file" -Recurse -ErrorAction Stop + } else { + Copy-Item "$target\$file" $zipTarget -ErrorAction Stop + } + } + + $packageName = "DSC-$productVersion-$architecture.zip" + $zipFile = Join-Path $PSScriptRoot 'bin' $packageName + Compress-Archive -Path "$zipTarget/*" -DestinationPath $zipFile -Force + Write-Host -ForegroundColor Green "`nZip file is created at $zipFile" +} elseif ($packageType -eq 'tgz') { + $tgzTarget = Join-Path $PSScriptRoot 'bin' $architecture 'tgz' + if (Test-Path $tgzTarget) { + Remove-Item $tgzTarget -Recurse -ErrorAction Stop -Force + } + + New-Item -ItemType Directory $tgzTarget > $null + + if ($IsLinux) { + $filesForPackage = $filesForLinuxPackage + } elseif ($IsMacOS) { + $filesForPackage = $filesForMacPackage + } else { + Write-Error "Unsupported platform for tgz package" + } + + foreach ($file in $filesForPackage) { + if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { + Copy-Item "$target\$file" "$tgzTarget\$file" -Recurse -ErrorAction Stop + } else { + Copy-Item "$target\$file" $tgzTarget -ErrorAction Stop + } + } + + # for Linux, we only build musl as its statically linked, so we remove the musl suffix + $productArchitecture = if ($architecture -eq 'aarch64-unknown-linux-musl') { + 'aarch64-linux' + } elseif ($architecture -eq 'x86_64-unknown-linux-musl') { + 'x86_64-linux' + } else { + $architecture + } + + Write-Verbose -Verbose "Creating tar.gz file" + $packageName = "DSC-$productVersion-$productArchitecture.tar.gz" + $tarFile = Join-Path $PSScriptRoot 'bin' $packageName + tar -czvf $tarFile -C $tgzTarget . + if ($LASTEXITCODE -ne 0) { + throw "Failed to create tar.gz file" + } + + # check it's valid + $out = file $tarFile + if ($out -notmatch 'gzip compressed data') { + throw "Invalid tar.gz file" + } + + Write-Host -ForegroundColor Green "`ntar.gz file is created at $tarFile" +} elseif ($packageType -eq 'rpm') { + if (!$IsLinux) { + throw "RPM package creation is only supported on Linux" + } + + # Check if rpmbuild is available + if ($null -eq (Get-Command rpmbuild -ErrorAction Ignore)) { + throw "rpmbuild not found. Please install rpm-build package (e.g., 'sudo apt install rpm build-essential' or 'sudo dnf install rpm-build')" + } + + $rpmTarget = Join-Path $PSScriptRoot 'bin' $architecture 'rpm' + if (Test-Path $rpmTarget) { + Remove-Item $rpmTarget -Recurse -ErrorAction Stop -Force + } + + New-Item -ItemType Directory $rpmTarget > $null + + # Create RPM build directories + $rpmBuildRoot = Join-Path $rpmTarget 'rpmbuild' + $rpmDirs = @('BUILD', 'RPMS', 'SOURCES', 'SPECS', 'SRPMS') + foreach ($dir in $rpmDirs) { + New-Item -ItemType Directory -Path (Join-Path $rpmBuildRoot $dir) -Force > $null + } + + # Create a staging directory for the files + $stagingDir = Join-Path $rpmBuildRoot 'SOURCES' 'dsc_files' + New-Item -ItemType Directory $stagingDir > $null + + $filesForPackage = $filesForLinuxPackage + + foreach ($file in $filesForPackage) { + if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { + Copy-Item "$target\$file" "$stagingDir\$file" -Recurse -ErrorAction Stop + } else { + Copy-Item "$target\$file" $stagingDir -ErrorAction Stop + } + } + + # Determine RPM architecture + $rpmArch = if ($architecture -eq 'current') { + # Detect current system architecture + $currentArch = uname -m + if ($currentArch -eq 'x86_64') { + 'x86_64' + } elseif ($currentArch -eq 'aarch64') { + 'aarch64' + } else { + throw "Unsupported current architecture for RPM: $currentArch" + } + } elseif ($architecture -eq 'aarch64-unknown-linux-musl' -or $architecture -eq 'aarch64-unknown-linux-gnu') { + 'aarch64' + } elseif ($architecture -eq 'x86_64-unknown-linux-musl' -or $architecture -eq 'x86_64-unknown-linux-gnu') { + 'x86_64' + } else { + throw "Unsupported architecture for RPM: $architecture" + } + + # Read the spec template and replace placeholders + $specTemplate = Get-Content "$PSScriptRoot/packaging/rpm/dsc.spec" -Raw + $specContent = $specTemplate.Replace('VERSION_PLACEHOLDER', $productVersion.Replace('-','~')).Replace('ARCH_PLACEHOLDER', $rpmArch) + $specFile = Join-Path $rpmBuildRoot 'SPECS' 'dsc.spec' + Set-Content -Path $specFile -Value $specContent + + Write-Verbose -Verbose "Building RPM package" + $rpmPackageName = "dsc-$productVersion-1.$rpmArch.rpm" + + # Build the RPM + rpmbuild -v -bb --define "_topdir $rpmBuildRoot" --buildroot "$rpmBuildRoot/BUILDROOT" $specFile 2>&1 > $rpmTarget/rpmbuild.log + + if ($LASTEXITCODE -ne 0) { + Write-Error (Get-Content $rpmTarget/rpmbuild.log -Raw) + throw "Failed to create RPM package" + } + + # Copy the RPM to the bin directory + $builtRpm = Get-ChildItem -Path (Join-Path $rpmBuildRoot 'RPMS') -Recurse -Filter '*.rpm' | Select-Object -First 1 + if ($null -eq $builtRpm) { + throw "RPM package was not created" + } + + $finalRpmPath = Join-Path $PSScriptRoot 'bin' $builtRpm.Name + Copy-Item $builtRpm.FullName $finalRpmPath -Force + + Write-Host -ForegroundColor Green "`nRPM package is created at $finalRpmPath" +} elseif ($packageType -eq 'deb') { + if (!$IsLinux) { + throw "DEB package creation is only supported on Linux" + } + + # Check if dpkg-deb is available + if ($null -eq (Get-Command dpkg-deb -ErrorAction Ignore)) { + throw "dpkg-deb not found. Please install dpkg package (e.g., 'sudo apt install dpkg' or 'sudo dnf install dpkg')" + } + + $debTarget = Join-Path $PSScriptRoot 'bin' $architecture 'deb' + if (Test-Path $debTarget) { + Remove-Item $debTarget -Recurse -ErrorAction Stop -Force + } + + New-Item -ItemType Directory $debTarget > $null + + # Create DEB package structure + $debBuildRoot = Join-Path $debTarget 'dsc' + $debDirs = @('DEBIAN', 'opt/dsc', 'usr/bin') + foreach ($dir in $debDirs) { + New-Item -ItemType Directory -Path (Join-Path $debBuildRoot $dir) -Force > $null + } + + # Copy files to the package directory + $filesForPackage = $filesForLinuxPackage + $stagingDir = Join-Path $debBuildRoot 'opt' 'dsc' + + foreach ($file in $filesForPackage) { + if ((Get-Item "$target\$file") -is [System.IO.DirectoryInfo]) { + Copy-Item "$target\$file" "$stagingDir\$file" -Recurse -ErrorAction Stop + } else { + Copy-Item "$target\$file" $stagingDir -ErrorAction Stop + } + } + + # Create symlink in usr/bin + $symlinkPath = Join-Path $debBuildRoot 'usr' 'bin' 'dsc' + New-Item -ItemType SymbolicLink -Path $symlinkPath -Target '/opt/dsc/dsc' -Force > $null + + # Determine DEB architecture + $debArch = if ($architecture -eq 'current') { + # Detect current system architecture + $currentArch = uname -m + if ($currentArch -eq 'x86_64') { + 'amd64' + } elseif ($currentArch -eq 'aarch64') { + 'arm64' + } else { + throw "Unsupported current architecture for DEB: $currentArch" + } + } elseif ($architecture -eq 'aarch64-unknown-linux-musl' -or $architecture -eq 'aarch64-unknown-linux-gnu') { + 'arm64' + } elseif ($architecture -eq 'x86_64-unknown-linux-musl' -or $architecture -eq 'x86_64-unknown-linux-gnu') { + 'amd64' + } else { + throw "Unsupported architecture for DEB: $architecture" + } + + # Read the control template and replace placeholders + $controlTemplate = Get-Content "$PSScriptRoot/packaging/deb/control" -Raw + $controlContent = $controlTemplate.Replace('VERSION_PLACEHOLDER', $productVersion).Replace('ARCH_PLACEHOLDER', $debArch) + $controlFile = Join-Path $debBuildRoot 'DEBIAN' 'control' + Set-Content -Path $controlFile -Value $controlContent + + Write-Verbose -Verbose "Building DEB package" + $debPackageName = "dsc_$productVersion-1_$debArch.deb" + + # Build the DEB + dpkg-deb --build $debBuildRoot 2>&1 > $debTarget/debbuild.log + + if ($LASTEXITCODE -ne 0) { + Write-Error (Get-Content $debTarget/debbuild.log -Raw) + throw "Failed to create DEB package" + } + + # Move the DEB to the bin directory with the correct name + $builtDeb = "$debBuildRoot.deb" + if (!(Test-Path $builtDeb)) { + throw "DEB package was not created" + } + + $finalDebPath = Join-Path $PSScriptRoot 'bin' $debPackageName + Move-Item $builtDeb $finalDebPath -Force + + Write-Host -ForegroundColor Green "`nDEB package is created at $finalDebPath" +} + +$env:RUST_BACKTRACE=1