From dd63a0590bd432a50e48de2f30977b1e64c91cf8 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Thu, 30 Mar 2023 16:38:10 -0500 Subject: [PATCH] Add support for an unpackaged distribution of Terminal (#15034) Since the removal of the Win10-specific variant of the Terminal MSIX in #15031, there has been no officially-sanctioned (or even unofficially tested!) way to get an unzippable double-click-runnable version of Windows Terminal. Due to a quirk in the resource loading system, an unpackaged distribution of Terminal needs to ship all of XAML's resources and all of is own resources in a single `resources.pri` file. The tooling to support this is minimal, and we were previously just coasting by on Visual Studio's generosity plus how the prerelease distribution of XAML embedded itself into the consuming package. This pull request introduces a build phase plus a supporting script (or three) that produces a ZIP file distribution of Windows Terminal when given a Terminal MSIX and an XAML AppX. The three scripts are: 1. A script to merge any number of PRI files and/or PRI dump files (made with `makepri dump /dt detailed`) 2. A script that specifically merges XAML's resources with Terminal's. This is necessary because the XAML package emits a couple PRI resources into Terminal's resources _even when it is not co-packaged._ We need to remove the conflicting resources. 3. Finally, a script to take a WT and XAML distribution and combine them -- resources, files, everything -- and strip out the things that we don't need. This script is an all-in-one that calls the other two and produces a ZIP file at the end. The final distribution is named after the PFN (`Microsoft.WindowsTerminal`, or `...Preview` or `WindowsTerminalDev`), the version number and the architecture. When expanded, it produces a directory named `terminal-X.Y.Z.A` (version number.) I've also added the build script to the release pipeline. As a treat, this also produces an unpackaged distribution out of every CI build... that way, contributors can download live deployable copies of WT Unpackaged to test out their changes. Pretty cool. Refs #1386 --- .github/actions/spelling/allow/microsoft.txt | 4 + .../actions/spelling/patterns/patterns.txt | 2 +- build/pipelines/release.yml | 16 ++- .../templates/build-console-steps.yml | 15 ++- build/scripts/Merge-PriFiles.ps1 | 78 ++++++++++++ .../Merge-TerminalAndXamlResources.ps1 | 47 +++++++ .../New-UnpackagedTerminalDistribution.ps1 | 117 ++++++++++++++++++ 7 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 build/scripts/Merge-PriFiles.ps1 create mode 100644 build/scripts/Merge-TerminalAndXamlResources.ps1 create mode 100644 build/scripts/New-UnpackagedTerminalDistribution.ps1 diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index 5e8c48dd03..b79f49c378 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -36,6 +36,7 @@ libucrtd LKG LOCKFILE Lxss +makepri mfcribbon microsoft microsoftonline @@ -53,10 +54,13 @@ pgo pgosweep powerrename powershell +priconfig +PRIINFO propkey pscustomobject QWORD regedit +resfiles robocopy SACLs segoe diff --git a/.github/actions/spelling/patterns/patterns.txt b/.github/actions/spelling/patterns/patterns.txt index a0e1931f36..5acf5e9bfa 100644 --- a/.github/actions/spelling/patterns/patterns.txt +++ b/.github/actions/spelling/patterns/patterns.txt @@ -35,7 +35,7 @@ ROY\sG\.\sBIV # hit-count: 71 file-count: 35 # Compiler flags (?:^|[\t ,"'`=(])-[D](?=[A-Z]{2,}|[A-Z][a-z]) -(?:^|[\t ,"'`=(])-[X](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) +(?:^|[\t ,"'`=(])-[X](?!aml)(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) # hit-count: 41 file-count: 28 # version suffix v# diff --git a/build/pipelines/release.yml b/build/pipelines/release.yml index 3f8684c70f..834df750d9 100644 --- a/build/pipelines/release.yml +++ b/build/pipelines/release.yml @@ -236,16 +236,12 @@ jobs: arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' - ${{ if eq(parameters.buildTerminal, true) }}: - task: CopyFiles@2 - displayName: Copy *.appx/*.msix to Artifacts + displayName: Copy *.msix and symbols to Artifacts inputs: Contents: >- - **/*.appx - **/*.msix **/*.appxsym - - !**/Microsoft.VCLibs*.appx TargetFolder: $(Build.ArtifactStagingDirectory)/appx OverWrite: true flattenFolders: true @@ -280,6 +276,16 @@ jobs: inputs: PathtoPublish: $(Build.ArtifactStagingDirectory)/appx ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration) + + - pwsh: |- + $XamlAppxPath = (Get-Item "src\cascadia\CascadiaPackage\AppPackages\*\Dependencies\$(BuildPlatform)\Microsoft.UI.Xaml*.appx").FullName + & .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $(WindowsTerminalPackagePath) -XamlAppX $XamlAppxPath -Destination "$(Build.ArtifactStagingDirectory)/unpackaged" + displayName: Build Unpackaged Distribution + + - publish: $(Build.ArtifactStagingDirectory)/unpackaged + artifact: unpackaged-$(BuildPlatform)-$(BuildConfiguration) + displayName: Publish Artifact (unpackaged) + - ${{ if eq(parameters.buildConPTY, true) }}: - task: CopyFiles@2 displayName: Copy ConPTY to Artifacts diff --git a/build/pipelines/templates/build-console-steps.yml b/build/pipelines/templates/build-console-steps.yml index 05cdf667c7..947dd35355 100644 --- a/build/pipelines/templates/build-console-steps.yml +++ b/build/pipelines/templates/build-console-steps.yml @@ -64,17 +64,24 @@ steps: Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}" - task: CopyFiles@2 - displayName: 'Copy *.appx/*.msix to Artifacts (Non-PR builds only)' + displayName: 'Copy *.msix to Artifacts' inputs: Contents: | - **/*.appx **/*.msix **/*.appxsym - !**/Microsoft.VCLibs*.appx TargetFolder: '$(Build.ArtifactStagingDirectory)/appx' OverWrite: true flattenFolders: true - condition: succeeded() + +- pwsh: |- + $TerminalMsixPath = (Get-Item "$(Build.ArtifactStagingDirectory)\appx\Cascadia*.msix").FullName + $XamlAppxPath = (Get-Item "src\cascadia\CascadiaPackage\AppPackages\*\Dependencies\$(BuildPlatform)\Microsoft.UI.Xaml*.appx").FullName + & .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $TerminalMsixPath -XamlAppX $XamlAppxPath -Destination "$(Build.ArtifactStagingDirectory)/unpackaged" + displayName: Build Unpackaged Distribution + +- publish: $(Build.ArtifactStagingDirectory)/unpackaged + artifact: unpackaged-$(BuildPlatform)-$(BuildConfiguration) + displayName: Publish Artifact (unpackaged) - task: CopyFiles@2 displayName: 'Copy outputs needed for test runs to Artifacts' diff --git a/build/scripts/Merge-PriFiles.ps1 b/build/scripts/Merge-PriFiles.ps1 new file mode 100644 index 0000000000..3a9985936a --- /dev/null +++ b/build/scripts/Merge-PriFiles.ps1 @@ -0,0 +1,78 @@ +Param( + [Parameter(Mandatory, + HelpMessage="List of PRI files or XML dumps (detailed only) to merge")] + [string[]] + $Path, + + [Parameter(Mandatory, + HelpMessage="Output Path")] + [string] + $OutputPath, + + [Parameter(HelpMessage="Name of index in output file; defaults to 'Application'")] + [string] + $IndexName = "Application", + + [Parameter(HelpMessage="Path to makepri.exe")] + [ValidateScript({Test-Path $_ -Type Leaf})] + [string] + $MakePriPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakePri.exe" +) + +$ErrorActionPreference = 'Stop' + +$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp" +New-Item -ItemType Directory -Path $tempDir | Out-Null +$priConfig = Join-Path $tempDir "priconfig.xml" +$priListFile = Join-Path $tempDir "pri.resfiles" +$dumpListFile = Join-Path $tempDir "dump.resfiles" + +@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"@ | Out-File -Encoding:utf8NoBOM $priConfig + +$Path | Where { $_ -Like "*.pri" } | ForEach-Object { + Get-Item $_ | Select -Expand FullName +} | Out-File -Encoding:utf8NoBOM $priListFile + +$Path | Where { $_ -Like "*.xml" } | ForEach-Object { + Get-Item $_ | Select -Expand FullName +} | Out-File -Encoding:utf8NoBOM $dumpListFile + +& $MakePriPath new /pr $tempDir /cf $priConfig /o /in $IndexName /of $OutputPath + +Remove-Item -Recurse -Force $tempDir diff --git a/build/scripts/Merge-TerminalAndXamlResources.ps1 b/build/scripts/Merge-TerminalAndXamlResources.ps1 new file mode 100644 index 0000000000..92c8bad556 --- /dev/null +++ b/build/scripts/Merge-TerminalAndXamlResources.ps1 @@ -0,0 +1,47 @@ +Param( + [Parameter(Mandatory, + HelpMessage="Root directory of extracted Terminal AppX")] + [string[]] + $TerminalRoot, + + [Parameter(Mandatory, + HelpMessage="Root directory of extracted Xaml AppX")] + [string[]] + $XamlRoot, + + [Parameter(Mandatory, + HelpMessage="Output Path")] + [string] + $OutputPath, + + [Parameter(HelpMessage="Path to makepri.exe")] + [ValidateScript({Test-Path $_ -Type Leaf})] + [string] + $MakePriPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakePri.exe" +) + +$ErrorActionPreference = 'Stop' + +$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp" +New-Item -ItemType Directory -Path $tempDir | Out-Null + +$terminalDump = Join-Path $tempDir "terminal.pri.xml" + +& $MakePriPath dump /if (Join-Path $TerminalRoot "resources.pri") /of $terminalDump /dt detailed + +Write-Verbose "Removing Microsoft.UI.Xaml node from Terminal to prevent a collision with XAML" +$terminalXMLDocument = [xml](Get-Content $terminalDump) +$resourceMap = $terminalXMLDocument.PriInfo.ResourceMap +$fileSubtree = $resourceMap.ResourceMapSubtree | Where-Object { $_.Name -eq "Files" } +$subtrees = $fileSubtree.ResourceMapSubtree +$xamlSubtreeChild = ($subtrees | Where-Object { $_.Name -eq "Microsoft.UI.Xaml" }) +if ($Null -Ne $xamlSubtreeChild) { + $null = $fileSubtree.RemoveChild($xamlSubtreeChild) + $terminalXMLDocument.Save($terminalDump) +} + +$indexName = $terminalXMLDocument.PriInfo.ResourceMap.name + +& (Join-Path $PSScriptRoot "Merge-PriFiles.ps1") -Path $terminalDump, (Join-Path $XamlRoot "resources.pri") -IndexName $indexName -OutputPath $OutputPath -MakePriPath $MakePriPath + +Remove-Item -Recurse -Force $tempDir diff --git a/build/scripts/New-UnpackagedTerminalDistribution.ps1 b/build/scripts/New-UnpackagedTerminalDistribution.ps1 new file mode 100644 index 0000000000..834f81bbd0 --- /dev/null +++ b/build/scripts/New-UnpackagedTerminalDistribution.ps1 @@ -0,0 +1,117 @@ +Param( + [Parameter(Mandatory, + HelpMessage="Path to Terminal AppX")] + [ValidateScript({Test-Path $_ -Type Leaf})] + [string] + $TerminalAppX, + + [Parameter(Mandatory, + HelpMessage="Path to Xaml AppX")] + [ValidateScript({Test-Path $_ -Type Leaf})] + [string] + $XamlAppX, + + [Parameter(HelpMessage="Output Directory")] + [string] + $Destination = ".", + + [Parameter(HelpMessage="Path to makeappx.exe")] + [ValidateScript({Test-Path $_ -Type Leaf})] + [string] + $MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakeAppx.exe" +) + +$filesToRemove = @("*.xml", "*.winmd", "Appx*", "Images/*Tile*", "Images/*Logo*") # Remove from Terminal +$filesToKeep = @("Microsoft.Terminal.Remoting.winmd") # ... except for these +$filesToCopyFromXaml = @("Microsoft.UI.Xaml.dll", "Microsoft.UI.Xaml") # We don't need the .winmd + +$ErrorActionPreference = 'Stop' + +If ($null -Eq (Get-Item $MakeAppxPath -EA:SilentlyContinue)) { + Write-Error "Could not find MakeAppx.exe at `"$MakeAppxPath`".`nMake sure that -MakeAppxPath points to a valid SDK." + Exit 1 +} + +$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp" +New-Item -ItemType Directory -Path $tempDir | Out-Null + +$XamlAppX = Get-Item $XamlAppX | Select-Object -Expand FullName +$TerminalAppX = Get-Item $TerminalAppX | Select-Object -Expand FullName + +######## +# Reading the AppX Manifest for preliminary info +######## + +$appxManifestPath = Join-Path $tempDir AppxManifest.xml +& tar.exe -x -f "$TerminalAppX" -C $tempDir AppxManifest.xml +$manifest = [xml](Get-Content $appxManifestPath) +$pfn = $manifest.Package.Identity.Name +$version = $manifest.Package.Identity.Version +$architecture = $manifest.Package.Identity.ProcessorArchitecture + +$distributionName = "{0}_{1}_{2}" -f ($pfn, $version, $architecture) +$terminalDir = "terminal-{0}" -f ($version) + +######## +# Unpacking Terminal and XAML +######## + +$terminalAppPath = Join-Path $tempdir $terminalDir +$xamlAppPath = Join-Path $tempdir "xaml" +New-Item -ItemType Directory -Path $terminalAppPath | Out-Null +New-Item -ItemType Directory -Path $xamlAppPath | Out-Null +& $MakeAppxPath unpack /p $TerminalAppX /d $terminalAppPath /o | Out-Null +If ($LASTEXITCODE -Ne 0) { + Throw "Unpacking $TerminalAppX failed" +} +& $MakeAppxPath unpack /p $XamlAppX /d $xamlAppPath /o | Out-Null +If ($LASTEXITCODE -Ne 0) { + Throw "Unpacking $XamlAppX failed" +} + +######## +# Some sanity checking +######## + +$xamlManifest = [xml](Get-Content (Join-Path $xamlAppPath "AppxManifest.xml")) +If ($xamlManifest.Package.Identity.Name -NotLike "Microsoft.UI.Xaml*") { + Throw "$XamlAppX is not a XAML package (instead, it looks like $($xamlManifest.Package.Identity.Name))" +} +If ($xamlManifest.Package.Identity.ProcessorArchitecture -Ne $architecture) { + Throw "$XamlAppX is not built for $architecture (instead, it is built for $($xamlManifest.Package.Identity.ProcessorArchitecture))" +} + +######## +# Preparation of source files +######## + +$itemsToRemove = $filesToRemove | ForEach-Object { + Get-Item (Join-Path $terminalAppPath $_) -EA:SilentlyContinue | Where-Object { + $filesToKeep -NotContains $_.Name + } +} | Sort-Object FullName -Unique +$itemsToRemove | Remove-Item -Recurse + +$filesToCopyFromXaml | ForEach-Object { + Get-Item (Join-Path $xamlAppPath $_) +} | Copy-Item -Recurse -Destination $terminalAppPath + +######## +# Resource Management +######## + +$finalTerminalPriFile = Join-Path $terminalAppPath "resources.pri" +& (Join-Path $PSScriptRoot "Merge-TerminalAndXamlResources.ps1") ` + -TerminalRoot $terminalAppPath ` + -XamlRoot $xamlAppPath ` + -OutputPath $finalTerminalPriFile ` + -Verbose:$Verbose + +######## +# Packaging +######## + +New-Item -ItemType Directory -Path $Destination -ErrorAction:SilentlyContinue | Out-Null +$outputZip = (Join-Path $Destination ("{0}.zip" -f ($distributionName))) +& tar -c --format=zip -f $outputZip -C $tempDir $terminalDir +Get-Item $outputZip