Skip to content

Commit c5ca6cf

Browse files
committed
Switch to single project packaging
1 parent 5dc2b47 commit c5ca6cf

File tree

16 files changed

+458
-190
lines changed

16 files changed

+458
-190
lines changed

.github/scripts/Configure-AppxManifest.ps1

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ if ($Branch -eq "SideloadPreview")
4242
# Save modified Package.appxmanifest
4343
$xmlDoc.Save($PackageManifestPath)
4444

45-
Get-ChildItem $WorkingDir -Include *.csproj, *.appxmanifest, *.wapproj, *.xaml -recurse | ForEach-Object -Process `
45+
Get-ChildItem $WorkingDir -Include *.csproj, *.appxmanifest, *.xaml -recurse | ForEach-Object -Process `
4646
{ `
4747
(Get-Content $_ -Raw | ForEach-Object -Process { $_ -replace "Assets\\AppTiles\\Dev", "Assets\AppTiles\Preview" }) | `
4848
Set-Content $_ -NoNewline `
@@ -53,6 +53,7 @@ if ($Branch -eq "SideloadPreview")
5353
(Get-Content $_ -Raw | ForEach-Object -Process { $_ -replace "files-dev", "files-preview" }) | `
5454
Set-Content $_ -NoNewline `
5555
}
56+
5657
}
5758
elseif ($Branch -eq "StorePreview")
5859
{
@@ -75,7 +76,7 @@ elseif ($Branch -eq "StorePreview")
7576

7677
$xmlDoc.Save($PackageManifestPath)
7778

78-
Get-ChildItem $WorkingDir -Include *.csproj, *.appxmanifest, *.wapproj, *.xaml -recurse | ForEach-Object -Process `
79+
Get-ChildItem $WorkingDir -Include *.csproj, *.appxmanifest, *.xaml -recurse | ForEach-Object -Process `
7980
{ `
8081
(Get-Content $_ -Raw | ForEach-Object -Process { $_ -replace "Assets\\AppTiles\\Dev", "Assets\AppTiles\Preview" }) | `
8182
Set-Content $_ -NoNewline `
@@ -86,6 +87,7 @@ elseif ($Branch -eq "StorePreview")
8687
(Get-Content $_ -Raw | ForEach-Object -Process { $_ -replace "files-dev", "files-preview" }) | `
8788
Set-Content $_ -NoNewline `
8889
}
90+
8991
}
9092
elseif ($Branch -eq "SideloadStable")
9193
{
@@ -102,7 +104,7 @@ elseif ($Branch -eq "SideloadStable")
102104
# Save modified Package.appxmanifest
103105
$xmlDoc.Save($PackageManifestPath)
104106

105-
Get-ChildItem $WorkingDir -Include *.csproj, *.appxmanifest, *.wapproj, *.xaml -recurse | ForEach-Object -Process `
107+
Get-ChildItem $WorkingDir -Include *.csproj, *.appxmanifest, *.xaml -recurse | ForEach-Object -Process `
106108
{ `
107109
(Get-Content $_ -Raw | ForEach-Object -Process { $_ -replace "Assets\\AppTiles\\Dev", "Assets\AppTiles\Release" }) | `
108110
Set-Content $_ -NoNewline `
@@ -113,6 +115,7 @@ elseif ($Branch -eq "SideloadStable")
113115
(Get-Content $_ -Raw | ForEach-Object -Process { $_ -replace "files-dev", "files-stable" }) | `
114116
Set-Content $_ -NoNewline `
115117
}
118+
116119
}
117120
elseif ($Branch -eq "StoreStable")
118121
{
@@ -133,7 +136,7 @@ elseif ($Branch -eq "StoreStable")
133136
# Save modified Package.appxmanifest
134137
$xmlDoc.Save($PackageManifestPath)
135138

136-
Get-ChildItem $WorkingDir -Include *.csproj, *.appxmanifest, *.wapproj, *.xaml -recurse | ForEach-Object -Process `
139+
Get-ChildItem $WorkingDir -Include *.csproj, *.appxmanifest, *.xaml -recurse | ForEach-Object -Process `
137140
{ `
138141
(Get-Content $_ -Raw | ForEach-Object -Process { $_ -replace "Assets\\AppTiles\\Dev", "Assets\AppTiles\Release" }) | `
139142
Set-Content $_ -NoNewline `
@@ -144,6 +147,15 @@ elseif ($Branch -eq "StoreStable")
144147
(Get-Content $_ -Raw | ForEach-Object -Process { $_ -replace "files-dev", "files-stable" }) | `
145148
Set-Content $_ -NoNewline `
146149
}
150+
151+
}
152+
153+
# Remove unused tile assets so they don't end up in the package
154+
$keepTiles = if ($Branch -match 'Preview') { 'Preview' } elseif ($Branch -match 'Stable') { 'Release' } else { $null }
155+
foreach ($folder in @('Dev', 'Preview', 'Release')) {
156+
if ($folder -eq $keepTiles) { continue }
157+
$tilePath = Join-Path $WorkingDir "src\Files.App\Assets\AppTiles\$folder"
158+
if (Test-Path $tilePath) { Remove-Item $tilePath -Recurse -Force }
147159
}
148160

149161
Get-ChildItem $WorkingDir -Include *.cs -recurse | ForEach-Object -Process `
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# Copyright (c) Files Community
2+
# Licensed under the MIT License.
3+
4+
# Abstract:
5+
# Creates an .msixbundle from individual per-platform .msix packages produced
6+
# by single-project MSIX packaging. Optionally generates an .appinstaller file
7+
# for sideload deployments, and an .msixupload for Store submissions.
8+
9+
param(
10+
[Parameter(Mandatory)]
11+
[string]$AppxPackageDir,
12+
13+
[Parameter(Mandatory)]
14+
[string]$BundleName,
15+
16+
[string]$Version = "",
17+
18+
[string]$PackageManifestPath = "",
19+
20+
[string]$AppInstallerUri = "",
21+
22+
[ValidateSet("Sideload", "StoreUpload", "SideloadOnly")]
23+
[string]$BuildMode = "Sideload"
24+
)
25+
26+
$ErrorActionPreference = "Stop"
27+
28+
# Locate makeappx.exe from the Windows 10 SDK
29+
$sdkRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\bin"
30+
$makeAppx = Get-ChildItem -Path $sdkRoot -Filter "makeappx.exe" -Recurse -ErrorAction SilentlyContinue |
31+
Where-Object { $_.DirectoryName -match '\\x64$' } |
32+
Sort-Object { [version]($_.DirectoryName -replace '^.*\\bin\\([^\\]+)\\.*$','$1') } -Descending |
33+
Select-Object -First 1
34+
if ($null -eq $makeAppx) {
35+
Write-Error "makeappx.exe not found under '$sdkRoot'"
36+
exit 1
37+
}
38+
Write-Host "Using makeappx: $($makeAppx.FullName)"
39+
40+
# Find only the app .msix files (exclude dependency packages)
41+
$msixFiles = Get-ChildItem -Path $AppxPackageDir -Filter "*.msix" -Recurse |
42+
Where-Object { $_.DirectoryName -notmatch '\\Dependencies\\' }
43+
if ($msixFiles.Count -eq 0) {
44+
Write-Error "No .msix files found in '$AppxPackageDir'"
45+
exit 1
46+
}
47+
48+
Write-Host "Found $($msixFiles.Count) .msix package(s):"
49+
$msixFiles | ForEach-Object { Write-Host " $_" }
50+
51+
# Auto-detect version from .msix filename if not provided (e.g. Files.App_4.0.29.0_x64.msix)
52+
if ($Version -eq "") {
53+
$Version = $msixFiles[0].BaseName -replace '^[^_]+_([^_]+)_.*$','$1'
54+
Write-Host "Detected version: $Version"
55+
}
56+
57+
# Create a temporary mapping file for MakeAppx
58+
$mappingDir = Join-Path $AppxPackageDir "_bundletemp"
59+
if (Test-Path $mappingDir) { Remove-Item $mappingDir -Recurse -Force }
60+
New-Item -ItemType Directory -Path $mappingDir | Out-Null
61+
62+
# Copy all msix files into the flat temp directory
63+
foreach ($msix in $msixFiles) {
64+
Copy-Item $msix.FullName -Destination $mappingDir
65+
}
66+
67+
# Output bundle path
68+
$bundlePath = Join-Path $AppxPackageDir "$BundleName.msixbundle"
69+
if (Test-Path $bundlePath) { Remove-Item $bundlePath -Force }
70+
71+
# Use MakeAppx to create the bundle
72+
Write-Host "Creating msixbundle at: $bundlePath"
73+
& $makeAppx.FullName bundle /d $mappingDir /p $bundlePath /o
74+
if ($LASTEXITCODE -ne 0) {
75+
Write-Error "MakeAppx bundle creation failed with exit code $LASTEXITCODE"
76+
exit 1
77+
}
78+
79+
# Clean up temp directory
80+
Remove-Item $mappingDir -Recurse -Force
81+
82+
Write-Host "Successfully created: $bundlePath"
83+
84+
# Reorganize output into WAP-compatible folder structure for CDN upload
85+
# Old structure: Files.Package_4.0.28.0_Test/Files.Package_4.0.28.0_x64_arm64.msixbundle
86+
# Files.Package_4.0.28.0_Test/Dependencies/x64/...
87+
# New single-project output: Files.App_4.0.29.0_x64_Test/Dependencies/x64/...
88+
$platformList = $msixFiles | ForEach-Object { $_.BaseName -replace '.*_(\w+)$','$1' } | Sort-Object -Descending
89+
$platforms = $platformList -join '_'
90+
$bundleFolder = "${BundleName}_${Version}_Test"
91+
$bundleFolderPath = Join-Path $AppxPackageDir $bundleFolder
92+
if (-not (Test-Path $bundleFolderPath)) {
93+
New-Item -ItemType Directory -Path $bundleFolderPath | Out-Null
94+
}
95+
96+
# Move the bundle into the organized folder with proper name
97+
$organizedBundleName = "${BundleName}_${Version}_${platforms}.msixbundle"
98+
$organizedBundlePath = Join-Path $bundleFolderPath $organizedBundleName
99+
Move-Item $bundlePath $organizedBundlePath -Force
100+
Write-Host "Moved bundle to: $organizedBundlePath"
101+
102+
# Map from build output folder names to canonical arch names used by the old WAP output
103+
$archMap = @{ 'arm64' = 'ARM64'; 'x64' = 'x64'; 'x86' = 'x86'; 'win32' = $null }
104+
105+
# Merge dependency folders from each per-platform build (only for bundle platforms)
106+
$organizedDepsDir = Join-Path $bundleFolderPath "Dependencies"
107+
foreach ($msix in $msixFiles) {
108+
$perPlatDepsDir = Join-Path $msix.DirectoryName "Dependencies"
109+
if (-not (Test-Path $perPlatDepsDir)) { continue }
110+
111+
Get-ChildItem -Path $perPlatDepsDir -Directory | ForEach-Object {
112+
$archDir = $_
113+
$archName = $archDir.Name
114+
115+
# Skip dependency folders that don't match a bundle platform (e.g. win32, x86)
116+
$canonicalArch = $archMap[$archName]
117+
if ($null -eq $canonicalArch) { return }
118+
$matchesBundlePlatform = $platformList | Where-Object { $_ -eq $archName }
119+
if (-not $matchesBundlePlatform) { return }
120+
121+
$targetArchDir = Join-Path $organizedDepsDir $canonicalArch
122+
if (-not (Test-Path $targetArchDir)) {
123+
New-Item -ItemType Directory -Path $targetArchDir | Out-Null
124+
}
125+
Get-ChildItem -Path $archDir.FullName -File | ForEach-Object {
126+
$destFile = Join-Path $targetArchDir $_.Name
127+
if (-not (Test-Path $destFile)) {
128+
Copy-Item $_.FullName -Destination $destFile
129+
Write-Host " Copied dependency: $destFile"
130+
}
131+
}
132+
}
133+
}
134+
135+
# Add VCLibs framework packages (not produced by single-project MSIX builds).
136+
# Try the VS Extension SDK first; fall back to downloading from Microsoft's CDN.
137+
$vcLibsSdkBase = "${env:ProgramFiles(x86)}\Microsoft SDKs\Windows Kits\10\ExtensionSDKs"
138+
$vcLibsCdnBase = "https://aka.ms"
139+
140+
$vcLibsPackages = @(
141+
@{ SdkFolder = "Microsoft.VCLibs"; FileTemplate = "Microsoft.VCLibs.{0}.14.00.appx" },
142+
@{ SdkFolder = "Microsoft.VCLibs.Desktop"; FileTemplate = "Microsoft.VCLibs.{0}.14.00.Desktop.appx" }
143+
)
144+
145+
foreach ($platform in $platformList) {
146+
$canonicalArch = $archMap[$platform]
147+
if ($null -eq $canonicalArch) { continue }
148+
149+
$targetArchDir = Join-Path $organizedDepsDir $canonicalArch
150+
if (-not (Test-Path $targetArchDir)) {
151+
New-Item -ItemType Directory -Path $targetArchDir | Out-Null
152+
}
153+
154+
foreach ($vcLib in $vcLibsPackages) {
155+
$fileName = $vcLib.FileTemplate -f $canonicalArch # e.g. Microsoft.VCLibs.ARM64.14.00.appx
156+
$destPath = Join-Path $targetArchDir $fileName
157+
if (Test-Path $destPath) { continue }
158+
159+
$sdkPath = Join-Path $vcLibsSdkBase "$($vcLib.SdkFolder)\14.0\Appx\Retail\$platform\$fileName"
160+
if (Test-Path $sdkPath) {
161+
Copy-Item $sdkPath -Destination $destPath
162+
Write-Host " Copied VCLibs from SDK: $destPath"
163+
} else {
164+
Write-Host " Downloading $fileName for $platform..."
165+
try {
166+
Invoke-WebRequest -Uri "$vcLibsCdnBase/$fileName" -OutFile $destPath -UseBasicParsing
167+
Write-Host " Downloaded VCLibs: $destPath"
168+
} catch {
169+
Write-Warning " Failed to download $fileName for ${platform}: $_"
170+
}
171+
}
172+
}
173+
}
174+
175+
# Clean up the per-platform build folders
176+
foreach ($msix in $msixFiles) {
177+
$perPlatDir = $msix.DirectoryName
178+
if (Test-Path $perPlatDir) {
179+
Remove-Item $perPlatDir -Recurse -Force
180+
Write-Host "Removed per-platform dir: $perPlatDir"
181+
}
182+
}
183+
184+
# Generate .msixupload for Store submissions
185+
if ($BuildMode -eq "StoreUpload") {
186+
$uploadPath = Join-Path $AppxPackageDir "$BundleName.msixupload"
187+
if (Test-Path $uploadPath) { Remove-Item $uploadPath -Force }
188+
189+
Write-Host "Creating msixupload at: $uploadPath"
190+
$zipPath = "$uploadPath.zip"
191+
Compress-Archive -Path $organizedBundlePath -DestinationPath $zipPath -Force
192+
Move-Item $zipPath $uploadPath -Force
193+
Write-Host "Successfully created: $uploadPath"
194+
}
195+
196+
# Generate .appinstaller for sideload deployments
197+
if ($AppInstallerUri -ne "" -and ($BuildMode -eq "Sideload" -or $BuildMode -eq "SideloadOnly")) {
198+
$appInstallerPath = Join-Path $AppxPackageDir "$BundleName.appinstaller"
199+
200+
# Read package identity from manifest
201+
if ($PackageManifestPath -ne "" -and (Test-Path $PackageManifestPath)) {
202+
[xml]$manifestXml = Get-Content $PackageManifestPath
203+
$packageName = $manifestXml.Package.Identity.Name
204+
} else {
205+
Write-Warning "PackageManifestPath not provided or not found; falling back to BundleName for identity"
206+
$packageName = $BundleName
207+
}
208+
209+
# Build the bundle URI using the reorganized folder structure
210+
$bundleUri = "${AppInstallerUri}${bundleFolder}/${organizedBundleName}"
211+
212+
# Collect dependency files from the reorganized Dependencies folder
213+
$dependencyEntries = ""
214+
if (Test-Path $organizedDepsDir) {
215+
Get-ChildItem -Path $organizedDepsDir -Recurse -Include *.appx, *.msix | ForEach-Object {
216+
$depFile = $_
217+
# Determine the sub-folder name (arch folder under Dependencies)
218+
$depArch = Split-Path (Split-Path $depFile.FullName -Parent) -Leaf
219+
220+
# Extract Name, Publisher, Version from the dependency package manifest
221+
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
222+
$depZip = [System.IO.Compression.ZipFile]::OpenRead($depFile.FullName)
223+
try {
224+
$depManifestEntry = $depZip.Entries | Where-Object { $_.FullName -eq "AppxManifest.xml" } | Select-Object -First 1
225+
if ($null -eq $depManifestEntry) { return }
226+
227+
$depReader = New-Object System.IO.StreamReader($depManifestEntry.Open())
228+
[xml]$depManifest = $depReader.ReadToEnd()
229+
$depReader.Close()
230+
}
231+
finally {
232+
$depZip.Dispose()
233+
}
234+
235+
$depNsMgr = New-Object System.Xml.XmlNamespaceManager($depManifest.NameTable)
236+
$depNsMgr.AddNamespace("pkg", "http://schemas.microsoft.com/appx/manifest/foundation/windows10")
237+
$depIdentity = $depManifest.SelectSingleNode("/pkg:Package/pkg:Identity", $depNsMgr)
238+
if ($null -eq $depIdentity) { return }
239+
240+
$depName = $depIdentity.GetAttribute("Name")
241+
$depPublisher = $depIdentity.GetAttribute("Publisher")
242+
$depVersion = $depIdentity.GetAttribute("Version")
243+
$depProcessorArch = $depIdentity.GetAttribute("ProcessorArchitecture")
244+
if ([string]::IsNullOrEmpty($depProcessorArch)) { $depProcessorArch = $depArch }
245+
246+
# Build the URI: {base}/{bundleFolder}/Dependencies/{archFolder}/{filename}
247+
$depRelPath = "Dependencies/$depArch/$($depFile.Name)"
248+
$depUri = "${AppInstallerUri}${bundleFolder}/$depRelPath"
249+
250+
$dependencyEntries += @"
251+
252+
<Package
253+
Name="$depName"
254+
Publisher="$depPublisher"
255+
ProcessorArchitecture="$depProcessorArch"
256+
Uri="$depUri"
257+
Version="$depVersion" />
258+
"@
259+
}
260+
}
261+
262+
if ($dependencyEntries -eq "") {
263+
Write-Warning "No dependency packages found"
264+
} else {
265+
Write-Host "Discovered dependencies:$dependencyEntries"
266+
}
267+
268+
$appInstallerContent = @"
269+
<?xml version="1.0" encoding="utf-8"?>
270+
<AppInstaller
271+
Uri="${AppInstallerUri}${BundleName}.appinstaller"
272+
Version="$Version" xmlns="http://schemas.microsoft.com/appx/appinstaller/2018">
273+
<MainBundle
274+
Name="$packageName"
275+
Version="$Version"
276+
Uri="$bundleUri" />
277+
<Dependencies>$dependencyEntries
278+
</Dependencies>
279+
</AppInstaller>
280+
"@
281+
282+
Write-Host "Creating appinstaller at: $appInstallerPath"
283+
$appInstallerContent | Set-Content -Path $appInstallerPath -Encoding UTF8
284+
Write-Host "Successfully created: $appInstallerPath"
285+
}

0 commit comments

Comments
 (0)