From 7cf25fade5943d66e298c62b73e3e1a718d4c2c7 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 7 Jul 2026 18:13:20 +0200 Subject: [PATCH 01/13] [docs] Improve XML docs for SKError (#25957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve XML documentation for the `SKError` enum: - Replace 'To be added.' placeholders with meaningful descriptions for all error codes - Remove empty `` nodes 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/StoreKit/Enums.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/StoreKit/Enums.cs b/src/StoreKit/Enums.cs index 9c1169aa7b45..eb5058aa6475 100644 --- a/src/StoreKit/Enums.cs +++ b/src/StoreKit/Enums.cs @@ -49,20 +49,20 @@ public enum SKError : long { /// Indicates that the cloud service network connection failed. CloudServiceNetworkConnectionFailed, // iOS 10.3 - /// To be added. + /// Indicates that the cloud service has been revoked. CloudServiceRevoked, // iOS 12.2 - /// To be added. + /// Indicates that a privacy acknowledgement is required before proceeding. PrivacyAcknowledgementRequired, - /// To be added. + /// Indicates that the request data is unauthorized. UnauthorizedRequestData, - /// To be added. + /// Indicates that the offer identifier is invalid. InvalidOfferIdentifier, - /// To be added. + /// Indicates that the signature is invalid. InvalidSignature, - /// To be added. + /// Indicates that required offer parameters are missing. MissingOfferParams, - /// To be added. + /// Indicates that the offer price is invalid. InvalidOfferPrice, OverlayCancelled = 15, From 6d24fb6d8b6d287b4cb951e8154ab217a4a19ef3 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 7 Jul 2026 18:55:20 +0200 Subject: [PATCH 02/13] [build] Only report build failures, not successes (#25959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'Report build result' step no longer posts a GitHub comment when the build succeeds - only when it fails. These "Build passed" comments (and emails) don't add much value, so just remove them. We're still interested in failures though, so those will still be posted. 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../devops/automation/templates/build/build.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tools/devops/automation/templates/build/build.yml b/tools/devops/automation/templates/build/build.yml index 1eff352e19b6..234079f64821 100644 --- a/tools/devops/automation/templates/build/build.yml +++ b/tools/devops/automation/templates/build/build.yml @@ -238,18 +238,17 @@ steps: # report the result of the build. - pwsh: | + # Only report failures, not successes. + if (("$Env:AGENT_JOBSTATUS" -eq "Succeeded") -or ("$Env:AGENT_JOBSTATUS" -eq "SucceededWithIssues")) { + Write-Host "Build passed, skipping comment." + return + } Import-Module $Env:SYSTEM_DEFAULTWORKINGDIRECTORY\$Env:BUILD_REPOSITORY_TITLE\tools\devops\automation\scripts\MaciosCI.psd1 $githubComments = New-GitHubCommentsObjectFromUrl -Url "$(Build.Repository.Uri)" -Token $(GitHub.Token) -Hash $Env:COMMENT_HASH $commentMessage = [System.Text.StringBuilder]::new() - if (("$Env:AGENT_JOBSTATUS" -eq "Succeeded") -or ("$Env:AGENT_JOBSTATUS" -eq "SucceededWithIssues")) { - $commentTitle = "Build passed ($(System.JobDisplayName))" - $commentIcon = ":white_check_mark:" - $commentMessage.AppendLine("") - } else { - $commentTitle = "Build failed ($(System.JobDisplayName))" - $commentIcon = ":fire:" - $commentMessage.AppendLine("Build failed for the job '$(System.JobDisplayName)' (with job status '$Env:AGENT_JOBSTATUS')") - } + $commentTitle = "Build failed ($(System.JobDisplayName))" + $commentIcon = ":fire:" + $commentMessage.AppendLine("Build failed for the job '$(System.JobDisplayName)' (with job status '$Env:AGENT_JOBSTATUS')") $githubComments.NewCommentFromMessage($commentTitle, $commentIcon, $commentMessage.ToString(), "build $(System.JobDisplayName)") condition: always() displayName: 'Report build result' From 58baaa193ef8dce8aa733529c3aae2e196a71475 Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Wed, 8 Jul 2026 06:09:35 -0400 Subject: [PATCH 03/13] [Signing] Singn unsigned libraries (#25966) Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3028786 --- dotnet/Workloads/SignList.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/Workloads/SignList.xml b/dotnet/Workloads/SignList.xml index 8c695597ea18..e674d1aa5c15 100644 --- a/dotnet/Workloads/SignList.xml +++ b/dotnet/Workloads/SignList.xml @@ -37,18 +37,15 @@ - - - @@ -58,6 +55,9 @@ + + + From 6b077b4ec40cb9a2608b2073ecbb9b6c3630d224 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:16:03 +0200 Subject: [PATCH 04/13] [main] Update dependencies from dotnet/xharness (#25967) This pull request updates the following dependencies ## From https://github.com/dotnet/xharness - **Subscription**: [02e03784-16b3-4ced-b02a-3715797fc7da](https://maestro.dot.net/subscriptions?search=02e03784-16b3-4ced-b02a-3715797fc7da) - **Build**: [20260707.1](https://dev.azure.com/dnceng/internal/_build/results?buildId=3016559) ([321779](https://maestro.dot.net/channel/2/github:dotnet:xharness/build/321779)) - **Date Produced**: July 7, 2026 6:05:10 PM UTC - **Commit**: [b631e17803318d67046871027986a005f3db02bf](https://github.com/dotnet/xharness/commit/b631e17803318d67046871027986a005f3db02bf) - **Branch**: [main](https://github.com/dotnet/xharness/tree/main) - **Dependency Updates**: - From [11.0.0-prerelease.26353.1 to 11.0.0-prerelease.26357.1][1] - Microsoft.DotNet.XHarness.iOS.Shared [1]: https://github.com/dotnet/xharness/compare/4da0015e9d...b631e17803 --- eng/Version.Details.props | 2 +- eng/Version.Details.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 0edeaf326381..bf8558982ca3 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -30,7 +30,7 @@ This file should be imported by eng/Versions.props 18.5.9227 26.5.9004 - 11.0.0-prerelease.26353.1 + 11.0.0-prerelease.26357.1 18.0.9617 18.0.9617 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ab891f2fbbee..bfb68d423fb1 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -107,9 +107,9 @@ https://github.com/dotnet/dotnet 0e603833cc19f22076e5fcac787ccd22f3900a51 - + https://github.com/dotnet/xharness - 4da0015e9dd1ed7d3992db7d1eb6878a5ee11ab2 + b631e17803318d67046871027986a005f3db02bf https://github.com/dotnet/dotnet From bf5e35a408140a2564a161561b8742c3fc43c586 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 8 Jul 2026 12:17:46 +0200 Subject: [PATCH 05/13] [assembly-preparer] Use a trimmer feature switch for DynamicRegistrationSupported. (#25939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When running as a step in the assembly-preparer, `RegistrarRemovalTrackingStep` used to rewrite `Runtime.get_DynamicRegistrationSupported` in the platform assembly (to return `false`) when it determined the dynamic registrar could be removed. That required the assembly-preparer to modify - and thus re-save - the platform assembly. Instead, compute the value and surface it to MSBuild, which enables the `ObjCRuntime.Runtime.DynamicRegistrationSupported` trimmer feature switch so that ILLink hardcodes the property (the same way it already does for e.g. `ObjCRuntime.Runtime.IsManagedStaticRegistrar`). This way the assembly-preparer doesn't have to touch any assembly for this optimization. ## How it works * Added `get_DynamicRegistrationSupported` substitution entries (feature `ObjCRuntime.Runtime.DynamicRegistrationSupported`) to the `ILLink.Substitutions.*.xml` for all four platforms. * `RegistrarRemovalTrackingStep` still computes `Optimizations.RemoveDynamicRegistrar`, but under `ASSEMBLY_PREPARER` it no longer rewrites the assembly - it reports the value via a new generic "MSBuild output property" mechanism instead. The real ILLink path (when `PrepareAssemblies` isn't enabled) is unchanged and still rewrites the assembly. ## Generic MSBuild output mechanism * `LinkerConfiguration.SetOutputForMSBuild (name, value)` collects output properties, and `FlushOutputForMSBuild` writes them - alphabetically sorted, one `Name=Value` per line - to a file. There's a separate file for the preparation and post-processing passes so the two passes don't clobber each other. * The files are added to `FileWrites` (when they exist after the assembly-preparer has run). * The `_SetDynamicRegistrationSupportedFeature` target (which runs after `_PrepareAssemblies` and before the trimmer) parses the `DynamicRegistrationSupported` property out of the preparation pass's output file and adds the `RuntimeHostConfigurationOption`. It's not incremental-gated and the file is persisted, so the feature switch is still set correctly when `_PrepareAssemblies` is skipped because its outputs are up-to-date. ## User override * Added a user-overridable `$(DynamicRegistrationSupported)` MSBuild property (documented in `build-properties.md`); the user value always wins. * When the user sets `$(DynamicRegistrationSupported)`, the value doesn't need to be computed, so `RegistrarRemovalTrackingStep` is skipped entirely in the preparation pass. ## Testing Added a `DynamicRegistrationSupportedTest` build test asserting the user value flows through to the feature switch. Also verified manually (default + explicit true/false) and with monotouch-test (`release|linksdk`, `PrepareAssemblies=true PostProcessAssemblies=true`). ## Note While working on this I ran into a pre-existing incremental-build failure with `PrepareAssemblies=true` (unrelated to this change); filed as #25938. 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/building-apps/build-properties.md | 17 ++++ dotnet/targets/Xamarin.Shared.Sdk.targets | 39 ++++++++ msbuild/Xamarin.Shared/Xamarin.Shared.targets | 4 +- src/ILLink.Substitutions.MacCatalyst.xml | 2 + src/ILLink.Substitutions.iOS.xml | 2 + src/ILLink.Substitutions.macOS.xml | 2 + src/ILLink.Substitutions.tvOS.xml | 2 + .../DynamicRegistrationSupportedTest.cs | 61 ++++++++++++ tools/assembly-preparer/AssemblyPreparer.cs | 29 ++++-- tools/dotnet-linker/LinkerConfiguration.cs | 95 +++++++++++++++---- tools/linker/RegistrarRemovalTrackingStep.cs | 29 ++++-- 11 files changed, 246 insertions(+), 36 deletions(-) create mode 100644 tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 78a10c4c2d9d..97525d1af61e 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -466,6 +466,23 @@ The full path to the `ditto` executable. The default behavior is to use `/usr/bin/ditto`. +## DynamicRegistrationSupported + +Controls whether the dynamic registrar is available at runtime (as reported by +`ObjCRuntime.Runtime.DynamicRegistrationSupported`). + +If this value is not specified, the build will compute a default value based +on whether the app needs the dynamic registrar, and enables the +`ObjCRuntime.Runtime.DynamicRegistrationSupported` trimmer feature switch +accordingly (so the trimmer can remove the dynamic registrar when it's not +needed, making the app smaller). + +Set this property to `true` or `false` to override the computed default. + +Removing the dynamic registrar requires a static registrar (`Registrar=static` or +`Registrar=managed-static`) and trimming, so setting this property has no effect (and the +build warns) when those conditions aren't met. + ## EmbedOnDemandResources If on-demand resources should be embedded in the app bundle. diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index d0e5e10e5d2f..ad6a24730095 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -272,6 +272,7 @@ _ExtendAppExtensionReferences; _ComputeLinkerArguments; _PrepareAssemblies; + _SetDynamicRegistrationSupportedFeature; _ComputeFrameworkFilesToPublish; _ComputeDynamicLibrariesToPublish; ComputeFilesToPublish; @@ -560,6 +561,37 @@ + + + + + + + <_DynamicRegistrationSupportedLine Include="@(_AssemblyPreparerMSBuildOutputProperty)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('DynamicRegistrationSupported='))" /> + + + + <_DynamicRegistrationSupportedLineText>@(_DynamicRegistrationSupportedLine) + <_DynamicRegistrationSupported>$(DynamicRegistrationSupported) + <_DynamicRegistrationSupported Condition="'$(_DynamicRegistrationSupported)' == '' And '$(_DynamicRegistrationSupportedLineText)' != ''">$(_DynamicRegistrationSupportedLineText.Replace('DynamicRegistrationSupported=', '')) + + + + + + @@ -607,6 +639,10 @@ <_CustomLinkerOptionsFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)custom-linker-options.txt')) <_CustomLinkerOptionsFile Condition="'$(BuildSessionId)' != ''">$(IntermediateOutputPath)custom-linker-options.txt + + <_AssemblyPreparerMSBuildOutputFile Condition="'$(PrepareAssemblies)' == 'true'">$(DeviceSpecificIntermediateOutputPath)assembly-preparer-msbuild-output.txt + <_AssemblyPostProcessorMSBuildOutputFile Condition="'$(PrepareAssemblies)' == 'true'">$(DeviceSpecificIntermediateOutputPath)assembly-postprocessor-msbuild-output.txt + <_LinkerItemsDirectory>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)linker-items')) <_LinkerItemsDirectory Condition="'$(BuildSessionId)' != ''">$(IntermediateOutputPath)linker-items @@ -649,6 +685,7 @@ @(_BundlerDlsym -> 'Dlsym=%(Identity)') Debug=$(_BundlerDebug) DeploymentTarget=$(_MinimumOSVersion) + DynamicRegistrationSupported=$(DynamicRegistrationSupported) @(_CustomLinkFlags -> 'CustomLinkFlags=%(Identity)') EnableSGenConc=$(EnableSGenConc) @(_BundlerEnvironmentVariables -> 'EnvironmentVariable=Overwrite=%(Overwrite)|%(Identity)=%(Value)') @@ -668,6 +705,8 @@ MarshalManagedExceptionMode=$(MarshalManagedExceptionMode) MarshalObjectiveCExceptionMode=$(MarshalObjectiveCExceptionMode) @(_MonoLibrary -> 'MonoLibrary=%(Identity)') + MSBuildOutputFile=$(_AssemblyPreparerMSBuildOutputFile) + MSBuildPostProcessOutputFile=$(_AssemblyPostProcessorMSBuildOutputFile) MtouchFloat32=$(MtouchFloat32) NoWarn=$(_BundlerNoWarn) Optimize=$(_BundlerOptimize) diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index edb94b8b57b2..413442c5494a 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -3502,7 +3502,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. Name="_PrepareAssemblies" Condition="'$(PrepareAssemblies)' == 'true'" Inputs="@(_AssembliesToPrepare)" - Outputs="@(_AssembliesToPrepare->'$(PreparedAssembliesDirectory)%(Filename)%(Extension)')" + Outputs="@(_AssembliesToPrepare->'$(PreparedAssembliesDirectory)%(Filename)%(Extension)');$(_AssemblyPreparerMSBuildOutputFile)" DependsOnTargets="_PrepareAssembliesForPreparation;_ComputeAssembliesToPostprocessOnPublish;SelectRegistrar" > + @@ -3565,6 +3566,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. + diff --git a/src/ILLink.Substitutions.MacCatalyst.xml b/src/ILLink.Substitutions.MacCatalyst.xml index da45aabaf46c..6cf57b701e0a 100644 --- a/src/ILLink.Substitutions.MacCatalyst.xml +++ b/src/ILLink.Substitutions.MacCatalyst.xml @@ -10,6 +10,8 @@ + + diff --git a/src/ILLink.Substitutions.iOS.xml b/src/ILLink.Substitutions.iOS.xml index 3436035d8aff..e778b006756d 100644 --- a/src/ILLink.Substitutions.iOS.xml +++ b/src/ILLink.Substitutions.iOS.xml @@ -12,6 +12,8 @@ + + diff --git a/src/ILLink.Substitutions.macOS.xml b/src/ILLink.Substitutions.macOS.xml index d93fac786837..3c1ddefda20d 100644 --- a/src/ILLink.Substitutions.macOS.xml +++ b/src/ILLink.Substitutions.macOS.xml @@ -9,6 +9,8 @@ + + diff --git a/src/ILLink.Substitutions.tvOS.xml b/src/ILLink.Substitutions.tvOS.xml index 7131bcf39363..4ea0b88f2fe3 100644 --- a/src/ILLink.Substitutions.tvOS.xml +++ b/src/ILLink.Substitutions.tvOS.xml @@ -12,6 +12,8 @@ + + diff --git a/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs b/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs new file mode 100644 index 000000000000..59976ca3ccd6 --- /dev/null +++ b/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Build.Framework; +using Microsoft.Build.Logging.StructuredLogger; + +#nullable enable + +namespace Xamarin.Tests { + [TestFixture] + public class DynamicRegistrationSupportedTest : TestBaseClass { + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64", "true")] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64", "false")] + public void UserSpecifiedValue (ApplePlatform platform, string runtimeIdentifiers, string dynamicRegistrationSupported) + { + // When the user sets $(DynamicRegistrationSupported), the value must be passed straight through to + // the 'ObjCRuntime.Runtime.DynamicRegistrationSupported' trimmer feature switch (the assembly-preparer + // doesn't need to compute it, so RegistrarRemovalTrackingStep is skipped). + var project = "MySimpleApp"; + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var project_path = GetProjectPath (project, platform: platform); + Clean (project_path); + var properties = GetDefaultProperties (runtimeIdentifiers); + // DynamicRegistrationSupported maps to the 'remove-dynamic-registrar' optimization, which is only + // valid with a static registrar, so use one (managed-static) and enable trimming. + properties ["MtouchLink"] = "SdkOnly"; + properties ["Registrar"] = "managed-static"; + properties ["PrepareAssemblies"] = "true"; + properties ["PostProcessAssemblies"] = "true"; + properties ["DynamicRegistrationSupported"] = dynamicRegistrationSupported; + + var rv = DotNet.AssertBuild (project_path, properties); + + var featureSwitch = GetRuntimeHostConfigurationOption (rv.BinLogPath, "ObjCRuntime.Runtime.DynamicRegistrationSupported"); + Assert.That (featureSwitch, Is.Not.Null, "The DynamicRegistrationSupported feature switch must be set."); + Assert.That (featureSwitch?.GetMetadata ("Value"), Is.EqualTo (dynamicRegistrationSupported), "The feature switch value must match the user-specified value."); + } + + // Returns the last RuntimeHostConfigurationOption item with the given name (ItemSpec) added during the build. + static ITaskItem? GetRuntimeHostConfigurationOption (string binLogPath, string name) + { + ITaskItem? rv = null; + foreach (var args in BinLog.ReadBuildEvents (binLogPath)) { + if (args is not TaskParameterEventArgs tpea) + continue; + if (tpea.Kind != TaskParameterMessageKind.AddItem) + continue; + if (tpea.ItemType != "RuntimeHostConfigurationOption") + continue; + foreach (var item in tpea.Items) { + if (item is ITaskItem taskItem && taskItem.ItemSpec == name) + rv = taskItem; + } + } + return rv; + } + } +} diff --git a/tools/assembly-preparer/AssemblyPreparer.cs b/tools/assembly-preparer/AssemblyPreparer.cs index 53caadb1dd1d..32ce33de331f 100644 --- a/tools/assembly-preparer/AssemblyPreparer.cs +++ b/tools/assembly-preparer/AssemblyPreparer.cs @@ -116,8 +116,8 @@ public static AssemblyPreparer LoadFromReproPath (string reproPath) public bool Prepare (out List exceptions) { - var steps = new ConfigurationAwareStep [] { - // All the same steps as the custom trimmer steps that are run before MarkStep in Xamarin.Shared.Sdk.targets (and in the same order). + // All the same steps as the custom trimmer steps that are run before MarkStep in Xamarin.Shared.Sdk.targets (and in the same order). + var steps = new List { // CollectAssembliesStep new LoadAssembliesStep (), new ComputeMethodOverridesStep (), @@ -131,14 +131,20 @@ public bool Prepare (out List exceptions) new MarkForStaticRegistrarStep (), new MarkNSObjectsStep (), new InlineDlfcnMethodsStep (), - new RegistrarRemovalTrackingStep (), - // PreMarkDispatcher: I don't think we need this one - new ManagedRegistrarStep (), - new TrimmableRegistrarStep (), - new ManagedRegistrarLookupTablesStep (), - new InlineClassGetHandleStep (), - new SaveAssembliesStep (), }; + + // If the user explicitly set $(DynamicRegistrationSupported), we don't need to compute the value, so + // skip RegistrarRemovalTrackingStep entirely (the value is passed straight through to the trimmer feature switch). + if (!configuration.DynamicRegistrationSupported.HasValue) + steps.Add (new RegistrarRemovalTrackingStep ()); + + // PreMarkDispatcher: I don't think we need this one + steps.Add (new ManagedRegistrarStep ()); + steps.Add (new TrimmableRegistrarStep ()); + steps.Add (new ManagedRegistrarLookupTablesStep ()); + steps.Add (new InlineClassGetHandleStep ()); + steps.Add (new SaveAssembliesStep ()); + return RunSteps (steps, out exceptions); } @@ -207,6 +213,11 @@ bool RunSteps (IList steps, out List e step.Process (linkContext); } + // The post-processing pass flushes its MSBuild output as its last step (DoneStep). The preparation + // pass has no DoneStep, so flush here so that its MSBuild output properties are written. + if (!configuration.Application.IsPostProcessingAssemblies) + configuration.FlushOutputForMSBuild (); + return exceptions.Count == 0; } diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index a5f94cceb61d..ca103ddf6557 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -30,6 +30,9 @@ public class LinkerConfiguration { public string DedupAssembly = string.Empty; public string CacheDirectory { get; private set; } = string.Empty; public Version? DeploymentTarget { get; private set; } + // The user-provided value of the $(DynamicRegistrationSupported) MSBuild property (null if not set). + // When set, RegistrarRemovalTrackingStep doesn't need to run in the assembly-preparer. + public bool? DynamicRegistrationSupported { get; private set; } public HashSet FrameworkAssemblies { get; private set; } = new HashSet (); public string IntermediateLinkDir { get; private set; } = string.Empty; public bool InvariantGlobalization { get; private set; } @@ -43,6 +46,9 @@ public class LinkerConfiguration { public HashSet FieldSymbols { get; } = new HashSet (); public string IntermediateOutputPath { get; private set; } = string.Empty; public string ItemsDirectory { get; private set; } = string.Empty; + // The files the assembly-preparer writes its MSBuild output properties to (one per pass). + public string MSBuildOutputFile { get; private set; } = string.Empty; + public string MSBuildPostProcessOutputFile { get; private set; } = string.Empty; public bool IsSimulatorBuild { get; private set; } public string PartialStaticRegistrarLibrary { get; set; } = string.Empty; public ApplePlatform Platform { get; private set; } @@ -100,6 +106,10 @@ public List Exceptions { Dictionary> msbuild_items = new Dictionary> (); + // MSBuild output properties (property name -> value), written (alphabetically sorted) to the + // MSBuild output file by FlushOutputForMSBuild. + SortedDictionary msbuild_properties = new SortedDictionary (StringComparer.Ordinal); + AppBundleRewriter? abr; internal AppBundleRewriter AppBundleRewriter { get { @@ -286,6 +296,24 @@ Configurator GetConfigurator (string linker_file) } }) )}, + { "DynamicRegistrationSupported", ( + // This is the user-overridable $(DynamicRegistrationSupported) MSBuild property. It maps to + // the RemoveDynamicRegistrar optimization (inverted): if dynamic registration is supported, + // then we're not removing the dynamic registrar. When set, RegistrarRemovalTrackingStep doesn't + // need to run in the assembly-preparer (the value is passed straight through to the trimmer + // feature switch), and it won't recompute the value in the real linker either. + new LoadValue ((key, value) => { + if (string.IsNullOrEmpty (value)) + return; // Not set: RegistrarRemovalTrackingStep will compute a default value. + if (!TryParseOptionalBoolean (value, out var dynamicRegistrationSupported)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + if (dynamicRegistrationSupported.HasValue) { + DynamicRegistrationSupported = dynamicRegistrationSupported.Value; + Application.Optimizations.RemoveDynamicRegistrar = !dynamicRegistrationSupported.Value; + } + }), + new SaveValue ((key, storage) => saveNullableBool (key, DynamicRegistrationSupported, storage)) + )}, { "EnableSGenConc", ( new LoadValue ((key, value) => Application.EnableSGenConc = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.EnableSGenConc ? "true" : "false")}")) @@ -404,6 +432,16 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => Application.MonoLibraries.Add (value)), new SaveValue ((key, storage) => storage.AddRange (Application.MonoLibraries.OrderBy (v => v).Select (v => $"{key}={v}"))) )}, + { "MSBuildOutputFile", ( + // The file the assembly-preparer's preparation pass writes its MSBuild output properties to. + new LoadValue ((key, value) => MSBuildOutputFile = value), + new SaveValue ((key, storage) => saveNonEmpty (key, MSBuildOutputFile, storage)) + )}, + { "MSBuildPostProcessOutputFile", ( + // The file the assembly-preparer's post-processing pass writes its MSBuild output properties to. + new LoadValue ((key, value) => MSBuildPostProcessOutputFile = value), + new SaveValue ((key, storage) => saveNonEmpty (key, MSBuildPostProcessOutputFile, storage)) + )}, { "MtouchFloat32", ( new LoadValue ((key, value) => loadNullableBool (key, value, out Application.AotFloat32)), new SaveValue ((key, storage) => saveNullableBool (key, Application.AotFloat32, storage)) @@ -866,25 +904,48 @@ public void WriteOutputForMSBuild (string itemName, List items) } } + // Register an MSBuild output property. The collected properties are written to the MSBuild + // output file (see FlushOutputForMSBuild) so that MSBuild can read them back. + public void SetOutputForMSBuild (string propertyName, string value) + { + msbuild_properties [propertyName] = value; + } + public void FlushOutputForMSBuild () { - Directory.CreateDirectory (ItemsDirectory); - foreach (var kvp in msbuild_items) { - var itemName = kvp.Key; - var items = kvp.Value; - - var xmlNs = XNamespace.Get ("http://schemas.microsoft.com/developer/msbuild/2003"); - var elements = items.Select (item => - new XElement (xmlNs + itemName, - new XAttribute ("Include", item.Include), - item.Metadata.Select (metadata => new XElement (xmlNs + metadata.Key, metadata.Value)))); - - var document = new XDocument ( - new XElement (xmlNs + "Project", - new XElement (xmlNs + "ItemGroup", - elements))); - - document.Save (Path.Combine (ItemsDirectory, itemName + ".items")); + // ItemsDirectory isn't set when running in the assembly-preparer, so only write + // the item files when we have a directory to write them to. + if (!string.IsNullOrEmpty (ItemsDirectory)) { + Directory.CreateDirectory (ItemsDirectory); + foreach (var kvp in msbuild_items) { + var itemName = kvp.Key; + var items = kvp.Value; + + var xmlNs = XNamespace.Get ("http://schemas.microsoft.com/developer/msbuild/2003"); + var elements = items.Select (item => + new XElement (xmlNs + itemName, + new XAttribute ("Include", item.Include), + item.Metadata.Select (metadata => new XElement (xmlNs + metadata.Key, metadata.Value)))); + + var document = new XDocument ( + new XElement (xmlNs + "Project", + new XElement (xmlNs + "ItemGroup", + elements))); + + document.Save (Path.Combine (ItemsDirectory, itemName + ".items")); + } + } + + // Write the collected MSBuild output properties (alphabetically sorted, one 'Name=Value' per line) + // to the output file for the current pass, so that MSBuild can read them back. We always write the + // file (even when there are no properties), so it's a consistent, persistent artifact of the pass + // (it's added to FileWrites by the _PrepareAssemblies/_PostprocessAssemblies targets). + var outputFile = Application.IsPostProcessingAssemblies ? MSBuildPostProcessOutputFile : MSBuildOutputFile; + if (!string.IsNullOrEmpty (outputFile)) { + var directory = Path.GetDirectoryName (outputFile); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); + File.WriteAllLines (outputFile, msbuild_properties.Select (kvp => $"{kvp.Key}={kvp.Value}")); } } diff --git a/tools/linker/RegistrarRemovalTrackingStep.cs b/tools/linker/RegistrarRemovalTrackingStep.cs index c295752c86cc..36f9e17306fb 100644 --- a/tools/linker/RegistrarRemovalTrackingStep.cs +++ b/tools/linker/RegistrarRemovalTrackingStep.cs @@ -28,7 +28,9 @@ protected override void TryProcessAssembly (AssemblyDefinition assembly) Process (assembly); } +#if !ASSEMBLY_PREPARER AssemblyDefinition? PlatformAssembly; +#endif bool dynamic_registration_support_required; @@ -49,7 +51,9 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) if (Profile.IsProductAssembly (assembly)) { if (Annotations.GetAction (assembly) != AssemblyAction.Link) return false; +#if !ASSEMBLY_PREPARER PlatformAssembly = assembly; +#endif } // Can't touch the forbidden fruit in the product assembly unless there's a reference to it @@ -166,20 +170,27 @@ protected override void TryEndProcess () App.Log (4, "Optimization dynamic registrar removal: {0}", Optimizations.RemoveDynamicRegistrar.Value ? "enabled" : "disabled"); - if (Optimizations.RemoveDynamicRegistrar.Value) { +#if ASSEMBLY_PREPARER + // In the assembly-preparer we don't rewrite the platform assembly. Instead we surface the computed + // value to MSBuild (as the DynamicRegistrationSupported output property), which enables the + // 'ObjCRuntime.Runtime.DynamicRegistrationSupported' trimmer feature switch so that ILLink hardcodes + // Runtime.DynamicRegistrationSupported. This way the assembly-preparer doesn't have to modify (and + // thus re-save) any assembly. + Configuration.SetOutputForMSBuild ("DynamicRegistrationSupported", App.DynamicRegistrationSupported ? "true" : "false"); +#else + if (Optimizations.RemoveDynamicRegistrar.Value && PlatformAssembly is not null) { // ILLink will optimize `Runtime.Initialize` based on `DynamicRegistrationSupported` returning a constant (`true`) // and this will runs before we have the chance to set it to `false` in `CoreOptimizedGeneratedCode` so we instead // do the change here so the linker can do this without further ado // note: it does not matter for _legacy_ so we apply the change (to earlier) to minimize the difference between them - if (PlatformAssembly is not null) { - var method = PlatformAssembly.MainModule.GetType ("ObjCRuntime.Runtime").Methods.First ((n) => n.Name == "get_DynamicRegistrationSupported"); - // Rewrite to return 'false' - var instr = method.Body.Instructions; - instr.Clear (); - instr.Add (Instruction.Create (OpCodes.Ldc_I4_0)); - instr.Add (Instruction.Create (OpCodes.Ret)); - } + var method = PlatformAssembly.MainModule.GetType ("ObjCRuntime.Runtime").Methods.First ((n) => n.Name == "get_DynamicRegistrationSupported"); + // Rewrite to return 'false' + var instr = method.Body.Instructions; + instr.Clear (); + instr.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + instr.Add (Instruction.Create (OpCodes.Ret)); } +#endif } } } From 7f1ad70d1fb21b3f0ec2e74c771745e8db16d6fb Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 8 Jul 2026 12:18:18 +0200 Subject: [PATCH 06/13] [assembly-preparer] Log per-step timings and whether each step modified assemblies. (#25944) Add timing information to the assembly-preparer's PrepareAssemblies task: for each step (in both the Prepare and PostProcess passes) log - at MessageImportance.Low - how long the step took, whether it modified any assemblies, and a total duration for all steps. The fixed-width duration is printed before the (variable-width) step name so the durations line up between all the log lines. To know whether a step modified any assemblies, add an AssemblySaved callback to the AppBundleRewriter (invoked whenever SaveAssembly is called). All the steps that modify assemblies go through AppBundleRewriter.SaveAssembly (except two that's being fixed in separate PRs to either not save (RegistrarRemovalTrackingStep) or to subclass AssemblyModifierStep). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/PrepareAssemblies.cs | 7 ++++ tools/assembly-preparer/AssemblyPreparer.cs | 34 +++++++++++++++++-- tools/dotnet-linker/AppBundleRewriter.cs | 4 +++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs index 9a94d2bb4cb8..fb9e61e6d893 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs @@ -75,6 +75,13 @@ public override bool Execute () rv = preparer.Prepare (out exceptions); } + var totalDuration = TimeSpan.Zero; + foreach (var step in preparer.StepExecutions) { + totalDuration += step.Duration; + Log.LogMessage (MessageImportance.Low, $"{step.Duration.ToString (@"hh\:mm\:ss\.fffffff")} {step.Name}: {(step.ModifiedAssemblies ? " ✏️ modified one or more assemblies" : " ✅ did not modify any assemblies")}"); + } + Log.LogMessage (MessageImportance.Low, $"{totalDuration.ToString (@"hh\:mm\:ss\.fffffff")} Total for all steps"); + foreach (var pe in exceptions) { if (pe.IsError (this)) { ((IToolLog) this).LogError (pe); diff --git a/tools/assembly-preparer/AssemblyPreparer.cs b/tools/assembly-preparer/AssemblyPreparer.cs index 32ce33de331f..520bd720ccd9 100644 --- a/tools/assembly-preparer/AssemblyPreparer.cs +++ b/tools/assembly-preparer/AssemblyPreparer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics; using System.IO; using System.Runtime.Serialization; using Mono.Cecil; @@ -40,6 +41,12 @@ public string IntermediateOutputPath { public List Assemblies { get; set; } = new List (); + // The list of steps that were executed, along with how long each step took and whether it modified any assemblies. + public List StepExecutions { get; } = new List (); + + // Set to true (via the AppBundleRewriter.AssemblySaved callback) whenever the currently executing step modifies an assembly. + bool currentStepModifiedAssemblies; + public IList<(string Path, AssemblyDefinition Assembly, string? OriginatingAssembly)> AddedAssemblies => configuration.AddedAssemblies; LinkerConfiguration.Configurator GetConfigurator (string? reproPath = null, Func? assemblyPreparerInfoFactory = null) @@ -209,8 +216,28 @@ bool RunSteps (IList steps, out List e var linkContext = configuration.DerivedLinkContext; - foreach (var step in steps) { - step.Process (linkContext); + // We detect whether a step modified any assemblies by subscribing to the AppBundleRewriter's + // AssemblySaved callback, which is called whenever an assembly is modified. All the steps that + // modify assemblies go through the AppBundleRewriter to do so. + Action? assemblySavedHandler = null; + try { + foreach (var step in steps) { + // Subscribe once the assemblies have been loaded: accessing the AppBundleRewriter before + // that point would create it without finding the corlib and platform assemblies. + if (assemblySavedHandler is null && configuration.Assemblies.Count > 0) { + assemblySavedHandler = (_) => currentStepModifiedAssemblies = true; + configuration.AppBundleRewriter.AssemblySaved += assemblySavedHandler; + } + + currentStepModifiedAssemblies = false; + var watch = Stopwatch.StartNew (); + step.Process (linkContext); + watch.Stop (); + StepExecutions.Add (new StepExecution (step.GetType ().Name, watch.Elapsed, currentStepModifiedAssemblies)); + } + } finally { + if (assemblySavedHandler is not null) + configuration.AppBundleRewriter.AssemblySaved -= assemblySavedHandler; } // The post-processing pass flushes its MSBuild output as its last step (DoneStep). The preparation @@ -230,6 +257,9 @@ public void Dispose () } } +// The result of executing a single step: its name, how long it took, and whether it modified any assemblies. +public record struct StepExecution (string Name, TimeSpan Duration, bool ModifiedAssemblies); + public class AssemblyPreparerInfo { internal AssemblyDefinition? Assembly { get; set; } internal bool IsCILAssembly { get; set; } diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index e9257f6727a5..db82fb797034 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -29,6 +29,9 @@ class AppBundleRewriter { AssemblyDefinition? corlib_assembly; AssemblyDefinition? platform_assembly; + // Invoked whenever an assembly is modified (i.e. whenever SaveAssembly is called). + public Action? AssemblySaved { get; set; } + public AssemblyDefinition CurrentAssembly { get { if (current_assembly is null) @@ -1455,6 +1458,7 @@ void SaveAssembly (AssemblyDefinition assembly) { if (assembly != CurrentAssembly && assembly != PlatformAssembly) throw new InvalidOperationException ($"Can't save assembly {assembly.Name} because it's not the current assembly ({CurrentAssembly.Name}) or the platform assembly ({PlatformAssembly.Name})."); + AssemblySaved?.Invoke (assembly); var annotations = configuration.Context.Annotations; var action = annotations.GetAction (assembly); if (action == AssemblyAction.Copy) { From 8433b172d074ceb840dfdf4c68f261e513a87c69 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 8 Jul 2026 12:19:33 +0200 Subject: [PATCH 07/13] [msbuild/dotnet] Fix remote Release builds when using plural RuntimeIdentifiers. Fixes #24046. (#24228) The problem is that when using RuntimeIdentifiers (plural), we have an outer build (for the plural version) and an inner build (for the singular version), and the session id is different between them. This means the outer build can't see the outputs from the inner build, because they're not in the same location on the Mac. The fix is to pass on the session id from the outer to the inner build, so that the inner build uses the same session id as the outer build. Fixes https://github.com/dotnet/macios/issues/24046. Fixes https://github.com/dotnet/macios/issues/25438 (once merged into `net11.0`). --------- Co-authored-by: Rolf Bjarne Kvinge --- dotnet/targets/Xamarin.Shared.Sdk.targets | 2 ++ .../Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets | 2 +- tests/dotnet/UnitTests/ProjectTest.cs | 5 +++-- tests/dotnet/UnitTests/WindowsTest.cs | 9 +++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index ad6a24730095..4c11d8f1ee04 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -428,6 +428,8 @@ Targets="_BuildRidSpecificAppBundle" Properties=" AppendRuntimeIdentifierToOutputPath=true; + BuildSessionId=$(BuildSessionId); + BuildAppName=$(BuildAppName); RuntimeIdentifier=%(_RuntimeIdentifiersAsItems.Identity); _CodesignItemsPath=%(_RuntimeIdentifiersAsItems.RidSpecificCodesignItemsPath); _MtouchSymbolsList=%(_RuntimeIdentifiersAsItems.RidSpecificSymbolsListPath); diff --git a/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets b/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets index 9a07dc40030d..0341c8a3cf17 100644 --- a/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets +++ b/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets @@ -71,7 +71,7 @@ - + ? extraProperties = null) + internal static void PluralRuntimeIdentifiersImpl (ApplePlatform platform, string runtimeIdentifiers, Dictionary? extraProperties = null, string configuration = "Debug") { var project = "MySimpleApp"; Configuration.IgnoreIfIgnoredPlatform (platform); Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); - var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath); + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration); Clean (project_path); var properties = GetDefaultProperties (extraProperties: extraProperties); + properties ["Configuration"] = configuration; properties ["RuntimeIdentifiers"] = runtimeIdentifiers; DotNet.AssertBuild (project_path, properties); diff --git a/tests/dotnet/UnitTests/WindowsTest.cs b/tests/dotnet/UnitTests/WindowsTest.cs index e156da865385..c6e887dfe56b 100644 --- a/tests/dotnet/UnitTests/WindowsTest.cs +++ b/tests/dotnet/UnitTests/WindowsTest.cs @@ -261,12 +261,13 @@ public void BundleStructureWithRemoteMac (ApplePlatform platform, string runtime } [Category ("RemoteWindows")] - [TestCase (ApplePlatform.iOS, "ios-arm64")] - [TestCase (ApplePlatform.iOS, "iossimulator-arm64;iossimulator-x64")] - public void PluralRuntimeIdentifiersWithRemoteMac (ApplePlatform platform, string runtimeIdentifiers) + [TestCase (ApplePlatform.iOS, "ios-arm64", "Debug")] + [TestCase (ApplePlatform.iOS, "ios-arm64", "Release")] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64;iossimulator-x64", "Debug")] + public void PluralRuntimeIdentifiersWithRemoteMac (ApplePlatform platform, string runtimeIdentifiers, string configuration) { var properties = AddRemoteProperties (); - DotNetProjectTest.PluralRuntimeIdentifiersImpl (platform, runtimeIdentifiers, properties); + DotNetProjectTest.PluralRuntimeIdentifiersImpl (platform, runtimeIdentifiers, properties, configuration); } [Category ("RemoteWindows")] From 3537dfd03bc5b47f2052c1c8e2e7200add6282dc Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:32:56 +0000 Subject: [PATCH 08/13] [main] Update dependencies from dotnet/dotnet (#25969) This pull request updates the following dependencies ## From https://github.com/dotnet/dotnet - **Subscription**: [da09b56a-0fb1-439a-b894-def14d2ec0a4](https://maestro.dot.net/subscriptions?search=da09b56a-0fb1-439a-b894-def14d2ec0a4) - **Build**: [20260707.10](https://dev.azure.com/dnceng/internal/_build/results?buildId=3016581) ([321816](https://maestro.dot.net/channel/10307/github:dotnet:dotnet/build/321816)) - **Date Produced**: July 7, 2026 8:37:06 PM UTC - **Commit**: [2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223](https://github.com/dotnet/dotnet/commit/2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223) - **Branch**: [release/10.0.4xx](https://github.com/dotnet/dotnet/tree/release/10.0.4xx) - **Dependency Updates**: - From [10.0.0-beta.26356.110 to 10.0.0-beta.26357.110][1] - Microsoft.DotNet.Arcade.Sdk - Microsoft.DotNet.Build.Tasks.Feed - Microsoft.DotNet.SharedFramework.Sdk - From [10.0.400-preview.0.26356.110 to 10.0.400-preview.0.26357.110][1] - Microsoft.NET.Sdk - From [10.0.400-preview.26356.110 to 10.0.400-preview.26357.110][1] - Microsoft.TemplateEngine.Authoring.Tasks [1]: https://github.com/dotnet/dotnet/compare/0e603833cc...2bd0e65f2e --- eng/Version.Details.props | 10 +- eng/Version.Details.xml | 20 +- .../core-templates/steps/publish-logs.yml | 3 - .../steps/source-index-stage1-publish.yml | 4 +- eng/common/cross/build-rootfs.sh | 197 +++++++++++------- eng/common/cross/install-debs.py | 151 ++++++++++---- eng/common/cross/toolchain.cmake | 40 ++-- eng/common/darc-init.ps1 | 8 +- eng/common/darc-init.sh | 4 +- .../native/LocateNativeCompiler.targets | 27 +++ eng/common/native/NativeAotSupported.props | 26 +++ eng/common/native/init-distro-rid.sh | 2 + eng/common/native/install-dependencies.sh | 11 +- eng/common/post-build/redact-logs.ps1 | 4 +- eng/common/templates/steps/vmr-sync.yml | 20 +- eng/common/templates/vmr-build-pr.yml | 2 +- global.json | 6 +- 17 files changed, 365 insertions(+), 170 deletions(-) create mode 100644 eng/common/native/LocateNativeCompiler.targets create mode 100644 eng/common/native/NativeAotSupported.props diff --git a/eng/Version.Details.props b/eng/Version.Details.props index bf8558982ca3..b940d445fb64 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,16 +6,16 @@ This file should be imported by eng/Versions.props - 10.0.0-beta.26356.110 - 10.0.0-beta.26356.110 + 10.0.0-beta.26357.110 + 10.0.0-beta.26357.110 0.11.5-alpha.26070.104 - 10.0.0-beta.26356.110 + 10.0.0-beta.26357.110 10.0.3-servicing.26070.104 10.0.3 10.0.3 - 10.0.400-preview.0.26356.110 + 10.0.400-preview.0.26357.110 10.0.3 - 10.0.400-preview.26356.110 + 10.0.400-preview.26357.110 26.0.11017 18.5.9227 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index bfb68d423fb1..5d2ae04cb01c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,8 +1,8 @@ - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 https://github.com/dotnet/dotnet @@ -95,25 +95,25 @@ - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 https://github.com/dotnet/xharness b631e17803318d67046871027986a005f3db02bf - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 diff --git a/eng/common/core-templates/steps/publish-logs.yml b/eng/common/core-templates/steps/publish-logs.yml index 4eed0312b807..694f55a926e6 100644 --- a/eng/common/core-templates/steps/publish-logs.yml +++ b/eng/common/core-templates/steps/publish-logs.yml @@ -33,9 +33,6 @@ steps: '$(publishing-dnceng-devdiv-code-r-build-re)' '$(dn-bot-all-orgs-artifact-feeds-rw)' '$(akams-client-id)' - '$(microsoft-symbol-server-pat)' - '$(symweb-symbol-server-pat)' - '$(dnceng-symbol-server-pat)' '$(dn-bot-all-orgs-build-rw-code-rw)' '$(System.AccessToken)' ${{parameters.CustomSensitiveDataList}} diff --git a/eng/common/core-templates/steps/source-index-stage1-publish.yml b/eng/common/core-templates/steps/source-index-stage1-publish.yml index e9a694afa58e..6e7666b4dcf8 100644 --- a/eng/common/core-templates/steps/source-index-stage1-publish.yml +++ b/eng/common/core-templates/steps/source-index-stage1-publish.yml @@ -14,8 +14,8 @@ steps: workingDirectory: $(Agent.TempDirectory) - script: | - $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools - $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools displayName: "Source Index: Download netsourceindex Tools" # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. workingDirectory: $(Agent.TempDirectory) diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 8abfb71f7275..3150ccac6fcd 100644 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -5,10 +5,11 @@ set -e usage() { echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [llvmx[.y]] [--skipunmount] --rootfsdir ]" - echo "BuildArch can be: arm(default), arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64, x86" + echo "BuildArch can be: arm(default), arm64, loongarch64, ppc64le, riscv64, s390x, x64, x86" echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine" echo " for alpine can be specified with version: alpineX.YY or alpineedge" echo " for FreeBSD can be: freebsd13, freebsd14" + echo " for OpenBSD can be: openbsd" echo " for illumos can be: illumos" echo " for Haiku can be: haiku." echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FreeBSD" @@ -27,6 +28,8 @@ __BuildArch=arm __AlpineArch=armv7 __FreeBSDArch=arm __FreeBSDMachineArch=armv7 +__OpenBSDArch=arm +__OpenBSDMachineArch=armv7 __IllumosArch=arm7 __HaikuArch=arm __QEMUArch=arm @@ -72,8 +75,8 @@ __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" -__FreeBSDBase="13.4-RELEASE" -__FreeBSDPkg="1.21.3" +__FreeBSDBase="13.5-RELEASE" +__FreeBSDPkg="2.7.5" __FreeBSDABI="13" __FreeBSDPackages="libunwind" __FreeBSDPackages+=" icu" @@ -82,6 +85,13 @@ __FreeBSDPackages+=" openssl" __FreeBSDPackages+=" krb5" __FreeBSDPackages+=" terminfo-db" +__OpenBSDVersion="7.8" +__OpenBSDPackages="heimdal-libs" +__OpenBSDPackages+=" icu4c" +__OpenBSDPackages+=" libinotify" +__OpenBSDPackages+=" openssl" +__OpenBSDPackages+=" e2fsprogs" + __IllumosPackages="icu" __IllumosPackages+=" mit-krb5" __IllumosPackages+=" openssl" @@ -130,7 +140,6 @@ __AlpineKeys=' 616db30d:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpUpyWDWjlUk3smlWeA0\nlIMW+oJ38t92CRLHH3IqRhyECBRW0d0aRGtq7TY8PmxjjvBZrxTNDpJT6KUk4LRm\na6A6IuAI7QnNK8SJqM0DLzlpygd7GJf8ZL9SoHSH+gFsYF67Cpooz/YDqWrlN7Vw\ntO00s0B+eXy+PCXYU7VSfuWFGK8TGEv6HfGMALLjhqMManyvfp8hz3ubN1rK3c8C\nUS/ilRh1qckdbtPvoDPhSbTDmfU1g/EfRSIEXBrIMLg9ka/XB9PvWRrekrppnQzP\nhP9YE3x/wbFc5QqQWiRCYyQl/rgIMOXvIxhkfe8H5n1Et4VAorkpEAXdsfN8KSVv\nLSMazVlLp9GYq5SUpqYX3KnxdWBgN7BJoZ4sltsTpHQ/34SXWfu3UmyUveWj7wp0\nx9hwsPirVI00EEea9AbP7NM2rAyu6ukcm4m6ATd2DZJIViq2es6m60AE6SMCmrQF\nwmk4H/kdQgeAELVfGOm2VyJ3z69fQuywz7xu27S6zTKi05Qlnohxol4wVb6OB7qG\nLPRtK9ObgzRo/OPumyXqlzAi/Yvyd1ZQk8labZps3e16bQp8+pVPiumWioMFJDWV\nGZjCmyMSU8V6MB6njbgLHoyg2LCukCAeSjbPGGGYhnKLm1AKSoJh3IpZuqcKCk5C\n8CM1S15HxV78s9dFntEqIokCAwEAAQ== 66ba20fe:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtfB12w4ZgqsXWZDfUAV/\n6Y4aHUKIu3q4SXrNZ7CXF9nXoAVYrS7NAxJdAodsY3vPCN0g5O8DFXR+390LdOuQ\n+HsGKCc1k5tX5ZXld37EZNTNSbR0k+NKhd9h6X3u6wqPOx7SIKxwAQR8qeeFq4pP\nrt9GAGlxtuYgzIIcKJPwE0dZlcBCg+GnptCUZXp/38BP1eYC+xTXSL6Muq1etYfg\nodXdb7Yl+2h1IHuOwo5rjgY5kpY7GcAs8AjGk3lDD/av60OTYccknH0NCVSmPoXK\nvrxDBOn0LQRNBLcAfnTKgHrzy0Q5h4TNkkyTgxkoQw5ObDk9nnabTxql732yy9BY\ns+hM9+dSFO1HKeVXreYSA2n1ndF18YAvAumzgyqzB7I4pMHXq1kC/8bONMJxwSkS\nYm6CoXKyavp7RqGMyeVpRC7tV+blkrrUml0BwNkxE+XnwDRB3xDV6hqgWe0XrifD\nYTfvd9ScZQP83ip0r4IKlq4GMv/R5shcCRJSkSZ6QSGshH40JYSoiwJf5FHbj9ND\n7do0UAqebWo4yNx63j/wb2ULorW3AClv0BCFSdPsIrCStiGdpgJDBR2P2NZOCob3\nG9uMj+wJD6JJg2nWqNJxkANXX37Qf8plgzssrhrgOvB0fjjS7GYhfkfmZTJ0wPOw\nA8+KzFseBh4UFGgue78KwgkCAwEAAQ== ' -__Keyring= __KeyringFile="/usr/share/keyrings/ubuntu-archive-keyring.gpg" __SkipSigCheck=0 __SkipEmulation=0 @@ -153,6 +162,10 @@ while :; do __AlpineArch=armv7 __QEMUArch=arm ;; + armel) + # this is only used for tizen-build-rootfs.sh + __BuildArch=armel + ;; arm64) __BuildArch=arm64 __UbuntuArch=arm64 @@ -160,31 +173,8 @@ while :; do __QEMUArch=aarch64 __FreeBSDArch=arm64 __FreeBSDMachineArch=aarch64 - ;; - armel) - __BuildArch=armel - __UbuntuArch=armel - __UbuntuRepo="http://archive.debian.org/debian/" - __CodeName=buster - __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" - __LLDB_Package="liblldb-6.0-dev" - __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" - __UbuntuPackages="${__UbuntuPackages// libomp5/}" - __UbuntuSuites= - ;; - armv6) - __BuildArch=armv6 - __UbuntuArch=armhf - __QEMUArch=arm - __UbuntuRepo="http://raspbian.raspberrypi.org/raspbian/" - __CodeName=buster - __KeyringFile="/usr/share/keyrings/raspbian-archive-keyring.gpg" - __LLDB_Package="liblldb-6.0-dev" - __UbuntuSuites= - - if [[ -e "$__KeyringFile" ]]; then - __Keyring="--keyring $__KeyringFile" - fi + __OpenBSDArch=arm64 + __OpenBSDMachineArch=aarch64 ;; loongarch64) __BuildArch=loongarch64 @@ -193,10 +183,6 @@ while :; do __UbuntuArch=loong64 __UbuntuSuites=unreleased __LLDB_Package="liblldb-19-dev" - - if [[ "$__CodeName" == "sid" ]]; then - __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" - fi ;; riscv64) __BuildArch=riscv64 @@ -212,7 +198,7 @@ while :; do __AlpineArch=ppc64le __QEMUArch=ppc64le __UbuntuArch=ppc64el - __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuRepo="https://ports.ubuntu.com/ubuntu-ports/" __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" @@ -223,7 +209,7 @@ while :; do __AlpineArch=s390x __QEMUArch=s390x __UbuntuArch=s390x - __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuRepo="https://ports.ubuntu.com/ubuntu-ports/" __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" @@ -235,15 +221,17 @@ while :; do __UbuntuArch=amd64 __FreeBSDArch=amd64 __FreeBSDMachineArch=amd64 + __OpenBSDArch=amd64 + __OpenBSDMachineArch=amd64 __illumosArch=x86_64 __HaikuArch=x86_64 - __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + __UbuntuRepo="https://archive.ubuntu.com/ubuntu/" ;; x86) __BuildArch=x86 __UbuntuArch=i386 __AlpineArch=x86 - __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + __UbuntuRepo="https://archive.ubuntu.com/ubuntu/" ;; lldb*) version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" @@ -295,9 +283,7 @@ while :; do ;; noble) # Ubuntu 24.04 __CodeName=noble - if [[ -z "$__LLDB_Package" ]]; then - __LLDB_Package="liblldb-19-dev" - fi + __LLDB_Package="liblldb-19-dev" ;; stretch) # Debian 9 __CodeName=stretch @@ -305,7 +291,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="https://archive.debian.org/debian/" fi ;; buster) # Debian 10 @@ -314,7 +300,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://archive.debian.org/debian/" + __UbuntuRepo="https://archive.debian.org/debian/" fi ;; bullseye) # Debian 11 @@ -322,7 +308,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="https://ftp.debian.org/debian/" fi ;; bookworm) # Debian 12 @@ -330,7 +316,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="https://ftp.debian.org/debian/" fi ;; sid) # Debian sid @@ -339,25 +325,21 @@ while :; do # Debian-Ports architectures need different values case "$__UbuntuArch" in - amd64|arm64|armel|armhf|i386|mips64el|ppc64el|riscv64|s390x) + amd64|arm64|armhf|i386|mips64el|ppc64el|riscv64|s390x) __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="https://ftp.debian.org/debian/" fi ;; *) __KeyringFile="/usr/share/keyrings/debian-ports-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" + __UbuntuRepo="https://ftp.debian.org/debian-ports/" fi ;; esac - - if [[ -e "$__KeyringFile" ]]; then - __Keyring="--keyring $__KeyringFile" - fi ;; tizen) __CodeName= @@ -383,10 +365,14 @@ while :; do ;; freebsd14) __CodeName=freebsd - __FreeBSDBase="14.2-RELEASE" + __FreeBSDBase="14.3-RELEASE" __FreeBSDABI="14" __SkipUnmount=1 ;; + openbsd) + __CodeName=openbsd + __SkipUnmount=1 + ;; illumos) __CodeName=illumos __SkipUnmount=1 @@ -457,7 +443,7 @@ fi __UbuntuPackages+=" ${__LLDB_Package:-}" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ports.ubuntu.com/" + __UbuntuRepo="https://ports.ubuntu.com/" fi if [[ -n "$__LLVM_MajorVersion" ]]; then @@ -544,15 +530,15 @@ if [[ "$__CodeName" == "alpine" ]]; then # initialize DB # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then # shellcheck disable=SC2086 __AlpinePackages+=" $("$__ApkToolsDir/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ search 'llvm*-libs' | grep -E '^llvm' | sort | tail -1 | sed 's/-[^-]*//2g')" fi @@ -560,8 +546,8 @@ if [[ "$__CodeName" == "alpine" ]]; then # install all packages in one go # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" $__NoEmulationArg \ add $__AlpinePackages @@ -578,7 +564,7 @@ elif [[ "$__CodeName" == "freebsd" ]]; then curl -SL "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version fi echo "ABI = \"FreeBSD:${__FreeBSDABI}:${__FreeBSDMachineArch}\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > "${__RootfsDir}"/usr/local/etc/pkg.conf - echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf + echo "FreeBSD: { url: \"pkg+https://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf mkdir -p "$__RootfsDir"/tmp # get and build package manager if [[ "$__hasWget" == 1 ]]; then @@ -592,9 +578,69 @@ elif [[ "$__CodeName" == "freebsd" ]]; then ./autogen.sh && ./configure --prefix="$__RootfsDir"/host && make -j "$JOBS" && make install rm -rf "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" # install packages we need. - INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update + INSTALL_AS_USER=$(whoami) IGNORE_OSVERSION=yes "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update # shellcheck disable=SC2086 INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages +elif [[ "$__CodeName" == "openbsd" ]]; then + # determine mirrors + OPENBSD_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/$__OpenBSDVersion/$__OpenBSDMachineArch" + + # download base system sets + ensureDownloadTool + + BASE_SETS=(base comp) + for set in "${BASE_SETS[@]}"; do + FILE="${set}${__OpenBSDVersion//./}.tgz" + echo "Downloading $FILE..." + if [[ "$__hasWget" == 1 ]]; then + wget -O- "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - + else + curl -SL "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - + fi + done + + PKG_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/${__OpenBSDVersion}/packages/${__OpenBSDMachineArch}" + + echo "Installing packages into sysroot..." + + # Fetch package index once + if [[ "$__hasWget" == 1 ]]; then + PKG_INDEX=$(wget -qO- "$PKG_MIRROR/") + else + PKG_INDEX=$(curl -s "$PKG_MIRROR/") + fi + + for pkg in $__OpenBSDPackages; do + PKG_FILE=$(echo "$PKG_INDEX" | grep -Po ">\K${pkg}-[0-9][^\" ]*\.tgz" \ + | sort -V | tail -n1) + + echo "Resolved package filename for $pkg: $PKG_FILE" + + [[ -z "$PKG_FILE" ]] && { echo "ERROR: Package $pkg not found"; exit 1; } + + if [[ "$__hasWget" == 1 ]]; then + wget -O- "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir/usr/local" -xzpf - + else + curl -SL "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir/usr/local" -xzpf - + fi + done + + echo "Creating versionless symlinks for shared libraries..." + # Find all versioned .so files and create the base .so symlink + for lib in "$__RootfsDir"/usr/lib/lib*.so.*; do + if [ -f "$lib" ]; then + # Extract the filename (e.g., libc++.so.12.0) + VERSIONED_NAME=$(basename "$lib") + # Remove the trailing version numbers (e.g., libc++.so) + BASE_NAME=${VERSIONED_NAME%.so.*}.so + # Create the symlink in the same directory + ln -sf "$VERSIONED_NAME" "$__RootfsDir/usr/lib/$BASE_NAME" + fi + done + + echo "Cleaning up unnecessary paths" + # we don't use executables and kernel in rootfs (as we use host's compiler with -sysroot) + rm -rf "$__RootfsDir/usr/share" "$__RootfsDir/usr/bin" elif [[ "$__CodeName" == "illumos" ]]; then mkdir "$__RootfsDir/tmp" pushd "$__RootfsDir/tmp" @@ -759,6 +805,14 @@ elif [[ "$__CodeName" == "haiku" ]]; then elif [[ -n "$__CodeName" ]]; then __Suites="$__CodeName $(for suite in $__UbuntuSuites; do echo -n "$__CodeName-$suite "; done)" + __SigCheckArgs= + if [[ "$__SkipSigCheck" == "0" ]]; then + if [[ -e "$__KeyringFile" ]]; then + __SigCheckArgs="--keyring $__KeyringFile" + fi + __SigCheckArgs="$__SigCheckArgs --force-check-gpg" + fi + if [[ "$__SkipEmulation" == "1" ]]; then if [[ -z "$AR" ]]; then if command -v ar &>/dev/null; then @@ -774,31 +828,23 @@ elif [[ -n "$__CodeName" ]]; then PYTHON=${PYTHON_EXECUTABLE:-python3} # shellcheck disable=SC2086,SC2046 - echo running "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ + echo running "$PYTHON" "$__CrossDir/install-debs.py" $__SigCheckArgs --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ $(for suite in $__Suites; do echo -n "--suite $suite "; done) \ $__UbuntuPackages # shellcheck disable=SC2086,SC2046 - "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ + "$PYTHON" "$__CrossDir/install-debs.py" $__SigCheckArgs --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ $(for suite in $__Suites; do echo -n "--suite $suite "; done) \ $__UbuntuPackages exit 0 fi - __UpdateOptions= - if [[ "$__SkipSigCheck" == "0" ]]; then - __Keyring="$__Keyring --force-check-gpg" - else - __Keyring= - __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories" - fi - # shellcheck disable=SC2086 - echo running debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" + echo running debootstrap "--variant=minbase" $__SigCheckArgs --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" # shellcheck disable=SC2086 - if ! debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then + if ! debootstrap "--variant=minbase" $__SigCheckArgs --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then echo "debootstrap failed! dumping debootstrap.log" cat "$__RootfsDir/debootstrap/debootstrap.log" exit 1 @@ -816,6 +862,11 @@ Components: main universe Signed-By: $__KeyringFile EOF + __UpdateOptions= + if [[ "$__SkipSigCheck" == "1" ]]; then + __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories" + fi + # shellcheck disable=SC2086 chroot "$__RootfsDir" apt-get update $__UpdateOptions chroot "$__RootfsDir" apt-get -f -y install diff --git a/eng/common/cross/install-debs.py b/eng/common/cross/install-debs.py index c81eb37e522e..1d1dfabf7dc9 100644 --- a/eng/common/cross/install-debs.py +++ b/eng/common/cross/install-debs.py @@ -4,6 +4,7 @@ import asyncio import aiohttp import gzip +import hashlib import os import re import shutil @@ -16,7 +17,7 @@ from collections import deque from functools import cmp_to_key -async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, timeout=60): +async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, timeout=60, checksum=None): """Asynchronous file download with retries.""" attempt = 0 while attempt < max_retries: @@ -25,19 +26,25 @@ async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, t if response.status == 200: with open(dest_path, "wb") as f: content = await response.read() + + # verify checksum if provided + if checksum: + sha256 = hashlib.sha256(content).hexdigest() + if sha256 != checksum: + raise Exception(f"SHA256 mismatch for {url}: expected {checksum}, got {sha256}") + f.write(content) print(f"Downloaded {url} at {dest_path}") return else: - print(f"Failed to download {url}, Status Code: {response.status}") - break + raise Exception(f"Failed to download {url}, Status Code: {response.status}") except (asyncio.CancelledError, asyncio.TimeoutError, aiohttp.ClientError) as e: print(f"Error downloading {url}: {type(e).__name__} - {e}. Retrying...") attempt += 1 await asyncio.sleep(retry_delay) - print(f"Failed to download {url} after {max_retries} attempts.") + raise Exception(f"Failed to download {url} after {max_retries} attempts.") async def download_deb_files_parallel(mirror, packages, tmp_dir): """Download .deb files in parallel.""" @@ -51,11 +58,11 @@ async def download_deb_files_parallel(mirror, packages, tmp_dir): if filename: url = f"{mirror}/{filename}" dest_path = os.path.join(tmp_dir, os.path.basename(filename)) - tasks.append(asyncio.create_task(download_file(session, url, dest_path))) + tasks.append(asyncio.create_task(download_file(session, url, dest_path, checksum=info.get("SHA256")))) await asyncio.gather(*tasks) -async def download_package_index_parallel(mirror, arch, suites): +async def download_package_index_parallel(mirror, arch, suites, check_sig, keyring): """Download package index files for specified suites and components entirely in memory.""" tasks = [] timeout = aiohttp.ClientTimeout(total=60) @@ -63,10 +70,9 @@ async def download_package_index_parallel(mirror, arch, suites): async with aiohttp.ClientSession(timeout=timeout) as session: for suite in suites: for component in ["main", "universe"]: - url = f"{mirror}/dists/{suite}/{component}/binary-{arch}/Packages.gz" - tasks.append(fetch_and_decompress(session, url)) + tasks.append(fetch_and_decompress(session, mirror, arch, suite, component, check_sig, keyring)) - results = await asyncio.gather(*tasks, return_exceptions=True) + results = await asyncio.gather(*tasks) merged_content = "" for result in results: @@ -77,20 +83,75 @@ async def download_package_index_parallel(mirror, arch, suites): return merged_content -async def fetch_and_decompress(session, url): +async def fetch_and_decompress(session, mirror, arch, suite, component, check_sig, keyring): """Fetch and decompress the Packages.gz file.""" - try: - async with session.get(url) as response: - if response.status == 200: - compressed_data = await response.read() - decompressed_data = gzip.decompress(compressed_data).decode('utf-8') - print(f"Downloaded index: {url}") - return decompressed_data - else: - print(f"Skipped index: {url} (doesn't exist)") - return None - except Exception as e: - print(f"Error fetching {url}: {e}") + + path = f"{component}/binary-{arch}/Packages.gz" + url = f"{mirror}/dists/{suite}/{path}" + + async with session.get(url) as response: + if response.status == 200: + compressed_data = await response.read() + decompressed_data = gzip.decompress(compressed_data).decode('utf-8') + print(f"Downloaded index: {url}") + + if check_sig: + # Verify the package index against the sha256 recorded in the Release file + release_file_content = await fetch_release_file(session, mirror, suite, keyring) + packages_sha = parse_release_file(release_file_content, path) + + sha256 = hashlib.sha256(compressed_data).hexdigest() + if sha256 != packages_sha: + raise Exception(f"SHA256 mismatch for {path}: expected {packages_sha}, got {sha256}") + print(f"Checksum verified for {path}") + + return decompressed_data + else: + print(f"Skipped index: {url} (doesn't exist)") + return None + +async def fetch_release_file(session, mirror, suite, keyring): + """Fetch Release and Release.gpg files and verify the signature.""" + + release_url = f"{mirror}/dists/{suite}/Release" + release_gpg_url = f"{mirror}/dists/{suite}/Release.gpg" + + with tempfile.NamedTemporaryFile() as release_file, tempfile.NamedTemporaryFile() as release_gpg_file: + await download_file(session, release_url, release_file.name) + await download_file(session, release_gpg_url, release_gpg_file.name) + + print("Verifying signature of Release with Release.gpg.") + # Use gpgv rather than gpg for verification. gpgv verifies a detached + # signature against a fixed keyring without involving gpg-agent or + # keyboxd, which makes it robust on hosts running GnuPG 2.4+ (e.g. Azure + # Linux) where "gpg --keyring" routes through keyboxd and can fail. + verify_command = ["gpgv"] + if keyring: + verify_command += ["--keyring", keyring] + verify_command += [release_gpg_file.name, release_file.name] + result = subprocess.run(verify_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if result.returncode != 0: + raise Exception(f"Signature verification failed: {result.stderr.decode('utf-8')}") + + print("Signature verified successfully.") + + with open(release_file.name) as f: + return f.read() + +def parse_release_file(content, path): + """Parses the Release file and returns sha256 checksum of the specified path.""" + + # data looks like this: + # + matches = re.findall(r'^ (\S*) +(\S*) +(\S*)$', content, re.MULTILINE) + + for entry in matches: + # the file has both md5 and sha256 checksums, we want sha256 which has a length of 64 + if entry[2] == path and len(entry[0]) == 64: + return entry[0] + + raise Exception(f"Could not find checksum for {path} in Release file.") def parse_debian_version(version): """Parse a Debian package version into epoch, upstream version, and revision.""" @@ -171,13 +232,15 @@ def parse_package_index(content): filename = fields.get("Filename") depends = fields.get("Depends") provides = fields.get("Provides", None) + sha256 = fields.get("SHA256") # Only update if package_name is not in packages or if the new version is higher if package_name not in packages or compare_debian_versions(version, packages[package_name]["Version"]) > 0: packages[package_name] = { "Version": version, "Filename": filename, - "Depends": depends + "Depends": depends, + "SHA256": sha256 } # Update aliases if package provides any alternatives @@ -233,7 +296,7 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): os.makedirs(extract_dir, exist_ok=True) with tempfile.TemporaryDirectory(dir=tmp_dir) as tmp_subdir: - result = subprocess.run(f"{ar_tool} t {os.path.abspath(deb_file)}", cwd=tmp_subdir, check=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + result = subprocess.run([ar_tool, "t", os.path.abspath(deb_file)], cwd=tmp_subdir, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) tar_filename = None for line in result.stdout.decode().splitlines(): @@ -247,7 +310,8 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): tar_file_path = os.path.join(tmp_subdir, tar_filename) print(f"Extracting {tar_filename} from {deb_file}..") - subprocess.run(f"{ar_tool} p {os.path.abspath(deb_file)} {tar_filename} > {tar_file_path}", check=True, shell=True) + with open(tar_file_path, "wb") as outfile: + subprocess.run([ar_tool, "p", os.path.abspath(deb_file), tar_filename], check=True, stdout=outfile, stderr=subprocess.PIPE) file_extension = os.path.splitext(tar_file_path)[1].lower() @@ -268,7 +332,18 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): raise ValueError(f"Unsupported compression format: {file_extension}") with tarfile.open(tar_file_path, mode) as tar: - tar.extractall(path=extract_dir, filter='fully_trusted') + tar.extractall(path=extract_dir, filter=_rootfs_extraction_filter) + +def _rootfs_extraction_filter(member, dest_path): + """Tarfile extraction filter based on the 'data' filter that additionally + rewrites absolute-target symlinks/hardlinks into rootfs-relative paths. + """ + if (member.issym() or member.islnk()) and os.path.isabs(member.linkname): + link_dir = os.path.dirname(member.name) + new_linkname = os.path.relpath(member.linkname.lstrip('/'), + start=link_dir or '.') + member = member.replace(linkname=new_linkname, deep=False) + return tarfile.data_filter(member, dest_path) def finalize_setup(rootfsdir): lib_dir = os.path.join(rootfsdir, 'lib') @@ -295,24 +370,17 @@ def finalize_setup(rootfsdir): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate rootfs for .NET runtime on Debian-like OS") - parser.add_argument("--distro", required=False, help="Distro name (e.g., debian, ubuntu, etc.)") parser.add_argument("--arch", required=True, help="Architecture (e.g., amd64, loong64, etc.)") parser.add_argument("--rootfsdir", required=True, help="Destination directory.") parser.add_argument('--suite', required=True, action='append', help='Specify one or more repository suites to collect index data.') - parser.add_argument("--mirror", required=False, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)") + parser.add_argument("--mirror", required=True, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)") parser.add_argument("--artool", required=False, default="ar", help="ar tool to extract debs (e.g., ar, llvm-ar etc.)") + parser.add_argument("--force-check-gpg", required=False, action='store_true', help="Verify the packages against signatures in Release file.") + parser.add_argument("--keyring", required=False, default='', help="Keyring file to check signature of Release file.") parser.add_argument("packages", nargs="+", help="List of package names to be installed.") args = parser.parse_args() - if args.mirror is None: - if args.distro == "ubuntu": - args.mirror = "http://archive.ubuntu.com/ubuntu" if args.arch in ["amd64", "i386"] else "http://ports.ubuntu.com/ubuntu-ports" - elif args.distro == "debian": - args.mirror = "http://ftp.debian.org/debian-ports" - else: - raise Exception("Unsupported distro") - DESIRED_PACKAGES = args.packages + [ # base packages "dpkg", "busybox", @@ -322,9 +390,16 @@ def finalize_setup(rootfsdir): "debianutils" ] - print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, distro: {args.distro}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}") + print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}") + + check_sig = args.force_check_gpg + if check_sig and not args.keyring: + print("ERROR: --force-check-gpg requires --keyring to specify a keyring file for signature verification.") + print("Install the appropriate keyring package (e.g., debian-ports-archive-keyring, ubuntu-archive-keyring)") + print("or pass --skipsigcheck to build-rootfs.sh to disable signature checking.") + sys.exit(1) - package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite)) + package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite, check_sig, args.keyring)) packages_info, aliases = parse_package_index(package_index_content) diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 0ff85cf0367e..f65c689f6954 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -3,15 +3,22 @@ set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) # reset platform variables (e.g. cmake 3.25 sets LINUX=1) unset(LINUX) unset(FREEBSD) +unset(OPENBSD) unset(ILLUMOS) unset(ANDROID) unset(TIZEN) unset(HAIKU) set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) + +file(GLOB OPENBSD_PROBE "${CROSS_ROOTFS}/etc/signify/openbsd-*.pub") + if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) set(CMAKE_SYSTEM_NAME FreeBSD) set(FREEBSD 1) +elseif(OPENBSD_PROBE) + set(CMAKE_SYSTEM_NAME OpenBSD) + set(OPENBSD 1) elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) set(CMAKE_SYSTEM_NAME SunOS) set(ILLUMOS 1) @@ -52,7 +59,9 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64") set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu") endif() elseif(FREEBSD) - set(triple "aarch64-unknown-freebsd12") + set(TOOLCHAIN "aarch64-unknown-freebsd14") + elseif(OPENBSD) + set(TOOLCHAIN "aarch64-unknown-openbsd") endif() elseif(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) @@ -108,7 +117,9 @@ elseif(TARGET_ARCH_NAME STREQUAL "x64") set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu") endif() elseif(FREEBSD) - set(triple "x86_64-unknown-freebsd12") + set(TOOLCHAIN "x86_64-unknown-freebsd14") + elseif(OPENBSD) + set(TOOLCHAIN "x86_64-unknown-openbsd") elseif(ILLUMOS) set(TOOLCHAIN "x86_64-illumos") elseif(HAIKU) @@ -148,9 +159,6 @@ if(TIZEN) else() find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") endif() - - message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}") - include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++) include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN}) endif() @@ -193,11 +201,11 @@ if(ANDROID) # include official NDK toolchain script include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) -elseif(FREEBSD) +elseif(FREEBSD OR OPENBSD) # we cross-compile by instructing clang - set(CMAKE_C_COMPILER_TARGET ${triple}) - set(CMAKE_CXX_COMPILER_TARGET ${triple}) - set(CMAKE_ASM_COMPILER_TARGET ${triple}) + set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld") @@ -214,13 +222,19 @@ elseif(ILLUMOS) locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) elseif(HAIKU) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") - set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") - locate_toolchain_exec(gcc CMAKE_C_COMPILER) - locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + if ("$ENV{CCC_CC}" MATCHES ".*gcc.*") + set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") + locate_toolchain_exec(gcc CMAKE_C_COMPILER) + locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + else() + set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") + set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") + set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") + endif() # let CMake set up the correct search paths include(Platform/Haiku) @@ -291,7 +305,7 @@ endif() # Specify compile options -if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD) OR ILLUMOS OR HAIKU) +if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD AND NOT OPENBSD) OR ILLUMOS OR HAIKU) set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 index e33743105635..a5be41db6906 100644 --- a/eng/common/darc-init.ps1 +++ b/eng/common/darc-init.ps1 @@ -29,11 +29,11 @@ function InstallDarcCli ($darcVersion, $toolpath) { Write-Host "Installing Darc CLI version $darcVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' if (-not $toolpath) { - Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g }else { - Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" } } diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index e889f439b8dc..e6ba4ee28c11 100644 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -73,9 +73,9 @@ function InstallDarcCli { echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." if [ -z "$toolpath" ]; then - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g) else - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") fi } diff --git a/eng/common/native/LocateNativeCompiler.targets b/eng/common/native/LocateNativeCompiler.targets new file mode 100644 index 000000000000..028b33d94441 --- /dev/null +++ b/eng/common/native/LocateNativeCompiler.targets @@ -0,0 +1,27 @@ + + + + + clang + $(ROOTFS_DIR) + + + + + + + + $(_CC_LDFLAGS.SubString(0, $(_CC_LDFLAGS.IndexOf(';')))) + <_LDFLAGS>$(_CC_LDFLAGS.SubString($([MSBuild]::Add($(_CC_LDFLAGS.IndexOf(';')), 1)))) + lld + + + diff --git a/eng/common/native/NativeAotSupported.props b/eng/common/native/NativeAotSupported.props new file mode 100644 index 000000000000..559a6663929e --- /dev/null +++ b/eng/common/native/NativeAotSupported.props @@ -0,0 +1,26 @@ + + + + + <_NativeAotSupportedOS Condition=" + '$(TargetOS)' != 'browser' and + '$(TargetOS)' != 'haiku' and + '$(TargetOS)' != 'illumos' and + '$(TargetOS)' != 'netbsd' and + '$(TargetOS)' != 'solaris' + ">true + + + <_NativeAotSupportedArch Condition=" + '$(TargetArchitecture)' != 'wasm' and + ('$(TargetArchitecture)' != 'x86' or '$(TargetOS)' == 'windows') + ">true + + true + + + diff --git a/eng/common/native/init-distro-rid.sh b/eng/common/native/init-distro-rid.sh index 83ea7aab0e08..8fc6d2fec78d 100644 --- a/eng/common/native/init-distro-rid.sh +++ b/eng/common/native/init-distro-rid.sh @@ -39,6 +39,8 @@ getNonPortableDistroRid() # $rootfsDir can be empty. freebsd-version is a shell script and should always work. __freebsd_major_version=$("$rootfsDir"/bin/freebsd-version | cut -d'.' -f1) nonPortableRid="freebsd.$__freebsd_major_version-${targetArch}" + elif [ "$targetOs" = "openbsd" ]; then + nonPortableRid="openbsd.$(uname -r)-${targetArch}" elif command -v getprop >/dev/null && getprop ro.product.system.model | grep -qi android; then __android_sdk_version=$(getprop ro.build.version.sdk) nonPortableRid="android.$__android_sdk_version-${targetArch}" diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index 477a44f335be..aff839fa0974 100644 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -24,14 +24,16 @@ case "$os" in apt update apt install -y build-essential gettext locales cmake llvm clang lld lldb liblldb-dev libunwind8-dev libicu-dev liblttng-ust-dev \ - libssl-dev libkrb5-dev pigz cpio + libssl-dev libkrb5-dev pigz cpio ninja-build file localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ]; then + elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ] || [ "$ID" = "centos" ]; then pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)" - $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio + $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build file + elif [ "$ID" = "amzn" ]; then + dnf install -y cmake llvm lld lldb clang python libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build file elif [ "$ID" = "alpine" ]; then - apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio + apk add build-base cmake bash curl clang llvm llvm-dev lld lldb-dev krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio ninja file else echo "Unsupported distro. distro: $ID" exit 1 @@ -52,6 +54,7 @@ brew "openssl@3" brew "pkgconf" brew "python3" brew "pigz" +brew "ninja" EOF ;; diff --git a/eng/common/post-build/redact-logs.ps1 b/eng/common/post-build/redact-logs.ps1 index 472d5bb562c9..c1e4104b79a4 100644 --- a/eng/common/post-build/redact-logs.ps1 +++ b/eng/common/post-build/redact-logs.ps1 @@ -48,8 +48,8 @@ try { Write-Host "Installing Binlog redactor CLI..." Write-Host "'$dotnet' new tool-manifest" & "$dotnet" new tool-manifest - Write-Host "'$dotnet' tool install $packageName --local --add-source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" - & "$dotnet" tool install $packageName --local --add-source "$PackageFeed" -v $verbosity --version $BinlogToolVersion + Write-Host "'$dotnet' tool install $packageName --local --source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" + & "$dotnet" tool install $packageName --local --source "$PackageFeed" -v $verbosity --version $BinlogToolVersion if (Test-Path $TokensFilePath) { Write-Host "Adding additional sensitive data for redaction from file: " $TokensFilePath diff --git a/eng/common/templates/steps/vmr-sync.yml b/eng/common/templates/steps/vmr-sync.yml index eb619c502683..cdc6a28ff1f6 100644 --- a/eng/common/templates/steps/vmr-sync.yml +++ b/eng/common/templates/steps/vmr-sync.yml @@ -45,11 +45,11 @@ steps: workingDirectory: ${{ parameters.vmrPath }} - script: | - ./eng/common/vmr-sync.sh \ - --vmr ${{ parameters.vmrPath }} \ - --tmp $(Agent.TempDirectory) \ - --azdev-pat '$(dn-bot-all-orgs-code-r)' \ - --ci \ + ./eng/common/vmr-sync.sh \ + --vmr ${{ parameters.vmrPath }} \ + --tmp $(Agent.TempDirectory) \ + --azdev-pat '$(AzdoToken)' \ + --ci \ --debug if [ "$?" -ne 0 ]; then @@ -67,11 +67,11 @@ steps: condition: eq(variables['Agent.OS'], 'Windows_NT') - powershell: | - ./eng/common/vmr-sync.ps1 ` - -vmr ${{ parameters.vmrPath }} ` - -tmp $(Agent.TempDirectory) ` - -azdevPat '$(dn-bot-all-orgs-code-r)' ` - -ci ` + ./eng/common/vmr-sync.ps1 ` + -vmr ${{ parameters.vmrPath }} ` + -tmp $(Agent.TempDirectory) ` + -azdevPat '$(AzdoToken)' ` + -ci ` -debugOutput if ($LASTEXITCODE -ne 0) { diff --git a/eng/common/templates/vmr-build-pr.yml b/eng/common/templates/vmr-build-pr.yml index 2f3694fa1323..d24de935248a 100644 --- a/eng/common/templates/vmr-build-pr.yml +++ b/eng/common/templates/vmr-build-pr.yml @@ -33,7 +33,7 @@ resources: - repository: vmr type: github name: dotnet/dotnet - endpoint: dotnet + endpoint: public ref: refs/heads/main # Set to whatever VMR branch the PR build should insert into stages: diff --git a/global.json b/global.json index 0b4ff331337e..32005f79bfa6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.400-preview.0.26356.110", + "version": "10.0.400-preview.0.26357.110", "paths": [ "builds/downloads/dotnet", "$host$" @@ -8,9 +8,9 @@ "errorMessage": "The .NET SDK could not be found, please run 'make dotnet -C builds'." }, "tools": { - "dotnet": "10.0.400-preview.0.26356.110" + "dotnet": "10.0.400-preview.0.26357.110" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26356.110" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26357.110" } } From d4f5f122fab7d136eb73a3fa4f7c14ef9444f263 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 8 Jul 2026 13:38:48 +0200 Subject: [PATCH 09/13] [tests] Capture 'log stream' output during macOS test execution (#25956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Start 'log stream --style compact' at the beginning of test execution and stop it at the end. The output is written to the crash reports directory as system.log.zip (compressed because it can be large), so it gets uploaded together with crash reports and other test artifacts. 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../run-packaged-macos-tests.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/scripts/run-packaged-macos-tests/run-packaged-macos-tests.cs b/scripts/run-packaged-macos-tests/run-packaged-macos-tests.cs index e3994c30b6b3..3716b027541f 100644 --- a/scripts/run-packaged-macos-tests/run-packaged-macos-tests.cs +++ b/scripts/run-packaged-macos-tests/run-packaged-macos-tests.cs @@ -26,6 +26,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -118,6 +119,40 @@ if (!string.IsNullOrEmpty (testOutputDir)) Directory.CreateDirectory (testOutputDir); +// Start 'log stream' to capture system logs for the entire test run +Process? logStreamProcess = null; +string? logStreamFile = null; +StreamWriter? logStreamWriter = null; +if (!string.IsNullOrEmpty (crashReportsDir)) { + Directory.CreateDirectory (crashReportsDir); + logStreamFile = Path.Combine (crashReportsDir, "system.log"); + logStreamWriter = new StreamWriter (logStreamFile, append: false, Encoding.UTF8); + logStreamProcess = new Process (); + logStreamProcess.StartInfo.FileName = "/usr/bin/log"; + logStreamProcess.StartInfo.ArgumentList.Add ("stream"); + logStreamProcess.StartInfo.ArgumentList.Add ("--style"); + logStreamProcess.StartInfo.ArgumentList.Add ("compact"); + logStreamProcess.StartInfo.UseShellExecute = false; + logStreamProcess.StartInfo.RedirectStandardOutput = true; + logStreamProcess.StartInfo.RedirectStandardError = true; + var writer = logStreamWriter; + logStreamProcess.OutputDataReceived += (_, e) => { + if (e.Data is not null) + lock (writer) + writer.WriteLine (e.Data); + }; + logStreamProcess.ErrorDataReceived += (_, e) => { + if (e.Data is not null) + lock (writer) + writer.WriteLine (e.Data); + }; + logStreamProcess.Start (); + logStreamProcess.BeginOutputReadLine (); + logStreamProcess.BeginErrorReadLine (); + + Console.WriteLine ($"Started 'log stream' (pid {logStreamProcess.Id}), writing to {logStreamFile}"); +} + var isAppleSilicon = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 || Environment.GetEnvironmentVariable ("PROCESSOR_ARCHITECTURE")?.Contains ("ARM", StringComparison.OrdinalIgnoreCase) == true; @@ -248,6 +283,34 @@ Console.WriteLine ($"HTML report written to {htmlReportPath}"); } +// Stop 'log stream' and zip the output +if (logStreamProcess is not null && logStreamFile is not null) { + try { + NativeMethods.kill (logStreamProcess.Id, 2 /* SIGINT */); + logStreamProcess.WaitForExit (10_000); + } catch { + // Process may have already exited + } + + // Flush and close the log writer + logStreamWriter?.Dispose (); + + try { + Console.WriteLine ($"Wrote {new FileInfo (logStreamFile).Length} bytes to {logStreamFile}"); + + // Zip the log file + var zipPath = logStreamFile + ".zip"; + using (var zip = ZipFile.Open (zipPath, ZipArchiveMode.Create)) + zip.CreateEntryFromFile (logStreamFile, Path.GetFileName (logStreamFile), CompressionLevel.Optimal); + File.Delete (logStreamFile); + Console.WriteLine ($"Compressed log stream to {zipPath}"); + } catch (Exception ex) { + Console.Error.WriteLine ($"Warning: Failed to save log stream output: {ex.Message}"); + } + + logStreamProcess.Dispose (); +} + return failedSuites > 0 ? 1 : 0; // ===== Helper methods ===== From f597a6fe1aa0f1172ab28625c6f6d592ef2db112 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 8 Jul 2026 13:55:25 +0200 Subject: [PATCH 10/13] [tests] Delete saved application state before executing test apps. Fixes #25922 (#25958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before and after launching a macOS/Mac Catalyst app in tests, delete its saved application state directory (`~/Library/Saved Application State/.savedState`). This prevents the "Do you want to try to reopen its windows again?" dialog from blocking automated test runs after a previous crash. Two implementations: - **C# (`TestBaseClass.cs`)**: The `Execute` method calls `DeleteSavedState` before and after running the app. It walks up the executable path to find the `.app` bundle, reads the bundle identifier from `Info.plist`, and deletes the saved state directory. - **Makefile (`shared-dotnet.mk`)**: A `delete-saved-state` target reads the bundle identifier via `defaults read` and removes the saved state directory. The `run-bare` target invokes it before and after execution. Fixes https://github.com/dotnet/macios/issues/25922 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/common/shared-dotnet.mk | 13 ++++++- tests/dotnet/UnitTests/TestBaseClass.cs | 48 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/common/shared-dotnet.mk b/tests/common/shared-dotnet.mk index 21f5f72ba6fe..c8448bdb0225 100644 --- a/tests/common/shared-dotnet.mk +++ b/tests/common/shared-dotnet.mk @@ -150,8 +150,12 @@ else ifeq ($(PLATFORM),tvOS) EXECUTABLE="$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-tvos/$(PATH_RID)$(TESTNAME).app/$(TESTNAME)" else ifeq ($(PLATFORM),MacCatalyst) EXECUTABLE="$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-maccatalyst/$(PATH_RID)$(TESTNAME).app/Contents/MacOS/$(TESTNAME)" +APPMANIFEST:=$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-maccatalyst/$(PATH_RID)$(TESTNAME).app/Contents/Info.plist +BUNDLE_ID=$(shell defaults read "$(APPMANIFEST)" CFBundleIdentifier 2>/dev/null) else ifeq ($(PLATFORM),macOS) EXECUTABLE="$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-macos/$(PATH_RID)$(TESTNAME).app/Contents/MacOS/$(TESTNAME)" +APPMANIFEST:=$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-macos/$(PATH_RID)$(TESTNAME).app/Contents/Info.plist +BUNDLE_ID=$(shell defaults read "$(APPMANIFEST)" CFBundleIdentifier 2>/dev/null) else EXECUTABLE="unknown-executable-platform-$(PLATFORM)" endif @@ -187,8 +191,15 @@ run: prepare @echo "Running $(wildcard *.?sproj)..." $(Q) $(DOTNET) build "/bl:$(abspath $@-$(BINLOG_TIMESTAMP).binlog)" *.?sproj $(DOTNET_BUILD_VERBOSITY) $(BUILD_ARGUMENTS) $(CONFIG_ARGUMENT) $(UNIVERSAL_ARGUMENT) $(NATIVEAOT_ARGUMENTS) $(TEST_VARIATION_ARGUMENT) -t:Run $(RUN_ARGUMENTS) -tl:off -run-bare: +# Delete the saved application state for the app, to prevent the +# "Do you want to try to reopen its windows again?" dialog from +# blocking automated runs after a crash. See https://github.com/dotnet/macios/issues/25922 +delete-saved-state: + $(Q) if test -n "$(BUNDLE_ID)"; then rm -rf "$(HOME)/Library/Saved Application State/$(BUNDLE_ID).savedState" && echo "Deleted saved state for $(BUNDLE_ID)"; fi + +run-bare: delete-saved-state $(Q) $(EXECUTABLE) --autostart --autoexit $(RUN_ARGUMENTS) + $(Q) $(MAKE) delete-saved-state # Get the list of applicable simulators, and pick the first in the list. # Make sure to have a matching simulator runtime installed, otherwise this won't work. diff --git a/tests/dotnet/UnitTests/TestBaseClass.cs b/tests/dotnet/UnitTests/TestBaseClass.cs index fbe234b535a6..f04e046c1e2c 100644 --- a/tests/dotnet/UnitTests/TestBaseClass.cs +++ b/tests/dotnet/UnitTests/TestBaseClass.cs @@ -480,6 +480,8 @@ protected Execution Execute (string executable, out string output, out string ma if (!File.Exists (executable)) throw new FileNotFoundException ($"The executable '{executable}' does not exists."); + DeleteSavedState (executable); + magicWord = Guid.NewGuid ().ToString (); var env = new Dictionary { { "MAGIC_WORD", magicWord }, @@ -492,9 +494,55 @@ protected Execution Execute (string executable, out string output, out string ma var rv = Execution.RunAsync (executable, Array.Empty (), environment: env, timeout: TimeSpan.FromSeconds (30)).Result; output = rv.Output.MergedOutput; + + DeleteSavedState (executable); + return rv; } + // Delete the saved application state for the app being launched, to prevent + // the "Do you want to try to reopen its windows again?" dialog from showing + // if the app crashed during a previous test run. See https://github.com/dotnet/macios/issues/25922 + static void DeleteSavedState (string executable) + { + // Find the .app bundle directory from the executable path + var dir = Path.GetDirectoryName (executable); + while (!string.IsNullOrEmpty (dir) && !dir.EndsWith (".app", StringComparison.OrdinalIgnoreCase)) + dir = Path.GetDirectoryName (dir); + + if (string.IsNullOrEmpty (dir)) + return; + + // Read the bundle identifier from Info.plist + string? bundleIdentifier = null; + var infoPlistPath = Path.Combine (dir, "Contents", "Info.plist"); + if (!File.Exists (infoPlistPath)) + infoPlistPath = Path.Combine (dir, "Info.plist"); + if (!File.Exists (infoPlistPath)) + return; + + try { + var infoPlist = PDictionary.OpenFile (infoPlistPath); + bundleIdentifier = infoPlist.GetString ("CFBundleIdentifier")?.Value; + } catch (Exception e) { + Console.WriteLine ($"Could not read bundle identifier from '{infoPlistPath}': {e.Message}"); + return; + } + + if (string.IsNullOrEmpty (bundleIdentifier)) + return; + + var savedStateDir = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", $"{bundleIdentifier}.savedState"); + try { + if (Directory.Exists (savedStateDir)) { + Directory.Delete (savedStateDir, true); + Console.WriteLine ($"Deleted saved application state: {savedStateDir}"); + } + } catch (Exception e) { + Console.WriteLine ($"Could not delete saved application state '{savedStateDir}': {e.Message}"); + } + } + public static StringBuilder AssertExecute (string executable, params string [] arguments) { return AssertExecute (executable, arguments, out _); From 52094e08914cc0d92d697da3fe514b1d0176a8ac Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 8 Jul 2026 18:07:35 +0200 Subject: [PATCH 11/13] [docs] Improve XML docs for ModelAttribute (#25964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve XML documentation for the `ModelAttribute` type: - Replace 'To be added.' placeholders with meaningful descriptions - Remove empty `` nodes - Remove redundant `docs/api/Foundation/ModelAttribute.xml` (docs are now inline in source) - Document `Name` property 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/api/Foundation/ModelAttribute.xml | 20 -------------------- src/Foundation/ModelAttribute.cs | 20 ++++++++++++-------- 2 files changed, 12 insertions(+), 28 deletions(-) delete mode 100644 docs/api/Foundation/ModelAttribute.xml diff --git a/docs/api/Foundation/ModelAttribute.xml b/docs/api/Foundation/ModelAttribute.xml deleted file mode 100644 index 661a5b458cf8..000000000000 --- a/docs/api/Foundation/ModelAttribute.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - Flag a class as a model. - - Objective-C protocols are like interfaces, but they support optional methods, that is, not all of the methods need to be implemented for the protocol to work. - -There are two ways of implementing a model, you can either implement it manually or use the existing strongly typed definitions. - - -MonoTouch provides already strongly typed declarations ready to use that do not require manual binding. To support this programming model, the MonoTouch runtime supports the [Model] attribute on a class declaration. This informs the runtime that it should not wire up all the methods in the class, unless the method is explicitly implemented. - - -The Model attribute is applied to a class that can have optional methods, and it is typically used for declaring Objective-C delegates or data models that have a number of optional methods. The MonoTouch runtime treats classes with the Model attribute applied specially: only when a user overrides methods in a class, is the actual override exposed to the Objective-C world as existing. - - -This attribute is used in all of the models and delegate classes to allow the user to only implement the methods that he is interested in. - - - - \ No newline at end of file diff --git a/src/Foundation/ModelAttribute.cs b/src/Foundation/ModelAttribute.cs index f90e249bb2f0..78465d73b284 100644 --- a/src/Foundation/ModelAttribute.cs +++ b/src/Foundation/ModelAttribute.cs @@ -25,19 +25,23 @@ namespace Foundation { - /// + /// Flags a class or interface as a model, indicating that only explicitly overridden methods are exposed to the Objective-C runtime. + /// + /// Objective-C protocols are like interfaces, but they support optional methods, that is, not all of the methods need to be implemented for the protocol to work. + /// The [Model] attribute is applied to a class that can have optional methods, and it is typically used for declaring Objective-C delegates or data models that have a number of optional methods. The runtime treats classes with the Model attribute applied specially: only when a user overrides methods in a class is the actual override exposed to the Objective-C world as existing. + /// This attribute is used in all of the models and delegate classes to allow the user to only implement the methods that they are interested in. + /// [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface)] public sealed class ModelAttribute : Attribute { - /// Default constructor. - /// To be added. + /// Initializes a new instance of the class. public ModelAttribute () { } - /// Specifies if the Objective-C type name for the model. - /// The Objective-C type name for the model. - /// - /// This value is only taken into account when used in binding code / projects. - /// + /// Gets or sets the Objective-C type name for the model. + /// The Objective-C type name for the model. + /// + /// This value is only taken into account when used in binding code / projects. + /// public string? Name { get; set; } } } From 3e22b424c95f3fbef5e81533572e8ccfea1b8c63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 20:31:48 +0200 Subject: [PATCH 12/13] [github] Bump actions/setup-dotnet from 5.3.0 to 5.4.0 (#25972) Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.3.0 to 5.4.0. --- .github/workflows/autoformat-v2.yml | 4 ++-- .github/workflows/maestro-changelog.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/autoformat-v2.yml b/.github/workflows/autoformat-v2.yml index b7ead504ef9b..d906a323359b 100644 --- a/.github/workflows/autoformat-v2.yml +++ b/.github/workflows/autoformat-v2.yml @@ -21,7 +21,7 @@ jobs: persist-credentials: false - name: 'Install .NET' - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 with: global-json-file: ./global.json @@ -67,7 +67,7 @@ jobs: fetch-depth: 1 - name: 'Install .NET' - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 with: global-json-file: ./global.json diff --git a/.github/workflows/maestro-changelog.yml b/.github/workflows/maestro-changelog.yml index ab0ebf924389..c976b9e161fb 100644 --- a/.github/workflows/maestro-changelog.yml +++ b/.github/workflows/maestro-changelog.yml @@ -17,7 +17,7 @@ jobs: if: github.event.pull_request.user.login == 'dotnet-maestro[bot]' steps: - - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 + - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 with: dotnet-version: '9' From 42aca4c87b44cd6abd1c75a51327e236aea5967c Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 8 Jul 2026 20:38:34 +0200 Subject: [PATCH 13/13] [assembly-preparer] Only run RegistrarRemovalTrackingStep if the platform assembly is trimmed. (#25941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RegistrarRemovalTrackingStep computes whether the dynamic registrar can be removed and reports the result to MSBuild as the DynamicRegistrationSupported feature switch. The dynamic registrar lives in the platform assembly, which is an SDK assembly that's only trimmed when trimming is enabled; so when nothing is being trimmed the dynamic registrar can't be removed and there's nothing for the step to compute. Don't add the step to the assembly-preparer's step list in that case (in addition to the existing skip when the user explicitly set $(DynamicRegistrationSupported)). This avoids unnecessary work when building without trimming. 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DynamicRegistrationSupportedTest.cs | 24 +++++++++++++++++++ tools/assembly-preparer/AssemblyPreparer.cs | 9 ++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs b/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs index 59976ca3ccd6..3bf5c7cfe633 100644 --- a/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs +++ b/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs @@ -39,6 +39,30 @@ public void UserSpecifiedValue (ApplePlatform platform, string runtimeIdentifier Assert.That (featureSwitch?.GetMetadata ("Value"), Is.EqualTo (dynamicRegistrationSupported), "The feature switch value must match the user-specified value."); } + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64")] + public void SkippedWhenPlatformAssemblyNotTrimmed (ApplePlatform platform, string runtimeIdentifiers) + { + // When the platform assembly isn't being trimmed (link mode None), the dynamic registrar can't be + // removed, so RegistrarRemovalTrackingStep is skipped and no DynamicRegistrationSupported feature + // switch is emitted (the dynamic registrar is kept, which is the default). + var project = "MySimpleApp"; + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var project_path = GetProjectPath (project, platform: platform); + Clean (project_path); + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["MtouchLink"] = "None"; + properties ["PrepareAssemblies"] = "true"; + properties ["PostProcessAssemblies"] = "true"; + + var rv = DotNet.AssertBuild (project_path, properties); + + var featureSwitch = GetRuntimeHostConfigurationOption (rv.BinLogPath, "ObjCRuntime.Runtime.DynamicRegistrationSupported"); + Assert.That (featureSwitch, Is.Null, "No DynamicRegistrationSupported feature switch should be set when the platform assembly isn't trimmed."); + } + // Returns the last RuntimeHostConfigurationOption item with the given name (ItemSpec) added during the build. static ITaskItem? GetRuntimeHostConfigurationOption (string binLogPath, string name) { diff --git a/tools/assembly-preparer/AssemblyPreparer.cs b/tools/assembly-preparer/AssemblyPreparer.cs index 520bd720ccd9..d03b0cf31e4c 100644 --- a/tools/assembly-preparer/AssemblyPreparer.cs +++ b/tools/assembly-preparer/AssemblyPreparer.cs @@ -140,9 +140,12 @@ public bool Prepare (out List exceptions) new InlineDlfcnMethodsStep (), }; - // If the user explicitly set $(DynamicRegistrationSupported), we don't need to compute the value, so - // skip RegistrarRemovalTrackingStep entirely (the value is passed straight through to the trimmer feature switch). - if (!configuration.DynamicRegistrationSupported.HasValue) + // Only add RegistrarRemovalTrackingStep if it's needed: + // * If the user explicitly set $(DynamicRegistrationSupported), we don't need to compute the value (it's + // passed straight through to the trimmer feature switch). + // * If nothing is being trimmed, the dynamic registrar (which lives in the platform assembly, an SDK + // assembly that's only trimmed when trimming is enabled) can't be removed, so there's nothing to compute. + if (!configuration.DynamicRegistrationSupported.HasValue && configuration.Application.AreAnyAssembliesTrimmed) steps.Add (new RegistrarRemovalTrackingStep ()); // PreMarkDispatcher: I don't think we need this one