diff --git a/Microsoft.Maui-dev.sln b/Microsoft.Maui-dev.sln index efaad84182ac..e38dc7fe1092 100644 --- a/Microsoft.Maui-dev.sln +++ b/Microsoft.Maui-dev.sln @@ -271,6 +271,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{50442BCE-D01 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maui25871Library", "src\Controls\tests\Maui25871Library\Maui25871Library.csproj", "{8820D95F-EB6D-4B70-4985-759AAC83CF68}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Maui.ApplicationArtifacts.Windows", "src\Workload\Microsoft.Maui.ApplicationArtifacts.Windows\Microsoft.Maui.ApplicationArtifacts.Windows.csproj", "{3BC53DE7-90E7-47FB-9A70-D913782E07D6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -684,6 +686,10 @@ Global {8820D95F-EB6D-4B70-4985-759AAC83CF68}.Debug|Any CPU.Build.0 = Debug|Any CPU {8820D95F-EB6D-4B70-4985-759AAC83CF68}.Release|Any CPU.ActiveCfg = Release|Any CPU {8820D95F-EB6D-4B70-4985-759AAC83CF68}.Release|Any CPU.Build.0 = Release|Any CPU + {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -808,6 +814,7 @@ Global {5E0D0ACD-EA38-448D-B643-30B966A41922} = {1A031B27-6EE7-35A8-5C38-EB7370E6029E} {50442BCE-D01E-36E8-5392-D3166C7E5B47} = {6A5A18F7-8A4B-639B-2D50-68074424D750} {8820D95F-EB6D-4B70-4985-759AAC83CF68} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E} + {3BC53DE7-90E7-47FB-9A70-D913782E07D6} = {EC63FD88-5E12-46D7-B440-68F70241D987} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50} diff --git a/Microsoft.Maui-mac.slnf b/Microsoft.Maui-mac.slnf index 623e1571661a..124c189cc49c 100644 --- a/Microsoft.Maui-mac.slnf +++ b/Microsoft.Maui-mac.slnf @@ -67,6 +67,7 @@ "src\\TestUtils\\src\\VisualTestUtils\\VisualTestUtils.csproj", "src\\TestUtils\\src\\VisualTestUtils.MagickNet\\VisualTestUtils.MagickNet.csproj", "src\\TestUtils\\src\\Microsoft.Maui.IntegrationTests\\Microsoft.Maui.IntegrationTests.csproj", + "src\\Workload\\Microsoft.Maui.ApplicationArtifacts.Windows\\Microsoft.Maui.ApplicationArtifacts.Windows.csproj", "src\\Workload\\Microsoft.Maui.Sdk\\Microsoft.Maui.Sdk.csproj", "src\\Workload\\Microsoft.NET.Sdk.Maui.Manifest\\Microsoft.NET.Sdk.Maui.Manifest.csproj" ] diff --git a/Microsoft.Maui-windows.slnf b/Microsoft.Maui-windows.slnf index 4ed4c36e2225..d69d5d54efdb 100644 --- a/Microsoft.Maui-windows.slnf +++ b/Microsoft.Maui-windows.slnf @@ -84,6 +84,7 @@ "src\\TestUtils\\src\\UITest.NUnit\\UITest.NUnit.csproj", "src\\TestUtils\\src\\VisualTestUtils\\VisualTestUtils.csproj", "src\\TestUtils\\src\\VisualTestUtils.MagickNet\\VisualTestUtils.MagickNet.csproj", + "src\\Workload\\Microsoft.Maui.ApplicationArtifacts.Windows\\Microsoft.Maui.ApplicationArtifacts.Windows.csproj", "src\\Workload\\Microsoft.Maui.Sdk\\Microsoft.Maui.Sdk.csproj", "src\\Workload\\Microsoft.NET.Sdk.Maui.Manifest\\Microsoft.NET.Sdk.Maui.Manifest.csproj" ] diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln index 37c133515923..e438216cb910 100644 --- a/Microsoft.Maui.sln +++ b/Microsoft.Maui.sln @@ -269,6 +269,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.DeviceTests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.UnitTests", "src\AI\tests\Essentials.AI.UnitTests\Essentials.AI.UnitTests.csproj", "{BD5D52FA-CA8E-4236-979A-95B4CE4158D8}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Maui.ApplicationArtifacts.Windows", "src\Workload\Microsoft.Maui.ApplicationArtifacts.Windows\Microsoft.Maui.ApplicationArtifacts.Windows.csproj", "{3BC53DE7-90E7-47FB-9A70-D913782E07D6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -686,6 +688,10 @@ Global {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.Build.0 = Release|Any CPU + {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -810,6 +816,7 @@ Global {B578E852-193C-4F37-ACB1-2B9B1B32F6B6} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C} {90B0A751-82EA-4575-B22C-256EF3A4FEF1} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C} {BD5D52FA-CA8E-4236-979A-95B4CE4158D8} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C} + {3BC53DE7-90E7-47FB-9A70-D913782E07D6} = {EC63FD88-5E12-46D7-B440-68F70241D987} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50} diff --git a/docs/design/ApplicationArtifact.md b/docs/design/ApplicationArtifact.md new file mode 100644 index 000000000000..62dfdcb5570b --- /dev/null +++ b/docs/design/ApplicationArtifact.md @@ -0,0 +1,42 @@ +# ApplicationArtifact metadata in .NET MAUI + +`@(ApplicationArtifact)` is the shared public item group for final application artifacts. Platform SDKs own creating these items and their artifact identity, path, format, and platform-specific metadata: + +- .NET for Android creates APK and AAB items. +- .NET for iOS, Mac Catalyst, tvOS, and macOS creates `.app`, `.ipa`, `.pkg`, and `.xcarchive` items. +- `Microsoft.Maui.ApplicationArtifacts.Windows` creates items for modern SDK-style Windows App SDK applications. +- Other platforms should populate the same item group from their own build or publish pipeline. + +MAUI does not rediscover platform package files and does not create a parallel MAUI-specific artifact item group. Each producer attaches common metadata from its final manifest or resolved build output: + +- `ApplicationId` +- `ApplicationName` +- `ApplicationTitle` +- `ApplicationDisplayVersion` +- `ApplicationVersion` + +Platform-specific metadata remains available alongside the common values, such as Android `PackageId`, Apple `BundleIdentifier`, and Windows `PackageVersion`. `ApplicationIdGuid` is Windows-specific and is present only when the effective package identity is a GUID. Resource-backed names remain resource references when the build cannot resolve a single locale. + +Each platform owns `GetApplicationArtifacts`, retains `$(GetApplicationArtifactsDependsOn)` as the downstream extension point, and makes the collected items available during Publish. + +## Windows App SDK + +`Microsoft.Maui.ApplicationArtifacts.Windows` is a targets-only `buildTransitive` package with no MAUI runtime, Resizetizer, or MAUI build-task dependency. `Microsoft.Maui.Core` depends on it only for Windows target frameworks, and non-MAUI Windows App SDK applications can reference it directly. It activates for real, non-design-time Windows App SDK application builds with `Exe` or `WinExe` output. Set `EnableWindowsApplicationArtifacts` to `false` to disable it. Classic `.wapproj` packaging is not supported. + +The package defines `GetApplicationArtifacts`, retains `$(GetApplicationArtifactsDependsOn)` as the downstream extension point, and makes publish-path items available to `AfterTargets="Publish"` consumers in the same invocation. It reads final Windows App SDK output properties and item lists rather than searching output directories. + +Each Windows item has an `ArtifactRole`: + +- `Primary` +- `PayloadDirectory` +- `Bundle` +- `StoreUpload` +- `DeploymentManifest` +- `Symbols` +- `Certificate` +- `InstallScript` +- `DependencyPackage` +- `LandingPage` +- `Support` + +Windows items also provide `IsPrimary`, `PrimaryArtifact`, `PackageFormat`, `PackageType`, `PackageVersion`, `Architecture`, `RuntimeIdentifier`, `Signed`, `BundlePlatforms`, `EntryPoint`, and `DeploymentDirectory` when available. Packaged identity, display name, and version metadata comes from the final Appx manifest, including unchanged `ms-resource:` references. diff --git a/docs/design/NuGets.md b/docs/design/NuGets.md index 7db4653f76d5..1ceb40aef5cb 100644 --- a/docs/design/NuGets.md +++ b/docs/design/NuGets.md @@ -12,6 +12,7 @@ There are several artifacts in the .NET MAUI "universe": * `Microsoft.Maui.Controls.Xaml` - The XAML parser * `Microsoft.Maui.Controls.Build.Tasks` - The real set of XAML[C|G] targets and globs * `Microsoft.Maui.Resizetizer` - The framework-and-platform-agnostic asset generator and integrator + * `Microsoft.Maui.ApplicationArtifacts.Windows` - Targets-only discovery of final Windows App SDK application outputs * `Microsoft.Maui.Core` - The base interfaces and handlers for all .NET MAUI things * `Microsoft.Maui.Essentials` - The core set of framework-agnostic, cross-platform APIs * `Microsoft.Maui.Controls.Compatibility` - The set of compatibility APIs for Xamarin.Forms @@ -42,6 +43,7 @@ Most nugets do not contain or need targets and/or props files, but some do. Thes - Manifest - `Microsoft.NET.Sdk.Maui.Manifest` - SDK - `Microsoft.Maui.Sdk` - NuGet Packages + - Windows application artifacts - `Microsoft.Maui.ApplicationArtifacts.Windows` - Controls - `Microsoft.Maui.Controls.Build.Tasks` - Core - `Microsoft.Maui.Core` - Resizetizer - `Microsoft.Maui.Resizetizer` @@ -104,12 +106,17 @@ The targets file can then import all the SDK: ### Targets in the Packs -There are 3 packs today that need targets: +There are 4 packs today that need targets: + - Windows application artifacts - `Microsoft.Maui.ApplicationArtifacts.Windows` - Controls - `Microsoft.Maui.Controls.Build.Tasks` - Core - `Microsoft.Maui.Core` - Resizetizer - `Microsoft.Maui.Resizetizer` +#### Targets in Windows application artifacts + +`Microsoft.Maui.ApplicationArtifacts.Windows` is a neutral, targets-only package. Its `buildTransitive` target discovers final outputs from modern SDK-style Windows App SDK application projects and exposes them as `@(ApplicationArtifact)`. It has no dependency on MAUI runtime assemblies, Resizetizer, or MAUI build tasks, so non-MAUI Windows App SDK applications can reference it directly. Classic `.wapproj` packaging is not supported. + #### Targets in Controls The Controls section of MAUI is the main set of libraries that a MAUI app uses. The targets and props needed but MAUI mostly live in the `Microsoft.Maui.Controls.Build.Tasks` nuget. This is mainly because of the dependency structure of MAUI. diff --git a/eng/Microsoft.Maui.Packages-mac.slnf b/eng/Microsoft.Maui.Packages-mac.slnf index 5529edb22fdb..265328468a12 100644 --- a/eng/Microsoft.Maui.Packages-mac.slnf +++ b/eng/Microsoft.Maui.Packages-mac.slnf @@ -22,6 +22,7 @@ "src\\Graphics\\src\\Text.Markdig\\Graphics.Text.Markdig.csproj", "src\\SingleProject\\Resizetizer\\src\\Resizetizer.csproj", "src\\Templates\\src\\Microsoft.Maui.Templates.csproj", + "src\\Workload\\Microsoft.Maui.ApplicationArtifacts.Windows\\Microsoft.Maui.ApplicationArtifacts.Windows.csproj", "src\\Workload\\Microsoft.Maui.Sdk\\Microsoft.Maui.Sdk.csproj", "src\\Workload\\Microsoft.NET.Sdk.Maui.Manifest\\Microsoft.NET.Sdk.Maui.Manifest.csproj" ] diff --git a/eng/Microsoft.Maui.Packages.slnf b/eng/Microsoft.Maui.Packages.slnf index 7c21491d97e3..fb74bed2074b 100644 --- a/eng/Microsoft.Maui.Packages.slnf +++ b/eng/Microsoft.Maui.Packages.slnf @@ -25,6 +25,7 @@ "src\\Essentials\\src\\Essentials.csproj", "src\\SingleProject\\Resizetizer\\src\\Resizetizer.csproj", "src\\Templates\\src\\Microsoft.Maui.Templates.csproj", + "src\\Workload\\Microsoft.Maui.ApplicationArtifacts.Windows\\Microsoft.Maui.ApplicationArtifacts.Windows.csproj", "src\\Workload\\Microsoft.Maui.Sdk\\Microsoft.Maui.Sdk.csproj", "src\\Workload\\Microsoft.NET.Sdk.Maui.Manifest\\Microsoft.NET.Sdk.Maui.Manifest.csproj" ] diff --git a/eng/cake/dotnet.cake b/eng/cake/dotnet.cake index 084a098460c9..27c006da3c55 100644 --- a/eng/cake/dotnet.cake +++ b/eng/cake/dotnet.cake @@ -34,6 +34,7 @@ var NuGetOnlyPackages = new string[] { "Microsoft.Maui.Core.*.{nupkg,snupkg}", "Microsoft.Maui.Essentials.*.{nupkg,snupkg}", "Microsoft.Maui.Graphics.*.{nupkg,snupkg}", + "Microsoft.Maui.ApplicationArtifacts.Windows.*.{nupkg,snupkg}", "Microsoft.Maui.Maps.*.{nupkg,snupkg}", "Microsoft.Maui.Resizetizer.*.{nupkg,snupkg}", "Microsoft.AspNetCore.Components.WebView.*.{nupkg,snupkg}", diff --git a/src/Core/src/Core.csproj b/src/Core/src/Core.csproj index 41e5ba010d08..005cf2f6c41a 100644 --- a/src/Core/src/Core.csproj +++ b/src/Core/src/Core.csproj @@ -51,6 +51,9 @@ + diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/BaseBuildTest.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/BaseBuildTest.cs index 1ed09d2b7e4a..ef2000e4c5fd 100644 --- a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/BaseBuildTest.cs +++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/BaseBuildTest.cs @@ -44,6 +44,7 @@ private void SetUpNuGetPackages() "Microsoft.Maui.Core.*.nupkg", "Microsoft.Maui.Essentials.*.nupkg", "Microsoft.Maui.Graphics.*.nupkg", + "Microsoft.Maui.ApplicationArtifacts.Windows.*.nupkg", "Microsoft.Maui.Maps.*.nupkg", "Microsoft.Maui.Resizetizer.*.nupkg", "Microsoft.AspNetCore.Components.WebView.*.nupkg", diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsApplicationArtifactsPackageTest.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsApplicationArtifactsPackageTest.cs new file mode 100644 index 000000000000..a1e5a6f6918e --- /dev/null +++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsApplicationArtifactsPackageTest.cs @@ -0,0 +1,505 @@ +using System.IO.Compression; +using System.Security; +using System.Xml.Linq; + +namespace Microsoft.Maui.IntegrationTests; + +[Trait("Category", "Build")] +public class WindowsApplicationArtifactsPackageTest : BaseBuildTest +{ + const string PackageId = "Microsoft.Maui.ApplicationArtifacts.Windows"; + + public WindowsApplicationArtifactsPackageTest(IntegrationTestFixture fixture, ITestOutputHelper output) + : base(fixture, output) + { + } + + [Fact] + public void PackageContainsOnlyNeutralBuildAssets() + { + SetTestIdentifier(nameof(PackageContainsOnlyNeutralBuildAssets)); + var packagePath = FindPackage(PackageId); + + using var archive = ZipFile.OpenRead(packagePath); + var entries = archive.Entries.Select(entry => entry.FullName).ToArray(); + + Assert.Contains("buildTransitive/Microsoft.Maui.ApplicationArtifacts.Windows.targets", entries); + Assert.DoesNotContain(entries, entry => + entry.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || + entry.StartsWith("lib/", StringComparison.OrdinalIgnoreCase) || + entry.StartsWith("runtimes/", StringComparison.OrdinalIgnoreCase) || + entry.StartsWith("tools/", StringComparison.OrdinalIgnoreCase) || + entry.StartsWith("analyzers/", StringComparison.OrdinalIgnoreCase)); + Assert.Single(entries.Where(entry => entry.StartsWith("buildTransitive/", StringComparison.OrdinalIgnoreCase))); + + var nuspecEntry = Assert.Single(archive.Entries.Where(entry => entry.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase))); + using var nuspecStream = nuspecEntry.Open(); + var nuspec = XDocument.Load(nuspecStream); + var ns = nuspec.Root!.Name.Namespace; + var packageType = Assert.Single(nuspec.Descendants(ns + "packageType")); + Assert.Equal("Dependency", (string?)packageType.Attribute("name")); + } + + [Fact] + public void CorePackageDependsOnApplicationArtifactsOnlyForWindows() + { + SetTestIdentifier(nameof(CorePackageDependsOnApplicationArtifactsOnlyForWindows)); + if (!TestEnvironment.IsWindows) + { + if (true) + return; // Skip: "Packing the Windows Core target framework is only supported on Windows CI." + } + + var coreProject = Path.Combine(TestEnvironment.GetMauiDirectory(), "src", "Core", "src", "Core.csproj"); + Assert.True( + DotnetInternal.Run( + "pack", + $"\"{coreProject}\" -c Debug --no-build --no-restore -p:Packing=true -p:EnableWindowsTargeting=true -p:IncludeBuildOutput=false -p:NoWarn=NU5127%3BNU5128 -p:PackageOutputPath=\"{TestDirectory}\" -v:minimal", + output: _output), + "Unable to generate the Core package dependency graph."); + var packagePath = Path.Combine(TestDirectory, $"Microsoft.Maui.Core.{MauiPackageVersion}.nupkg"); + Assert.True(File.Exists(packagePath), $"The expected Core package was not created at '{packagePath}'."); + + using var archive = ZipFile.OpenRead(packagePath); + var nuspecEntry = Assert.Single(archive.Entries.Where(entry => entry.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase))); + using var nuspecStream = nuspecEntry.Open(); + var nuspec = XDocument.Load(nuspecStream); + var ns = nuspec.Root!.Name.Namespace; + var dependencyGroups = nuspec + .Descendants(ns + "group") + .Where(group => group.Elements(ns + "dependency").Any(dependency => + string.Equals((string?)dependency.Attribute("id"), PackageId, StringComparison.Ordinal))) + .ToArray(); + + Assert.NotEmpty(dependencyGroups); + Assert.All(dependencyGroups, group => + { + Assert.Contains("windows", (string?)group.Attribute("targetFramework") ?? string.Empty, StringComparison.OrdinalIgnoreCase); + var dependency = Assert.Single(group.Elements(ns + "dependency").Where(candidate => + string.Equals((string?)candidate.Attribute("id"), PackageId, StringComparison.Ordinal))); + Assert.Equal("All", (string?)dependency.Attribute("include"), ignoreCase: true); + Assert.Null(dependency.Attribute("exclude")); + }); + } + + [Fact] + public void TargetsClassifyAndDeduplicateAuthoritativeOutputs() + { + SetTestIdentifier(nameof(TargetsClassifyAndDeduplicateAuthoritativeOutputs)); + var packageDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "AppPackages")).FullName; + var packageTestDir = Directory.CreateDirectory(Path.Combine(packageDir, "ArtifactApp_2.3.4.5_x64_Test")).FullName; + var targetDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "bin", "Debug", "net11.0-windows")).FullName; + var uploadDir = Directory.CreateDirectory(Path.Combine(targetDir, "Upload")).FullName; + var intermediateDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "obj", "Debug")).FullName; + + var primary = CreateFile(packageTestDir, "ArtifactApp_2.3.4.5_x64.msix"); + var bundle = CreateFile(packageTestDir, "ArtifactApp_2.3.4.5_x64.msixbundle"); + var uploadPackage = CreateFile(uploadDir, "ArtifactApp_2.3.4.5_x64.msix"); + var storeUpload = CreateFile(packageDir, "ArtifactApp_2.3.4.5_x64.msixupload"); + var appInstaller = CreateFile(packageDir, "ArtifactApp.appinstaller"); + var symbols = CreateFile(packageTestDir, "ArtifactApp_2.3.4.5_x64.appxsym"); + var certificate = CreateFile(packageTestDir, "ArtifactApp.cer"); + var installScript = CreateFile(packageTestDir, "Install.ps1"); + var dependency = CreateFile(Path.Combine(packageTestDir, "Dependencies", "x64"), "Framework.msix"); + var landingPage = CreateFile(packageDir, "index.html"); + var support = CreateFile(packageDir, "install.json"); + var appInstallerSupport = CreateFile(packageDir, "appinstaller-data.xml"); + var intermediate = CreateFile(intermediateDir, "intermediate.msix"); + _ = CreateFile(targetDir, "loose-payload.dll"); + + var manifest = Path.Combine(targetDir, "AppxManifest.xml"); + File.WriteAllText(manifest, """ + + + + + ms-resource:ArtifactApp + + + + + + """); + + var artifactsFile = Path.Combine(TestDirectory, "application-artifacts.txt"); + var projectFile = Path.Combine(TestDirectory, "ArtifactClassification.proj"); + var targetsFile = Path.Combine( + TestEnvironment.GetMauiDirectory(), + "src", + "Workload", + PackageId, + "buildTransitive", + $"{PackageId}.targets"); + + File.WriteAllText(projectFile, $""" + + + net11.0-windows10.0.19041.0 + WinExe + false + {Escape(packageDir)} + MSIX + {Escape(WithDirectorySeparator(targetDir))} + {Escape(WithDirectorySeparator(targetDir))} + {Escape(WithDirectorySeparator(intermediateDir))} + {Escape(WithDirectorySeparator(packageDir))} + {Escape(WithDirectorySeparator(packageTestDir))} + {Escape(WithDirectorySeparator(uploadDir))} + {Escape(primary)} + {Escape(bundle)} + {Escape(uploadPackage)} + {Escape(storeUpload)} + {Escape(symbols)} + {Escape(certificate)} + {Escape(landingPage)} + {Escape(manifest)} + x64 + win-x64 + true + x64_arm64 + ArtifactApp + StampCollectedArtifacts + + + + true + + + + + + + + + + + + + + + + + + extended-%(ApplicationArtifact.BundlePlatforms) + + + + + + + + """); + + Assert.True( + DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteApplicationArtifacts -v:minimal", output: _output), + "Unable to execute the Windows ApplicationArtifact targets contract fixture."); + + var artifacts = File.ReadAllLines(artifactsFile) + .Select(ArtifactRecord.Parse) + .ToArray(); + + Assert.Equal(13, artifacts.Length); + Assert.Equal(artifacts.Length, artifacts.Select(artifact => artifact.Path).Distinct(StringComparer.OrdinalIgnoreCase).Count()); + Assert.DoesNotContain(artifacts, artifact => artifact.Path.StartsWith(intermediateDir, StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(artifacts, artifact => artifact.Path.EndsWith("loose-payload.dll", StringComparison.OrdinalIgnoreCase)); + + AssertRole(primary, "Primary", isPrimary: true); + AssertRole(packageTestDir, "PayloadDirectory"); + AssertRole(bundle, "Bundle"); + AssertRole(uploadPackage, "StoreUpload"); + AssertRole(storeUpload, "StoreUpload"); + AssertRole(appInstaller, "DeploymentManifest"); + AssertRole(symbols, "Symbols"); + AssertRole(certificate, "Certificate"); + AssertRole(installScript, "InstallScript"); + AssertRole(dependency, "DependencyPackage"); + AssertRole(landingPage, "LandingPage"); + AssertRole(support, "Support"); + AssertRole(appInstallerSupport, "Support"); + + Assert.All(artifacts, artifact => + { + Assert.Equal("MSIX", artifact.PackageType); + Assert.Equal("2.3.4.5", artifact.PackageVersion); + Assert.Equal("x64", artifact.Architecture); + Assert.Equal("win-x64", artifact.RuntimeIdentifier); + Assert.Equal("true", artifact.Signed, ignoreCase: true); + Assert.Equal("extended-x64_arm64", artifact.BundlePlatforms); + Assert.Equal("ArtifactApp.exe", artifact.EntryPoint); + Assert.Equal( + Path.TrimEndingDirectorySeparator(Path.GetFullPath(packageTestDir)), + Path.TrimEndingDirectorySeparator(Path.GetFullPath(artifact.DeploymentDirectory))); + Assert.Equal("11111111-2222-3333-4444-555555555555", artifact.ApplicationId); + Assert.Equal(artifact.ApplicationId, artifact.ApplicationIdGuid); + Assert.Equal("ms-resource:ArtifactApp", artifact.ApplicationTitle); + Assert.Equal(artifact.ApplicationTitle, artifact.ApplicationName); + Assert.Equal("2.3.4", artifact.ApplicationDisplayVersion); + Assert.Equal("5", artifact.ApplicationVersion); + }); + + void AssertRole(string path, string role, bool isPrimary = false) + { + var artifact = Assert.Single(artifacts.Where(candidate => + string.Equals( + Path.TrimEndingDirectorySeparator(Path.GetFullPath(candidate.Path)), + Path.TrimEndingDirectorySeparator(Path.GetFullPath(path)), + StringComparison.OrdinalIgnoreCase))); + Assert.Equal(role, artifact.Role); + Assert.Equal(isPrimary ? "true" : "false", artifact.IsPrimary, ignoreCase: true); + Assert.Equal( + Path.GetFullPath(isPrimary ? path : primary), + Path.GetFullPath(artifact.PrimaryArtifact), + ignoreCase: true); + } + } + + [Theory] + [InlineData("net11.0", "WinExe", true, true, false, true)] + [InlineData("net11.0-windows10.0.19041.0", "Library", true, true, false, true)] + [InlineData("net11.0-windows10.0.19041.0", "WinExe", false, true, false, true)] + [InlineData("net11.0-windows10.0.19041.0", "WinExe", true, false, false, true)] + [InlineData("net11.0-windows10.0.19041.0", "WinExe", true, true, true, true)] + [InlineData("net11.0-windows10.0.19041.0", "WinExe", true, true, false, false)] + public void TargetsAreInertOutsideRealWindowsAppSdkApplications( + string targetFramework, + string outputType, + bool hasWindowsAppSdk, + bool enabled, + bool designTimeBuild, + bool buildingProject) + { + SetTestIdentifier(targetFramework, outputType, hasWindowsAppSdk, enabled, designTimeBuild, buildingProject); + var artifactsFile = Path.Combine(TestDirectory, "application-artifacts.txt"); + var projectFile = Path.Combine(TestDirectory, "Inert.proj"); + var targetsFile = Path.Combine( + TestEnvironment.GetMauiDirectory(), + "src", + "Workload", + PackageId, + "buildTransitive", + $"{PackageId}.targets"); + + File.WriteAllText(projectFile, $""" + + + {targetFramework} + {outputType} + {buildingProject} + {designTimeBuild} + {enabled} + {(hasWindowsAppSdk ? Escape(TestDirectory) : string.Empty)} + None + + + + + + + + """); + + Assert.True( + DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteApplicationArtifacts -v:minimal", output: _output), + "Unable to execute the inert Windows ApplicationArtifact fixture."); + Assert.True(!File.Exists(artifactsFile) || new FileInfo(artifactsFile).Length == 0); + } + + [Fact] + public void SparsePackageUsesTheFinalMsixAsPrimaryArtifact() + { + SetTestIdentifier(nameof(SparsePackageUsesTheFinalMsixAsPrimaryArtifact)); + var targetDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "bin")).FullName; + var sparsePackage = CreateFile(targetDir, "SparseApp.msix"); + var artifactsFile = Path.Combine(TestDirectory, "application-artifacts.txt"); + var projectFile = Path.Combine(TestDirectory, "Sparse.proj"); + var targetsFile = Path.Combine( + TestEnvironment.GetMauiDirectory(), + "src", + "Workload", + PackageId, + "buildTransitive", + $"{PackageId}.targets"); + + File.WriteAllText(projectFile, $""" + + + net11.0-windows10.0.19041.0 + WinExe + false + {Escape(TestDirectory)} + Sparse + {Escape(WithDirectorySeparator(targetDir))} + {Escape(sparsePackage)} + SparseApp + + + + true + + + + + + + + + + """); + + Assert.True( + DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteApplicationArtifacts -v:minimal", output: _output), + "Unable to execute the sparse Windows ApplicationArtifact fixture."); + + var fields = Assert.Single(File.ReadAllLines(artifactsFile)).Split('|'); + Assert.Equal(Path.GetFullPath(sparsePackage), Path.GetFullPath(fields[0]), ignoreCase: true); + Assert.Equal("Primary", fields[1]); + Assert.Equal("Sparse", fields[2]); + Assert.Equal("msix", fields[3]); + } + + [Fact] + public void UnpackagedBuildAndPublishUseTheirOwnOutputPaths() + { + SetTestIdentifier(nameof(UnpackagedBuildAndPublishUseTheirOwnOutputPaths)); + var targetDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "bin")).FullName; + var publishDir = Directory.CreateDirectory(Path.Combine(targetDir, "publish")).FullName; + var buildExecutable = CreateFile(targetDir, "UnpackagedApp.exe"); + var publishExecutable = CreateFile(publishDir, "UnpackagedApp.exe"); + var buildArtifactsFile = Path.Combine(TestDirectory, "build-application-artifacts.txt"); + var publishArtifactsFile = Path.Combine(TestDirectory, "publish-application-artifacts.txt"); + var projectFile = Path.Combine(TestDirectory, "Unpackaged.proj"); + var targetsFile = Path.Combine( + TestEnvironment.GetMauiDirectory(), + "src", + "Workload", + PackageId, + "buildTransitive", + $"{PackageId}.targets"); + + File.WriteAllText(projectFile, $""" + + + net11.0-windows10.0.19041.0 + WinExe + true + {Escape(TestDirectory)} + None + {Escape(WithDirectorySeparator(targetDir))} + {Escape(WithDirectorySeparator(publishDir))} + UnpackagedApp + UnpackagedApp + com.example.unpackaged + Unpackaged App + 1.2.3 + 4 + + + + + + + + + + + + + """); + + Assert.True( + DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteBuildApplicationArtifacts;Publish -v:minimal", output: _output), + "Unable to collect unpackaged build and publish artifacts in one MSBuild invocation."); + var sameInvocationPublishPrimary = Assert.Single(File.ReadAllLines(publishArtifactsFile).Where(line => line.EndsWith("|Primary", StringComparison.Ordinal))); + Assert.Equal(Path.GetFullPath(publishExecutable), Path.GetFullPath(sameInvocationPublishPrimary.Split('|')[0]), ignoreCase: true); + + File.Delete(buildArtifactsFile); + File.Delete(publishArtifactsFile); + + Assert.True( + DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteBuildApplicationArtifacts -v:minimal", output: _output), + "Unable to collect unpackaged build artifacts."); + var buildPrimary = Assert.Single(File.ReadAllLines(buildArtifactsFile).Where(line => line.EndsWith("|Primary", StringComparison.Ordinal))); + Assert.Equal(Path.GetFullPath(buildExecutable), Path.GetFullPath(buildPrimary.Split('|')[0]), ignoreCase: true); + + Assert.True( + DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:Publish -v:minimal", output: _output), + "Unable to collect unpackaged publish artifacts."); + var publishPrimary = Assert.Single(File.ReadAllLines(publishArtifactsFile).Where(line => line.EndsWith("|Primary", StringComparison.Ordinal))); + Assert.Equal(Path.GetFullPath(publishExecutable), Path.GetFullPath(publishPrimary.Split('|')[0]), ignoreCase: true); + } + + string FindPackage(string packageId) + { + var artifactDir = Path.Combine(TestEnvironment.GetMauiDirectory(), "artifacts"); + var matchingPackages = Directory + .GetFiles(artifactDir, $"{packageId}.{MauiPackageVersion}.nupkg", SearchOption.AllDirectories) + .Where(path => !path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + return Assert.Single(matchingPackages); + } + + static string CreateFile(string directory, string fileName) + { + Directory.CreateDirectory(directory); + var path = Path.Combine(directory, fileName); + File.WriteAllText(path, fileName); + return path; + } + + static string WithDirectorySeparator(string path) => + path.EndsWith(Path.DirectorySeparatorChar, StringComparison.Ordinal) ? path : path + Path.DirectorySeparatorChar; + + static string Escape(string value) => SecurityElement.Escape(value) ?? string.Empty; + + sealed record ArtifactRecord( + string Path, + string Role, + string IsPrimary, + string PrimaryArtifact, + string PackageFormat, + string PackageType, + string PackageVersion, + string Architecture, + string RuntimeIdentifier, + string Signed, + string BundlePlatforms, + string EntryPoint, + string DeploymentDirectory, + string ApplicationId, + string ApplicationIdGuid, + string ApplicationTitle, + string ApplicationName, + string ApplicationDisplayVersion, + string ApplicationVersion) + { + public static ArtifactRecord Parse(string line) + { + var fields = line.Split('|'); + Assert.Equal(19, fields.Length); + return new( + fields[0], + fields[1], + fields[2], + fields[3], + fields[4], + fields[5], + fields[6], + fields[7], + fields[8], + fields[9], + fields[10], + fields[11], + fields[12], + fields[13], + fields[14], + fields[15], + fields[16], + fields[17], + fields[18]); + } + } +} diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsTemplateTest.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsTemplateTest.cs index 3842969306c3..eb7b70e79de9 100644 --- a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsTemplateTest.cs +++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsTemplateTest.cs @@ -1,4 +1,9 @@ -namespace Microsoft.Maui.IntegrationTests; +using System.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Xml.Linq; + +namespace Microsoft.Maui.IntegrationTests; [Trait("Category", "WindowsTemplates")] public class WindowsTemplateTest : BaseTemplateTests @@ -46,7 +51,8 @@ public void BuildWindowsAppSDKSelfContained(string id, bool wasdkself, bool nets SetTestIdentifier(id, wasdkself, netself, packageType); if (TestEnvironment.IsMacOS) { - if (true) return; // Skip: "This test is designed for testing a windows build." + if (true) + return; // Skip: "This test is designed for testing a windows build." } var projectDir = TestDirectory; @@ -80,7 +86,8 @@ public void BuildWindowsRidGraph(string id, bool useRidGraph, string packageType SetTestIdentifier(id, useRidGraph, packageType); if (TestEnvironment.IsMacOS) { - if (true) return; // Skip: "This test is designed for testing a windows build." + if (true) + return; // Skip: "This test is designed for testing a windows build." } var projectDir = TestDirectory; @@ -113,7 +120,8 @@ public void PublishUnpackaged(string id, string framework, string config, bool u SetTestIdentifier(id, framework, config, usesRidGraph); if (!TestEnvironment.IsWindows) { - if (true) return; // Skip: "Running Windows templates is only supported on Windows." + if (true) + return; // Skip: "Running Windows templates is only supported on Windows." } var projectDir = TestDirectory; @@ -156,7 +164,8 @@ public void PublishPackaged(string id, string framework, string config, bool use SetTestIdentifier(id, framework, config, usesRidGraph); if (!TestEnvironment.IsWindows) { - if (true) return; // Skip: "Running Windows templates is only supported on Windows." + if (true) + return; // Skip: "Running Windows templates is only supported on Windows." } var projectDir = TestDirectory; @@ -213,4 +222,281 @@ public void BuildWithIdentityClient() Assert.True(DotnetInternal.Build(projectFile, "Debug", properties: BuildProps, msbuildWarningsAsErrors: true, output: _output), $"Project {Path.GetFileName(projectFile)} failed to build. Check test output/attachments for errors."); } + + [Theory] + [InlineData("None")] + [InlineData("MSIX")] + public void ApplicationArtifactsAreAvailableForMauiBuildAndPublish(string packageType) + { + SetTestIdentifier(packageType); + if (!TestEnvironment.IsWindows) + { + if (true) + return; // Skip: "Running Windows templates is only supported on Windows." + } + + var projectDir = TestDirectory; + var projectFile = Path.Combine(projectDir, $"{Path.GetFileName(projectDir)}.csproj"); + var buildArtifactsFile = Path.Combine(projectDir, "build-application-artifacts.txt"); + var publishArtifactsFile = Path.Combine(projectDir, "publish-application-artifacts.txt"); + + Assert.True(DotnetInternal.New("maui", projectDir, DotNetCurrent, output: _output), + "Unable to create the MAUI template. Check test output for errors."); + + var signingProperties = string.Empty; + if (packageType == "MSIX") + { + const string certificatePassword = "application-artifacts-test"; + var certificatePath = Path.Combine(projectDir, "ApplicationArtifactsTest.pfx"); + CreateSigningCertificate(certificatePath, certificatePassword); + signingProperties = $""" + true + true + true + {Escape(certificatePath)} + {certificatePassword} + """; + } + + FileUtilities.ReplaceInFile( + projectFile, + "None", + $""" + {packageType} + {signingProperties} + """); + AddApplicationArtifactWriters(projectFile, buildArtifactsFile, publishArtifactsFile); + + var framework = $"{DotNetCurrent}-windows10.0.19041.0"; + Assert.True( + DotnetInternal.Build(projectFile, "Debug", target: "WriteBuildApplicationArtifacts", framework: framework, properties: BuildProps, output: _output), + "The MAUI project failed to collect build ApplicationArtifact items."); + AssertApplicationArtifacts(File.ReadAllLines(buildArtifactsFile), packageType, published: false); + + Assert.True( + DotnetInternal.Publish(projectFile, "Debug", framework: framework, properties: BuildProps, output: _output), + "The MAUI project failed to publish."); + AssertApplicationArtifacts(File.ReadAllLines(publishArtifactsFile), packageType, published: true); + } + + [Fact] + public void ApplicationArtifactsIncludeBundleStoreUploadAndAppInstaller() + { + SetTestIdentifier(nameof(ApplicationArtifactsIncludeBundleStoreUploadAndAppInstaller)); + if (!TestEnvironment.IsWindows) + { + if (true) + return; // Skip: "Running Windows templates is only supported on Windows." + } + + const string certificatePassword = "application-artifacts-test"; + var projectDir = TestDirectory; + var projectFile = Path.Combine(projectDir, $"{Path.GetFileName(projectDir)}.csproj"); + var certificatePath = Path.Combine(projectDir, "ApplicationArtifactsTest.pfx"); + var publishArtifactsFile = Path.Combine(projectDir, "publish-application-artifacts.txt"); + + Assert.True(DotnetInternal.New("maui", projectDir, DotNetCurrent, output: _output), + "Unable to create the MAUI template. Check test output for errors."); + + CreateSigningCertificate(certificatePath, certificatePassword); + FileUtilities.ReplaceInFile( + projectFile, + "None", + $""" + MSIX + true + true + true + {Escape(certificatePath)} + {certificatePassword} + StoreAndSideload + Always + x64 + true + https://example.invalid/application-artifacts/ + 0 + """); + AddApplicationArtifactWriters(projectFile, Path.Combine(projectDir, "build-application-artifacts.txt"), publishArtifactsFile); + + var framework = $"{DotNetCurrent}-windows10.0.19041.0"; + Assert.True( + DotnetInternal.Publish(projectFile, "Release", framework: framework, properties: BuildProps, output: _output), + "The MAUI project failed to produce the Windows packaging output matrix."); + + var artifactLines = File.ReadAllLines(publishArtifactsFile); + var artifacts = artifactLines.Select(WindowsArtifact.Parse).ToArray(); + AssertApplicationArtifacts(artifactLines, "MSIX", published: true); + Assert.Contains(artifacts, artifact => artifact.Role == "Bundle" && artifact.Path.EndsWith(".msixbundle", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(artifacts, artifact => artifact.Role == "StoreUpload" && artifact.Path.EndsWith(".msixupload", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(artifacts, artifact => artifact.Role == "DeploymentManifest" && artifact.Path.EndsWith(".appinstaller", StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public void StandaloneWindowsAppSdkApplicationCanReferenceApplicationArtifactsPackage() + { + SetTestIdentifier(nameof(StandaloneWindowsAppSdkApplicationCanReferenceApplicationArtifactsPackage)); + if (!TestEnvironment.IsWindows) + { + if (true) + return; // Skip: "Running Windows templates is only supported on Windows." + } + + var projectDir = TestDirectory; + var projectFile = Path.Combine(projectDir, "StandaloneWindowsApp.csproj"); + var buildArtifactsFile = Path.Combine(projectDir, "build-application-artifacts.txt"); + var publishArtifactsFile = Path.Combine(projectDir, "publish-application-artifacts.txt"); + var windowsAppSdkVersion = GetWindowsAppSdkPackageVersion(); + + File.WriteAllText(Path.Combine(projectDir, "Program.cs"), """ + namespace StandaloneWindowsApp; + + internal static class Program + { + [STAThread] + static void Main() + { + } + } + """); + File.WriteAllText(projectFile, $""" + + + {DotNetCurrent}-windows10.0.19041.0 + WinExe + win-x64 + None + com.example.standalonewindowsapp + Standalone Windows App + 7.8.9 + 10 + + + + + + {GetApplicationArtifactWriters(buildArtifactsFile, publishArtifactsFile)} + + """); + + Assert.True( + DotnetInternal.Build(projectFile, "Debug", target: "WriteBuildApplicationArtifacts", properties: BuildProps, output: _output), + "The standalone Windows App SDK project failed to collect build ApplicationArtifact items."); + AssertApplicationArtifacts(File.ReadAllLines(buildArtifactsFile), "None", published: false); + + Assert.True( + DotnetInternal.Publish(projectFile, "Debug", properties: BuildProps, output: _output), + "The standalone Windows App SDK project failed to publish."); + AssertApplicationArtifacts(File.ReadAllLines(publishArtifactsFile), "None", published: true); + + var assetsFile = File.ReadAllText(Path.Combine(projectDir, "obj", "project.assets.json")); + Assert.DoesNotContain("Microsoft.Maui.Core/", assetsFile, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("Microsoft.Maui.Resizetizer/", assetsFile, StringComparison.OrdinalIgnoreCase); + } + + static void AddApplicationArtifactWriters(string projectFile, string buildArtifactsFile, string publishArtifactsFile) + { + FileUtilities.ReplaceInFile( + projectFile, + "", + $""" + {GetApplicationArtifactWriters(buildArtifactsFile, publishArtifactsFile)} + + """); + } + + static string GetApplicationArtifactWriters(string buildArtifactsFile, string publishArtifactsFile) => $""" + + + + + + + """; + + static void AssertApplicationArtifacts(string[] artifactLines, string packageType, bool published) + { + var artifacts = artifactLines.Select(WindowsArtifact.Parse).ToArray(); + Assert.NotEmpty(artifacts); + Assert.Equal(artifacts.Length, artifacts.Select(artifact => artifact.Path).Distinct(StringComparer.OrdinalIgnoreCase).Count()); + Assert.DoesNotContain(artifacts, artifact => + artifact.Path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase)); + Assert.All(artifacts, artifact => + { + Assert.False(string.IsNullOrWhiteSpace(artifact.Role)); + Assert.False(string.IsNullOrWhiteSpace(artifact.Format)); + Assert.Equal(packageType, artifact.PackageType, ignoreCase: true); + Assert.False(string.IsNullOrWhiteSpace(artifact.ApplicationId)); + Assert.False(string.IsNullOrWhiteSpace(artifact.ApplicationTitle)); + Assert.False(string.IsNullOrWhiteSpace(artifact.ApplicationDisplayVersion)); + Assert.False(string.IsNullOrWhiteSpace(artifact.ApplicationVersion)); + }); + + var primary = Assert.Single(artifacts.Where(artifact => artifact.Role == "Primary")); + Assert.Equal(packageType == "None" ? "exe" : "msix", primary.Format, ignoreCase: true); + if (published && packageType == "None") + Assert.Contains($"{Path.DirectorySeparatorChar}publish{Path.DirectorySeparatorChar}", primary.Path, StringComparison.OrdinalIgnoreCase); + + Assert.Contains(artifacts, artifact => artifact.Role == "PayloadDirectory"); + if (packageType == "MSIX") + { + Assert.Equal("true", primary.Signed, ignoreCase: true); + Assert.False(string.IsNullOrWhiteSpace(primary.PackageVersion)); + Assert.Contains(artifacts, artifact => artifact.Role == "Certificate"); + Assert.Contains(artifacts, artifact => artifact.Role == "InstallScript"); + } + else + { + Assert.Equal("false", primary.Signed, ignoreCase: true); + } + } + + static void CreateSigningCertificate(string path, string password) + { + using var rsa = RSA.Create(2048); + var request = new CertificateRequest("CN=User Name", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false)); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); + var enhancedKeyUsages = new OidCollection + { + new Oid("1.3.6.1.5.5.7.3.3") + }; + request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhancedKeyUsages, false)); + using var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30)); + File.WriteAllBytes(path, certificate.Export(X509ContentType.Pfx, password)); + } + + static string GetWindowsAppSdkPackageVersion() + { + var versionsFile = Path.Combine(TestEnvironment.GetMauiDirectory(), "eng", "Versions.props"); + var versions = XDocument.Load(versionsFile); + return versions.Descendants("MicrosoftWindowsAppSDKPackageVersion").Single().Value; + } + + static string Escape(string value) => SecurityElement.Escape(value) ?? string.Empty; + + sealed record WindowsArtifact( + string Path, + string Role, + string Format, + string PackageType, + string PackageVersion, + string Signed, + string ApplicationId, + string ApplicationTitle, + string ApplicationDisplayVersion, + string ApplicationVersion) + { + public static WindowsArtifact Parse(string line) + { + var fields = line.Split('|'); + Assert.Equal(10, fields.Length); + return new(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], fields[7], fields[8], fields[9]); + } + } } diff --git a/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/Microsoft.Maui.ApplicationArtifacts.Windows.csproj b/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/Microsoft.Maui.ApplicationArtifacts.Windows.csproj new file mode 100644 index 000000000000..b9d882dd3869 --- /dev/null +++ b/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/Microsoft.Maui.ApplicationArtifacts.Windows.csproj @@ -0,0 +1,21 @@ + + + + + + true + false + Dependency + Microsoft.Maui.ApplicationArtifacts.Windows + Windows Application Artifacts + MSBuild targets that expose final Windows App SDK application outputs through the ApplicationArtifact item. + + + + + + + diff --git a/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/buildTransitive/Microsoft.Maui.ApplicationArtifacts.Windows.targets b/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/buildTransitive/Microsoft.Maui.ApplicationArtifacts.Windows.targets new file mode 100644 index 000000000000..ded8fdfd38ff --- /dev/null +++ b/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/buildTransitive/Microsoft.Maui.ApplicationArtifacts.Windows.targets @@ -0,0 +1,490 @@ + + + + <_MicrosoftMauiWindowsApplicationArtifactsEnabled + Condition="'$(EnableWindowsApplicationArtifacts)' != 'false' + and '$(MicrosoftWindowsAppSDKPackageDir)' != '' + and '$(MSBuildProjectExtension)' != '.wapproj' + and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' + and ('$(OutputType)' == 'Exe' or '$(OutputType)' == 'WinExe')">true + + + _CollectMicrosoftMauiWindowsApplicationArtifacts; + $(GetApplicationArtifactsDependsOn) + + + + + + + + + + + + + + + <_MicrosoftMauiWindowsCollectingForPublish>true + + + + + + + + + + + <_MicrosoftMauiWindowsPrimaryArtifact>$([System.IO.Path]::GetFullPath('$(PublishDir)$(TargetName).exe')) + <_MicrosoftMauiWindowsDeploymentDirectory>$([System.IO.Path]::GetFullPath('$(PublishDir)')) + + + + + + + Primary + true + $(_MicrosoftMauiWindowsPrimaryArtifact) + exe + $(WindowsPackageType) + $(_MicrosoftMauiWindowsPackageVersion) + $(_MicrosoftMauiWindowsArchitecture) + $(RuntimeIdentifier) + $(_MicrosoftMauiWindowsSigned) + $(AppxBundlePlatforms) + $(_MicrosoftMauiWindowsEntryPoint) + $(_MicrosoftMauiWindowsDeploymentDirectory) + $(_MicrosoftMauiWindowsApplicationId) + $(_MicrosoftMauiWindowsApplicationIdGuid) + $(_MicrosoftMauiWindowsApplicationTitle) + $(_MicrosoftMauiWindowsApplicationName) + $(_MicrosoftMauiWindowsApplicationDisplayVersion) + $(_MicrosoftMauiWindowsApplicationVersion) + <_MicrosoftMauiWindowsApplicationArtifact>true + + + + PayloadDirectory + false + $(_MicrosoftMauiWindowsPrimaryArtifact) + directory + $(WindowsPackageType) + $(_MicrosoftMauiWindowsPackageVersion) + $(_MicrosoftMauiWindowsArchitecture) + $(RuntimeIdentifier) + $(_MicrosoftMauiWindowsSigned) + $(AppxBundlePlatforms) + $(_MicrosoftMauiWindowsEntryPoint) + $(_MicrosoftMauiWindowsDeploymentDirectory) + $(_MicrosoftMauiWindowsApplicationId) + $(_MicrosoftMauiWindowsApplicationIdGuid) + $(_MicrosoftMauiWindowsApplicationTitle) + $(_MicrosoftMauiWindowsApplicationName) + $(_MicrosoftMauiWindowsApplicationDisplayVersion) + $(_MicrosoftMauiWindowsApplicationVersion) + <_MicrosoftMauiWindowsApplicationArtifact>true + + + + + + + + + <_MicrosoftMauiWindowsApplicationArtifactRaw Remove="@(_MicrosoftMauiWindowsApplicationArtifactRaw)" /> + <_MicrosoftMauiWindowsReportedOutput Remove="@(_MicrosoftMauiWindowsReportedOutput)" /> + <_MicrosoftMauiWindowsApplicationArtifactNormalized Remove="@(_MicrosoftMauiWindowsApplicationArtifactNormalized)" /> + <_MicrosoftMauiWindowsApplicationArtifactDistinct Remove="@(_MicrosoftMauiWindowsApplicationArtifactDistinct)" /> + + + + + + + + + + + + + + + + + + + + <_MicrosoftMauiWindowsPackageVersion Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestVersion)' != ''">$(_MicrosoftMauiWindowsManifestVersion) + <_MicrosoftMauiWindowsPackageVersion Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsPackageVersion)' == ''">$(AppxManifestIdentityVersion) + + <_MicrosoftMauiWindowsApplicationId Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestIdentity)' != ''">$(_MicrosoftMauiWindowsManifestIdentity) + <_MicrosoftMauiWindowsApplicationId Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsApplicationId)' == '' and '$(AppxManifestIdentityName)' != ''">$(AppxManifestIdentityName) + <_MicrosoftMauiWindowsApplicationId Condition="'$(_MicrosoftMauiWindowsApplicationId)' == '' and '$(ApplicationId)' != ''">$(ApplicationId) + <_MicrosoftMauiWindowsApplicationId Condition="'$(_MicrosoftMauiWindowsApplicationId)' == ''">$(AssemblyName) + + <_MicrosoftMauiWindowsApplicationTitle Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestDisplayName)' != ''">$(_MicrosoftMauiWindowsManifestDisplayName) + <_MicrosoftMauiWindowsApplicationTitle Condition="'$(_MicrosoftMauiWindowsApplicationTitle)' == '' and '$(ApplicationTitle)' != ''">$(ApplicationTitle) + <_MicrosoftMauiWindowsApplicationTitle Condition="'$(_MicrosoftMauiWindowsApplicationTitle)' == '' and '$(ApplicationName)' != ''">$(ApplicationName) + <_MicrosoftMauiWindowsApplicationTitle Condition="'$(_MicrosoftMauiWindowsApplicationTitle)' == '' and '$(AssemblyTitle)' != ''">$(AssemblyTitle) + <_MicrosoftMauiWindowsApplicationTitle Condition="'$(_MicrosoftMauiWindowsApplicationTitle)' == ''">$(AssemblyName) + + <_MicrosoftMauiWindowsApplicationName Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestDisplayName)' != ''">$(_MicrosoftMauiWindowsManifestDisplayName) + <_MicrosoftMauiWindowsApplicationName Condition="'$(_MicrosoftMauiWindowsApplicationName)' == '' and '$(ApplicationName)' != ''">$(ApplicationName) + <_MicrosoftMauiWindowsApplicationName Condition="'$(_MicrosoftMauiWindowsApplicationName)' == '' and '$(ApplicationTitle)' != ''">$(ApplicationTitle) + <_MicrosoftMauiWindowsApplicationName Condition="'$(_MicrosoftMauiWindowsApplicationName)' == ''">$(AssemblyName) + + <_MicrosoftMauiWindowsApplicationDisplayVersion + Condition="'$(WindowsPackageType)' != 'None' + and $([System.Text.RegularExpressions.Regex]::IsMatch('$(_MicrosoftMauiWindowsPackageVersion)', '^\d+\.\d+\.\d+\.\d+$'))">$([System.Text.RegularExpressions.Regex]::Replace('$(_MicrosoftMauiWindowsPackageVersion)', '^(\d+\.\d+\.\d+)\.(\d+)$', '$1')) + <_MicrosoftMauiWindowsApplicationVersion + Condition="'$(WindowsPackageType)' != 'None' + and $([System.Text.RegularExpressions.Regex]::IsMatch('$(_MicrosoftMauiWindowsPackageVersion)', '^\d+\.\d+\.\d+\.\d+$'))">$([System.Text.RegularExpressions.Regex]::Replace('$(_MicrosoftMauiWindowsPackageVersion)', '^(\d+\.\d+\.\d+)\.(\d+)$', '$2')) + <_MicrosoftMauiWindowsApplicationDisplayVersion Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsApplicationDisplayVersion)' == ''">$(_MicrosoftMauiWindowsPackageVersion) + <_MicrosoftMauiWindowsApplicationVersion Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsApplicationVersion)' == ''">$(ApplicationVersion) + + <_MicrosoftMauiWindowsApplicationDisplayVersion Condition="'$(WindowsPackageType)' == 'None' and '$(ApplicationDisplayVersion)' != ''">$(ApplicationDisplayVersion) + <_MicrosoftMauiWindowsApplicationDisplayVersion Condition="'$(WindowsPackageType)' == 'None' and '$(_MicrosoftMauiWindowsApplicationDisplayVersion)' == '' and '$(InformationalVersion)' != ''">$(InformationalVersion) + <_MicrosoftMauiWindowsApplicationDisplayVersion Condition="'$(WindowsPackageType)' == 'None' and '$(_MicrosoftMauiWindowsApplicationDisplayVersion)' == ''">$(Version) + <_MicrosoftMauiWindowsApplicationVersion Condition="'$(WindowsPackageType)' == 'None' and '$(ApplicationVersion)' != ''">$(ApplicationVersion) + <_MicrosoftMauiWindowsApplicationVersion Condition="'$(WindowsPackageType)' == 'None' and '$(_MicrosoftMauiWindowsApplicationVersion)' == '' and '$(FileVersion)' != ''">$(FileVersion) + <_MicrosoftMauiWindowsApplicationVersion Condition="'$(WindowsPackageType)' == 'None' and '$(_MicrosoftMauiWindowsApplicationVersion)' == ''">$(AssemblyVersion) + + <_MicrosoftMauiWindowsApplicationIdGuid + Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(_MicrosoftMauiWindowsApplicationId)', '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}|[0-9a-fA-F]{32})$'))">$(_MicrosoftMauiWindowsApplicationId) + + <_MicrosoftMauiWindowsArchitecture Condition="'$(PackageArchitecture)' != ''">$(PackageArchitecture) + <_MicrosoftMauiWindowsArchitecture Condition="'$(_MicrosoftMauiWindowsArchitecture)' == '' and '$(PlatformTarget)' != ''">$(PlatformTarget) + <_MicrosoftMauiWindowsArchitecture Condition="'$(_MicrosoftMauiWindowsArchitecture)' == ''">$(Platform) + <_MicrosoftMauiWindowsSigned Condition="'$(WindowsPackageType)' == 'None'">false + <_MicrosoftMauiWindowsSigned Condition="'$(_MicrosoftMauiWindowsSigned)' == '' and '$(AppxPackageSigningEnabled)' != ''">$(AppxPackageSigningEnabled) + <_MicrosoftMauiWindowsSigned Condition="'$(_MicrosoftMauiWindowsSigned)' == ''">false + + <_MicrosoftMauiWindowsEntryPoint Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestExecutable)' != ''">$(_MicrosoftMauiWindowsManifestExecutable) + <_MicrosoftMauiWindowsEntryPoint Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsEntryPoint)' == ''">$(_MicrosoftMauiWindowsManifestEntryPoint) + <_MicrosoftMauiWindowsEntryPoint Condition="'$(WindowsPackageType)' == 'None'">$(TargetName).exe + + + + <_MicrosoftMauiWindowsPrimaryArtifact Condition="'$(AppxPackageOutput)' != '' and Exists('$(AppxPackageOutput)')">$([System.IO.Path]::GetFullPath('$(AppxPackageOutput)')) + <_MicrosoftMauiWindowsPrimaryArtifact Condition="'$(_MicrosoftMauiWindowsPrimaryArtifact)' == '' and '$(AppxMainPackageOutput)' != '' and Exists('$(AppxMainPackageOutput)')">$([System.IO.Path]::GetFullPath('$(AppxMainPackageOutput)')) + <_MicrosoftMauiWindowsDeploymentDirectory Condition="'$(AppxPackageTestDir)' != '' and Exists('$(AppxPackageTestDir)')">$([System.IO.Path]::GetFullPath('$(AppxPackageTestDir)')) + + + + <_MicrosoftMauiWindowsPublishExecutable>$(PublishDir)$(TargetName).exe + <_MicrosoftMauiWindowsPublishExecutable Condition="!Exists('$(_MicrosoftMauiWindowsPublishExecutable)')">$(PublishDir)$(AssemblyName).exe + <_MicrosoftMauiWindowsBuildExecutable>$(TargetDir)$(TargetName).exe + <_MicrosoftMauiWindowsTargetPathExtension Condition="'$(TargetPath)' != ''">$([System.IO.Path]::GetExtension('$(TargetPath)')) + <_MicrosoftMauiWindowsBuildExecutable Condition="!Exists('$(_MicrosoftMauiWindowsBuildExecutable)') and '$(TargetPath)' != '' and '$(_MicrosoftMauiWindowsTargetPathExtension)' == '.exe'">$(TargetPath) + <_MicrosoftMauiWindowsPrimaryArtifact Condition="'$(_MicrosoftMauiWindowsCollectingForPublish)' == 'true' and '$(PublishDir)' != '' and Exists('$(_MicrosoftMauiWindowsPublishExecutable)')">$([System.IO.Path]::GetFullPath('$(_MicrosoftMauiWindowsPublishExecutable)')) + <_MicrosoftMauiWindowsPrimaryArtifact Condition="'$(_MicrosoftMauiWindowsPrimaryArtifact)' == '' and Exists('$(_MicrosoftMauiWindowsBuildExecutable)')">$([System.IO.Path]::GetFullPath('$(_MicrosoftMauiWindowsBuildExecutable)')) + <_MicrosoftMauiWindowsDeploymentDirectory Condition="'$(_MicrosoftMauiWindowsCollectingForPublish)' == 'true' and '$(PublishDir)' != '' and Exists('$(PublishDir)')">$([System.IO.Path]::GetFullPath('$(PublishDir)')) + <_MicrosoftMauiWindowsDeploymentDirectory Condition="'$(_MicrosoftMauiWindowsDeploymentDirectory)' == '' and '$(TargetDir)' != '' and Exists('$(TargetDir)')">$([System.IO.Path]::GetFullPath('$(TargetDir)')) + + + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxPackageOutput)" + Condition="'$(AppxPackageOutput)' != '' and Exists('$(AppxPackageOutput)')" + ArtifactRole="Primary" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxMainPackageOutput)" + Condition="'$(AppxMainPackageOutput)' != '' and Exists('$(AppxMainPackageOutput)')" + ArtifactRole="Primary" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(FinalAppxPackageItem)" + Condition="Exists('%(FinalAppxPackageItem.Identity)')" + ArtifactRole="Primary" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxPackageTestDir)" + Condition="'$(AppxPackageTestDir)' != '' and Exists('$(AppxPackageTestDir)')" + ArtifactRole="PayloadDirectory" + PackageFormat="directory" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxBundleOutput)" + Condition="'$(AppxBundleOutput)' != '' and Exists('$(AppxBundleOutput)')" + ArtifactRole="Bundle" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxUploadBundleOutput)" + Condition="'$(AppxUploadBundleOutput)' != '' and Exists('$(AppxUploadBundleOutput)')" + ArtifactRole="Bundle" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxStoreContainer)" + Condition="'$(AppxStoreContainer)' != '' and Exists('$(AppxStoreContainer)')" + ArtifactRole="StoreUpload" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxUploadPackageOutput)" + Condition="'$(AppxUploadPackageOutput)' != '' and Exists('$(AppxUploadPackageOutput)')" + ArtifactRole="StoreUpload" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxUploadMainPackageOutput)" + Condition="'$(AppxUploadMainPackageOutput)' != '' and Exists('$(AppxUploadMainPackageOutput)')" + ArtifactRole="StoreUpload" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(FinalAppxUploadPackageItem)" + Condition="Exists('%(FinalAppxUploadPackageItem.Identity)')" + ArtifactRole="StoreUpload" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(AppInstallerFilePath)" + Condition="Exists('%(AppInstallerFilePath.Identity)')" + ArtifactRole="DeploymentManifest" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxSymbolPackageOutput)" + Condition="'$(AppxSymbolPackageOutput)' != '' and Exists('$(AppxSymbolPackageOutput)')" + ArtifactRole="Symbols" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxUploadSymbolPackageOutput)" + Condition="'$(AppxUploadSymbolPackageOutput)' != '' and Exists('$(AppxUploadSymbolPackageOutput)')" + ArtifactRole="Symbols" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(FinalAppxSymbolPackageItem)" + Condition="Exists('%(FinalAppxSymbolPackageItem.Identity)')" + ArtifactRole="Symbols" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(FinalAppxUploadSymbolPackageItem)" + Condition="Exists('%(FinalAppxUploadSymbolPackageItem.Identity)')" + ArtifactRole="Symbols" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxPackagePublicKeyFile)" + Condition="'$(AppxPackagePublicKeyFile)' != '' and Exists('$(AppxPackagePublicKeyFile)')" + ArtifactRole="Certificate" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(LandingPagePath)" + Condition="'$(LandingPagePath)' != '' and Exists('$(LandingPagePath)')" + ArtifactRole="LandingPage" /> + + + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(_MicrosoftMauiWindowsPrimaryArtifact)" + Condition="'$(_MicrosoftMauiWindowsPrimaryArtifact)' != ''" + ArtifactRole="Primary" + PackageFormat="exe" /> + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(_MicrosoftMauiWindowsDeploymentDirectory)" + Condition="'$(_MicrosoftMauiWindowsDeploymentDirectory)' != ''" + ArtifactRole="PayloadDirectory" + PackageFormat="directory" /> + + + + <_MicrosoftMauiWindowsApplicationArtifactTargetDir Condition="'$(TargetDir)' != ''">$([System.IO.Path]::GetFullPath('$(TargetDir)')) + <_MicrosoftMauiWindowsApplicationArtifactTargetDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactTargetDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactTargetDir)')) + <_MicrosoftMauiWindowsApplicationArtifactTargetDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactTargetDir.ToLowerInvariant()) + + <_MicrosoftMauiWindowsApplicationArtifactOutDir Condition="'$(OutDir)' != ''">$([System.IO.Path]::GetFullPath('$(OutDir)')) + <_MicrosoftMauiWindowsApplicationArtifactOutDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactOutDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactOutDir)')) + <_MicrosoftMauiWindowsApplicationArtifactOutDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactOutDir.ToLowerInvariant()) + + <_MicrosoftMauiWindowsApplicationArtifactPublishDir Condition="'$(PublishDir)' != ''">$([System.IO.Path]::GetFullPath('$(PublishDir)')) + <_MicrosoftMauiWindowsApplicationArtifactPublishDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPublishDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactPublishDir)')) + <_MicrosoftMauiWindowsApplicationArtifactPublishDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactPublishDir.ToLowerInvariant()) + + <_MicrosoftMauiWindowsApplicationArtifactPackageDir Condition="'$(AppxPackageDir)' != ''">$([System.IO.Path]::GetFullPath('$(AppxPackageDir)')) + <_MicrosoftMauiWindowsApplicationArtifactPackageDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPackageDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactPackageDir)')) + <_MicrosoftMauiWindowsApplicationArtifactPackageDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactPackageDir.ToLowerInvariant()) + + <_MicrosoftMauiWindowsApplicationArtifactPackageTestDir Condition="'$(AppxPackageTestDir)' != ''">$([System.IO.Path]::GetFullPath('$(AppxPackageTestDir)')) + <_MicrosoftMauiWindowsApplicationArtifactPackageTestDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDir)')) + <_MicrosoftMauiWindowsApplicationArtifactPackageTestDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDir.ToLowerInvariant()) + + <_MicrosoftMauiWindowsApplicationArtifactUploadDir Condition="'$(AppxUploadPackageDir)' != ''">$([System.IO.Path]::GetFullPath('$(AppxUploadPackageDir)')) + <_MicrosoftMauiWindowsApplicationArtifactUploadDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactUploadDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactUploadDir)')) + <_MicrosoftMauiWindowsApplicationArtifactUploadDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactUploadDir.ToLowerInvariant()) + + <_MicrosoftMauiWindowsApplicationArtifactIntermediateDir Condition="'$(IntermediateOutputPath)' != ''">$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)')) + <_MicrosoftMauiWindowsApplicationArtifactIntermediateDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDir)')) + <_MicrosoftMauiWindowsApplicationArtifactIntermediateDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDir.ToLowerInvariant()) + + + + <_MicrosoftMauiWindowsReportedOutput Include="@(InstallerFileWrites)"> + <_Source>InstallerFileWrites + + <_MicrosoftMauiWindowsReportedOutput Include="@(PackagingFileWrites)"> + <_Source>PackagingFileWrites + + <_MicrosoftMauiWindowsReportedOutput Include="@(AllBuiltSideloadPackages)"> + <_Source>AllBuiltSideloadPackages + + <_MicrosoftMauiWindowsReportedOutput Include="@(AllBuiltUploadPackages)"> + <_Source>AllBuiltUploadPackages + + <_MicrosoftMauiWindowsReportedOutput Include="@(AppInstallerFileWrites)"> + <_Source>AppInstallerFileWrites + + + <_MicrosoftMauiWindowsReportedOutput Update="@(_MicrosoftMauiWindowsReportedOutput)"> + <_ComparablePath>$([System.String]::Copy('%(FullPath)').ToLowerInvariant()) + <_ExtensionLower>$([System.String]::Copy('%(Extension)').ToLowerInvariant()) + <_IsFinalOutput>false + <_IsIntermediateOutput>false + + <_MicrosoftMauiWindowsReportedOutput Update="@(_MicrosoftMauiWindowsReportedOutput)"> + <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactTargetDirComparable)' != '' + and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactTargetDirComparable)'))">true + <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactOutDirComparable)' != '' + and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactOutDirComparable)'))">true + <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPublishDirComparable)' != '' + and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactPublishDirComparable)'))">true + <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPackageDirComparable)' != '' + and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactPackageDirComparable)'))">true + <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDirComparable)' != '' + and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDirComparable)'))">true + <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactUploadDirComparable)' != '' + and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactUploadDirComparable)'))">true + <_IsIntermediateOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDirComparable)' != '' + and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDirComparable)'))">true + + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')" + Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true' + and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true' + and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)') + and ('%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.msixbundle' + or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.appxbundle')" + ArtifactRole="Bundle" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')" + Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true' + and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true' + and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)') + and ('%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.msixupload' + or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.appxupload' + or ('%(_MicrosoftMauiWindowsReportedOutput._Source)' == 'AllBuiltUploadPackages' + and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' != '.msixbundle' + and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' != '.appxbundle'))" + ArtifactRole="StoreUpload" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')" + Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true' + and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true' + and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)') + and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.appinstaller'" + ArtifactRole="DeploymentManifest" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')" + Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true' + and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true' + and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)') + and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.appxsym'" + ArtifactRole="Symbols" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')" + Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true' + and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true' + and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)') + and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.cer'" + ArtifactRole="Certificate" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')" + Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true' + and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true' + and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)') + and ('%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.ps1' + or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.psd1' + or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.cmd' + or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.bat')" + ArtifactRole="InstallScript" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')" + Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true' + and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true' + and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)') + and ($([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').Contains('\dependencies\')) + or $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').Contains('/dependencies/')))" + ArtifactRole="DependencyPackage" /> + + <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')" + Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true' + and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true' + and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')" + ArtifactRole="Support" /> + + + + <_MicrosoftMauiWindowsApplicationArtifactRaw Update="@(_MicrosoftMauiWindowsApplicationArtifactRaw)" + Condition="'%(_MicrosoftMauiWindowsApplicationArtifactRaw.PackageFormat)' == '' + and '%(_MicrosoftMauiWindowsApplicationArtifactRaw.Extension)' != ''"> + $([System.String]::Copy('%(_MicrosoftMauiWindowsApplicationArtifactRaw.Extension)').TrimStart('.').ToLowerInvariant()) + + <_MicrosoftMauiWindowsApplicationArtifactRaw Update="@(_MicrosoftMauiWindowsApplicationArtifactRaw)" + Condition="'%(_MicrosoftMauiWindowsApplicationArtifactRaw.PackageFormat)' == ''"> + file + + + <_MicrosoftMauiWindowsApplicationArtifactNormalized + Include="@(_MicrosoftMauiWindowsApplicationArtifactRaw->'%(FullPath)')" + KeepMetadata="ArtifactRole;PackageFormat" /> + + + + + + + + + %(_MicrosoftMauiWindowsApplicationArtifactDistinct.ArtifactRole) + true + false + %(_MicrosoftMauiWindowsApplicationArtifactDistinct.Identity) + $(_MicrosoftMauiWindowsPrimaryArtifact) + %(_MicrosoftMauiWindowsApplicationArtifactDistinct.PackageFormat) + $(WindowsPackageType) + $(_MicrosoftMauiWindowsPackageVersion) + $(_MicrosoftMauiWindowsArchitecture) + $(RuntimeIdentifier) + $(_MicrosoftMauiWindowsSigned) + $(AppxBundlePlatforms) + $(_MicrosoftMauiWindowsEntryPoint) + $(_MicrosoftMauiWindowsDeploymentDirectory) + $(_MicrosoftMauiWindowsApplicationId) + $(_MicrosoftMauiWindowsApplicationIdGuid) + $(_MicrosoftMauiWindowsApplicationTitle) + $(_MicrosoftMauiWindowsApplicationName) + $(_MicrosoftMauiWindowsApplicationDisplayVersion) + $(_MicrosoftMauiWindowsApplicationVersion) + <_MicrosoftMauiWindowsApplicationArtifact>true + + + + + diff --git a/src/Workload/Microsoft.NET.Sdk.Maui.Manifest/WorkloadManifest.in.json b/src/Workload/Microsoft.NET.Sdk.Maui.Manifest/WorkloadManifest.in.json index 9a742a5d6bf1..e8803e6f4c6b 100644 --- a/src/Workload/Microsoft.NET.Sdk.Maui.Manifest/WorkloadManifest.in.json +++ b/src/Workload/Microsoft.NET.Sdk.Maui.Manifest/WorkloadManifest.in.json @@ -74,6 +74,7 @@ "description": ".NET MAUI SDK for Windows", "extends": [ "maui-blazor" ], "packs": [ + "Microsoft.Maui.ApplicationArtifacts.Windows", "Microsoft.Maui.Graphics.Windows" ] }, @@ -130,6 +131,10 @@ "win-arm64": "Microsoft.Maui.Graphics.Win2D.WinUI.Desktop" } }, + "Microsoft.Maui.ApplicationArtifacts.Windows": { + "kind": "library", + "version": "@VERSION@" + }, "Microsoft.Maui.Resizetizer": { "kind": "library", "version": "@VERSION@" diff --git a/src/Workload/README.md b/src/Workload/README.md index 82f4738f5df6..66090677a3aa 100644 --- a/src/Workload/README.md +++ b/src/Workload/README.md @@ -89,6 +89,8 @@ installed: * `maui-windows` * `maui-tizen` +The `maui-windows` workload also installs `Microsoft.Maui.ApplicationArtifacts.Windows`, a targets-only package that exposes final modern Windows App SDK build and publish outputs as `@(ApplicationArtifact)`. The package can be referenced directly by non-MAUI Windows App SDK applications; classic `.wapproj` projects are outside its scope. + `maui-android` simply extends the `android` workload, adding the Android-specific platform implementation for MAUI.