From 79ec998f95406ec6a15f7638860ce0ce3f9d98ec Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 22 May 2026 08:59:47 +0200 Subject: [PATCH 01/97] [NSUrlSessionHandler] Reject unsupported auth methods to allow fallback to Basic. Fixes #25485. (#25493) When a server advertises multiple WWW-Authenticate challenges (e.g. Bearer before Basic), the handler would call PerformDefaultHandling for unrecognized methods like Bearer. This prevented the system from trying subsequent methods. Fix by rejecting unrecognized HTTP auth protection spaces, which allows the URL loading system to try the next advertised authentication method. Fixes https://github.com/dotnet/macios/issues/25485. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Foundation/NSUrlSessionHandler.cs | 18 +-- .../NSUrlSessionHandlerTest.cs | 106 ++++++++++++++++++ 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/src/Foundation/NSUrlSessionHandler.cs b/src/Foundation/NSUrlSessionHandler.cs index 2c0af8202220..fc35793204e2 100644 --- a/src/Foundation/NSUrlSessionHandler.cs +++ b/src/Foundation/NSUrlSessionHandler.cs @@ -1175,15 +1175,19 @@ static bool TryGetAuthenticationType (NSUrlProtectionSpace protectionSpace, [Not authenticationType = "basic"; } else if (protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodHTTPDigest) { authenticationType = "digest"; - } else if (protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodNegotiate || - protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodHTMLForm) { - // Want to reject this authentication type to allow the next authentication method in the request to - // be used. - authenticationType = RejectProtectionSpaceAuthType; - } else { - // ServerTrust, ClientCertificate or Default. + } else if (protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodServerTrust || + protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodClientCertificate) { + // ServerTrust and ClientCertificate are handled earlier in DidReceiveChallengeImpl, + // so we should not reach here for these types. Return false just in case. authenticationType = null; return false; + } else { + // For any other authentication method (Negotiate, HTMLForm, Bearer, etc.), + // reject this protection space to allow the next authentication method in the + // request to be tried. This is important when the server advertises multiple + // WWW-Authenticate challenges (e.g. Bearer before Basic) - rejecting unsupported + // methods allows fallback to one we can handle. + authenticationType = RejectProtectionSpaceAuthType; } return true; } diff --git a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs index 86c0b0efb8b3..f8bf44a1035c 100644 --- a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs +++ b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs @@ -3,7 +3,11 @@ // using System; +using System.Net; using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; using System.Threading.Tasks; using Foundation; using NUnit.Framework; @@ -216,5 +220,107 @@ void IgnoreIfExceptionDueToBackgroundServiceInUseByAnotherProcess (Exception? e) Assert.Ignore ("The background service is in use by another process."); } + + // https://github.com/dotnet/macios/issues/25485 + [Test] + public void BasicAuthWorksWhenBearerIsAdvertisedFirst () + { + if (!HttpListener.IsSupported) { + Assert.Inconclusive ("HttpListener is not supported"); + } + + const string username = "admin"; + const string password = "secret"; + var expectedBasicValue = Convert.ToBase64String (Encoding.UTF8.GetBytes ($"{username}:{password}")); + + var serverReady = new SemaphoreSlim (0, 1); + + var httpListener = StartListenerOnAvailablePort (out var listeningPort); + if (httpListener is null) { + Assert.Inconclusive ("Could not find an available port for the test server."); + return; + } + + var serverTask = Task.Run (async () => { + serverReady.Release (); + try { + while (httpListener.IsListening) { + var context = await httpListener.GetContextAsync ().ConfigureAwait (false); + var request = context.Request; + var response = context.Response; + + var authHeader = request.Headers ["Authorization"]; + if (authHeader is not null && authHeader == $"Basic {expectedBasicValue}") { + // Authenticated - return success + response.StatusCode = 200; + var body = Encoding.UTF8.GetBytes ("authenticated"); + response.ContentLength64 = body.Length; + response.OutputStream.Write (body, 0, body.Length); + } else { + // Return 401 with Bearer first, then Basic + response.StatusCode = 401; + response.AddHeader ("WWW-Authenticate", "Bearer realm=\"test\", charset=\"UTF-8\""); + response.AppendHeader ("WWW-Authenticate", "Basic realm=\"test\", charset=\"UTF-8\""); + } + response.Close (); + } + } catch (ObjectDisposedException) { + // listener was stopped + } catch (HttpListenerException) { + // listener was stopped + } + }); + + HttpStatusCode? statusCode = null; + string responseBody = null; + + try { + var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { + await serverReady.WaitAsync ().ConfigureAwait (false); + + using var handler = new NSUrlSessionHandler (); + handler.Credentials = new NetworkCredential (username, password); + using var client = new HttpClient (handler); + using var request = new HttpRequestMessage (HttpMethod.Get, $"http://localhost:{listeningPort}/test"); + var response = await client.SendAsync (request).ConfigureAwait (false); + statusCode = response.StatusCode; + responseBody = await response.Content.ReadAsStringAsync ().ConfigureAwait (false); + }, out var ex); + + Assert.That (done, Is.True, "Request timed out"); + Assert.That (ex, Is.Null, $"Exception: {ex}"); + Assert.That (statusCode, Is.EqualTo (HttpStatusCode.OK), "Expected 200 OK after Basic auth negotiation"); + Assert.That (responseBody, Is.EqualTo ("authenticated"), "Response body"); + + if (serverTask.IsFaulted) + Assert.Fail ($"Server task failed: {serverTask.Exception}"); + } finally { + httpListener.Stop (); + httpListener.Close (); + } + } + + static HttpListener? StartListenerOnAvailablePort (out int listeningPort) + { + // IANA suggested range for dynamic or private ports + const int MinPort = 49215; + const int MaxPort = 65535; + + for (var port = MinPort; port < MaxPort; port++) { + var listener = new HttpListener (); + listener.Prefixes.Add ($"http://*:{port}/"); + try { + listener.Start (); + listeningPort = port; + return listener; + } catch { + // port in use, try next + listener.Close (); + } + } + + listeningPort = -1; + return null; + } } } From 417695160533766d7b979984d6664a67e258aec9 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 22 May 2026 09:45:25 +0200 Subject: [PATCH 02/97] [NSObject] Store the super handle in a ConditionalWeakTable. (#25454) Store the super handle in a ConditionalWeakTable. * Allocate native memory for it. * Cache that native memory in a ConditionalWeakTable. * Free it when the NSObject is freed (but not before). * Obsolete the SuperHandle property in .NET 11, and remove it in XAMCORE_5_0. This is less performant, but any performance issues will be alleviated by the fact that NSObject.GetSuper () will be called much less after #25376. The great advantage is that NSObjectData is now smaller, and fits in the tagged memory returned by `ObjectiveCMarshal.CreateReferenceTrackingHandle`, which will simplify memory management _a lot_ for CoreCLR. Contributes towards https://github.com/dotnet/macios/issues/25383. This is a continuation/simplification of #25405. --- runtime/xamarin/runtime.h | 3 +- src/Foundation/NSObject2.cs | 86 ++++++++++++++----- src/ObjCRuntime/Registrar.cs | 2 +- ...alyst-MonoVM-interpreter-preservedapis.txt | 16 ++-- .../MacCatalyst-MonoVM-interpreter-size.txt | 8 +- .../MacCatalyst-MonoVM-preservedapis.txt | 16 ++-- .../expected/MacCatalyst-MonoVM-size.txt | 8 +- ...atalyst-NativeAOT-TrimmableStatic-size.txt | 6 +- .../expected/MacCatalyst-NativeAOT-size.txt | 4 +- ...reCLR-Interpreter-TrimmableStatic-size.txt | 10 +-- .../MacOSX-CoreCLR-Interpreter-size.txt | 10 +-- .../MacOSX-NativeAOT-TrimmableStatic-size.txt | 4 +- .../expected/MacOSX-NativeAOT-size.txt | 4 +- .../TVOS-MonoVM-interpreter-preservedapis.txt | 16 ++-- .../expected/TVOS-MonoVM-interpreter-size.txt | 4 +- .../expected/TVOS-MonoVM-preservedapis.txt | 16 ++-- .../UnitTests/expected/TVOS-MonoVM-size.txt | 6 +- .../TVOS-NativeAOT-TrimmableStatic-size.txt | 4 +- .../expected/TVOS-NativeAOT-size.txt | 6 +- .../iOS-MonoVM-interpreter-preservedapis.txt | 16 ++-- .../expected/iOS-MonoVM-interpreter-size.txt | 6 +- .../expected/iOS-MonoVM-preservedapis.txt | 16 ++-- .../UnitTests/expected/iOS-MonoVM-size.txt | 8 +- .../iOS-NativeAOT-TrimmableStatic-size.txt | 6 +- .../UnitTests/expected/iOS-NativeAOT-size.txt | 6 +- 25 files changed, 175 insertions(+), 112 deletions(-) diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h index 774713b78dd7..60c461f1b4af 100644 --- a/runtime/xamarin/runtime.h +++ b/runtime/xamarin/runtime.h @@ -367,10 +367,9 @@ extern xamarin_register_assemblies_callback xamarin_register_assemblies; // This has a managed equivalent in NSObject.cs struct NSObjectData { id handle; - struct objc_super* super; uint32_t /* NSObjectFlags */ flags; // if this structure ever changes, the encoding for this method will likely have to be updated in Registrar.RegistrarTypeUnsafe, currently it's: - // Signature = "^{NSObjectData=@^{objc_super}I}:", + // Signature = "^{NSObjectData=@I}:", }; #ifdef __cplusplus diff --git a/src/Foundation/NSObject2.cs b/src/Foundation/NSObject2.cs index 7f1a5adb7e8e..92657093752f 100644 --- a/src/Foundation/NSObject2.cs +++ b/src/Foundation/NSObject2.cs @@ -119,12 +119,9 @@ internal interface INSObjectFactory { #if !COREBUILD // Allocated in native memory, so that it can be accessed from native code without having to deal with the GC. - // Also put objc_super here, because it simplifies code. // This is mirrored in runtime.h and the definition needs to be in sync. struct NSObjectData { - // the layout here is important, the two first fields have to match the objc_super struct. public NativeHandle handle; - public NativeHandle classHandle; public NSObject.Flags flags; } @@ -137,34 +134,42 @@ struct NSObjectData { // * https://github.com/AustinWise/runtime/blob/2bd10ad43df967950657ae0ade1f899dc1b18a41/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs#L15 // * https://github.com/AustinWise/runtime/blob/2bd10ad43df967950657ae0ade1f899dc1b18a41/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs#L59-L71 // * https://github.com/AustinWise/runtime/blob/2bd10ad43df967950657ae0ade1f899dc1b18a41/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs#L181-L185 - unsafe class NSObjectDataHandle { - NSObjectData* data; + unsafe class NSObjectDataHandle : TrackedMemory { + public NSObjectData* Data { get => (NSObjectData*) Value; } + + public NSObjectDataHandle () : base ((nuint) sizeof (NSObjectData)) + { + } + } + + class TrackedMemory { GCHandle handle; - public NSObjectData* Data { get => data; } + public IntPtr Value { get; private set; } - public NSObjectDataHandle () + public unsafe TrackedMemory (nuint size) { - data = Runtime.AllocZeroed (); + Value = (IntPtr) NativeMemory.AllocZeroed (size); } - public void CreateHandle (NSObject obj) + public unsafe void CreateHandle (NSObject trackedObject) { - handle = GCHandle.Alloc (obj, GCHandleType.WeakTrackResurrection); + handle = GCHandle.Alloc (trackedObject, GCHandleType.WeakTrackResurrection); } - ~NSObjectDataHandle () + ~TrackedMemory () { var handleAllocated = handle.IsAllocated; - if (handleAllocated && handle.Target is not null) { // The NSObject instance isn't gone yet, we have to try again later. GC.ReRegisterForFinalize (this); return; } - NativeMemory.Free (data); - data = null; + unsafe { + NativeMemory.Free ((void*) Value); + } + Value = IntPtr.Zero; if (handleAllocated) handle.Free (); @@ -208,6 +213,10 @@ public partial class NSObject : INativeObject // having to attach those threads to to the managed runtime. IntPtr /* unsafe NSObjectData* */ __data; // Read directly from several places in the runtime +#pragma warning disable CS8618 // "Non-nullable field '...' must contain a non-null value when exiting constructor.": this field is always non-null, because NSObject.Initialize is called before anything else is done. + static ConditionalWeakTable super_map; +#pragma warning restore CS8618 + unsafe NativeHandle handle { get => GetData ()->handle; set => GetData ()->handle = value; @@ -388,17 +397,28 @@ internal static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, Flag } } +#if !XAMCORE_5_0 unsafe NativeHandle GetSuper () { - var data = GetData (); - if (data->classHandle == NativeHandle.Zero) - data->classHandle = ClassHandle; - return (IntPtr) (&data->handle); + var memory = super_map.GetValue (this, (obj) => { + unsafe { + var memory = new TrackedMemory ((nuint) sizeof (objc_super)); + memory.CreateHandle (obj); + return memory; + } + }); + objc_super* sup = (objc_super*) memory.Value; + if (sup->ClassHandle == NativeHandle.Zero) + sup->ClassHandle = ClassHandle; + sup->Handle = handle; + return memory.Value; } +#endif // !XAMCORE_5_0 internal static NativeHandle Initialize () { data_table = new ConditionalWeakTable (); + super_map = new ConditionalWeakTable (); return class_ptr; } @@ -722,16 +742,35 @@ public NSObject DangerousAutorelease () return this; } +#if !XAMCORE_5_0 /// Handle used to represent the methods in the base class for this . /// An opaque pointer, represents an Objective-C objc_super object pointing to our base class. /// - /// This property is used to access members of a base class. - /// This is typically used when you call any of the Messaging - /// methods to invoke methods that were implemented in your base - /// class, instead of invoking the implementation in the current - /// class. + /// + /// This property is used to access members of a base class. + /// This is typically used when you call any of the Messaging + /// methods to invoke methods that were implemented in your base + /// class, instead of invoking the implementation in the current + /// class. + /// + /// + /// This property is obsolete; use the struct instead: + /// + /// + /// + /// /// [EditorBrowsable (EditorBrowsableState.Never)] +#if NET11_0_OR_GREATER + [Obsolete ("Use 'ObjCSuper' instead.")] +#endif public NativeHandle SuperHandle { get { if (handle == IntPtr.Zero) @@ -740,6 +779,7 @@ public NativeHandle SuperHandle { return GetSuper (); } } +#endif // !XAMCORE_5_0 /// Handle (pointer) to the unmanaged object representation. /// A pointer. diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index 173bf5645c4d..a90eed1e9ffe 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -2125,7 +2125,7 @@ void FlattenInterfaces (TType? [] ifaces) objcType.Add (new ObjCMethod (this, objcType, null) { Selector = "xamarinGetNSObjectData", Trampoline = Trampoline.GetNSObjectData, - Signature = "^{NSObjectData=@^{objc_super}I}:", + Signature = "^{NSObjectData=@I}:", IsStatic = false, }, ref exceptions); } diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt index 6ec14abe043b..7342f8307492 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt @@ -195,12 +195,9 @@ Microsoft.MacCatalyst.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NS Microsoft.MacCatalyst.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::InitialSet Microsoft.MacCatalyst.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::None Microsoft.MacCatalyst.dll:Foundation.NSObjectData -Microsoft.MacCatalyst.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::data Microsoft.MacCatalyst.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::Data() Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle..ctor() -Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle.CreateHandle(Foundation.NSObject) -Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle.Finalize() Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle.get_Data() Microsoft.MacCatalyst.dll:Foundation.NSObjectFlag Microsoft.MacCatalyst.dll:Foundation.NSObjectFlag Foundation.NSObjectFlag::Empty @@ -252,6 +249,12 @@ Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute.get_IsStubClass() Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute.get_IsWrapper() Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute.get_Name() Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute.get_SkipRegistration() +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory..ctor(System.UIntPtr) +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory.CreateHandle(Foundation.NSObject) +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory.Finalize() +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory.get_Value() +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory.set_Value(System.IntPtr) Microsoft.MacCatalyst.dll:Foundation.You_Should_Not_Call_base_In_This_Method Microsoft.MacCatalyst.dll:Foundation.You_Should_Not_Call_base_In_This_Method..ctor() Microsoft.MacCatalyst.dll:ObjCRuntime.__Registrar__ @@ -495,7 +498,6 @@ Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObject::class_pt Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObject::ClassHandle() Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObject::handle() Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObject::Handle() -Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::classHandle Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::handle Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSString::class_ptr Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSString::ClassHandle() @@ -592,7 +594,6 @@ Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.g__Constru Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.g__ConstructNSObjectViaFactoryMethod|288_0`1(ObjCRuntime.NativeHandle) Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object, System.Runtime.InteropServices.GCHandleType) Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object) -Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AllocZeroed`1() Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AppendAdditionalInformation(System.Text.StringBuilder, System.IntPtr, System.RuntimeMethodHandle) Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.attempt_retain_nsobject(System.IntPtr, System.IntPtr*) Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AttemptRetainNSObject(System.IntPtr) @@ -1334,6 +1335,8 @@ Microsoft.MacCatalyst.dll:System.IntPtr CoreFoundation.CFRange::len Microsoft.MacCatalyst.dll:System.IntPtr CoreFoundation.CFRange::loc Microsoft.MacCatalyst.dll:System.IntPtr Foundation.NSObject::__data Microsoft.MacCatalyst.dll:System.IntPtr Foundation.NSObject/NSObject_Disposer::class_ptr +Microsoft.MacCatalyst.dll:System.IntPtr Foundation.TrackedMemory::k__BackingField +Microsoft.MacCatalyst.dll:System.IntPtr Foundation.TrackedMemory::Value() Microsoft.MacCatalyst.dll:System.IntPtr ObjCRuntime.AdoptsAttribute::ProtocolHandle() Microsoft.MacCatalyst.dll:System.IntPtr ObjCRuntime.BlockCollector::block Microsoft.MacCatalyst.dll:System.IntPtr ObjCRuntime.Libraries/CoreFoundation::Handle @@ -1497,9 +1500,10 @@ Microsoft.MacCatalyst.dll:System.Reflection.MethodBase Registrar.Registrar/ObjCM Microsoft.MacCatalyst.dll:System.Reflection.PropertyInfo Registrar.Registrar/ObjCProperty::Property Microsoft.MacCatalyst.dll:System.Reflection.TypeFilter Registrar.SharedDynamic/<>c::<>9__0_0 Microsoft.MacCatalyst.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::data_table +Microsoft.MacCatalyst.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::super_map Microsoft.MacCatalyst.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Runtime::block_lifetime_table Microsoft.MacCatalyst.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Class::assembly_to_name -Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.GCHandle Foundation.NSObjectDataHandle::handle +Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.GCHandle Foundation.TrackedMemory::handle Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::height Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::width Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-size.txt index 78ee37f1f790..23bdbd58a4b5 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-size.txt @@ -1,9 +1,9 @@ -AppBundleSize: 5,787,160 bytes (5,651.5 KB = 5.5 MB) +AppBundleSize: 5,789,578 bytes (5,653.9 KB = 5.5 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 3,310 bytes (3.2 KB = 0.0 MB) -Contents/Info.plist: 1,101 bytes (1.1 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 4,563,536 bytes (4,456.6 KB = 4.4 MB) -Contents/MonoBundle/Microsoft.MacCatalyst.dll: 157,184 bytes (153.5 KB = 0.1 MB) +Contents/Info.plist: 1,119 bytes (1.1 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 4,565,424 bytes (4,458.4 KB = 4.4 MB) +Contents/MonoBundle/Microsoft.MacCatalyst.dll: 157,696 bytes (154.0 KB = 0.2 MB) Contents/MonoBundle/runtimeconfig.bin: 1,405 bytes (1.4 KB = 0.0 MB) Contents/MonoBundle/SizeTestApp.dll: 7,680 bytes (7.5 KB = 0.0 MB) Contents/MonoBundle/System.Private.CoreLib.aotdata.arm64: 41,224 bytes (40.3 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-preservedapis.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-preservedapis.txt index 0043aa3bcc3b..d9b210cc8000 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-preservedapis.txt @@ -178,12 +178,9 @@ Microsoft.MacCatalyst.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NS Microsoft.MacCatalyst.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::InitialSet Microsoft.MacCatalyst.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::None Microsoft.MacCatalyst.dll:Foundation.NSObjectData -Microsoft.MacCatalyst.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::data Microsoft.MacCatalyst.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::Data() Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle..ctor() -Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle.CreateHandle(Foundation.NSObject) -Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle.Finalize() Microsoft.MacCatalyst.dll:Foundation.NSObjectDataHandle.get_Data() Microsoft.MacCatalyst.dll:Foundation.NSObjectFlag Microsoft.MacCatalyst.dll:Foundation.NSObjectFlag Foundation.NSObjectFlag::Empty @@ -194,6 +191,12 @@ Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute..ctor(System.String, System.Boolean) Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute..ctor(System.String) Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute.get_IsWrapper() +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory..ctor(System.UIntPtr) +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory.CreateHandle(Foundation.NSObject) +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory.Finalize() +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory.get_Value() +Microsoft.MacCatalyst.dll:Foundation.TrackedMemory.set_Value(System.IntPtr) Microsoft.MacCatalyst.dll:Foundation.You_Should_Not_Call_base_In_This_Method Microsoft.MacCatalyst.dll:Foundation.You_Should_Not_Call_base_In_This_Method..ctor() Microsoft.MacCatalyst.dll:ObjCRuntime.__Registrar__ @@ -375,7 +378,6 @@ Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObject::class_pt Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObject::ClassHandle() Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObject::handle() Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObject::Handle() -Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::classHandle Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::handle Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle ObjCRuntime.Class::handle Microsoft.MacCatalyst.dll:ObjCRuntime.NativeHandle ObjCRuntime.Class::Handle() @@ -450,7 +452,6 @@ Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.g__Constru Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.g__ConstructNSObjectViaFactoryMethod|288_0`1(ObjCRuntime.NativeHandle) Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object, System.Runtime.InteropServices.GCHandleType) Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object) -Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AllocZeroed`1() Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AppendAdditionalInformation(System.Text.StringBuilder, System.IntPtr, System.RuntimeMethodHandle) Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.attempt_retain_nsobject(System.IntPtr, System.IntPtr*) Microsoft.MacCatalyst.dll:ObjCRuntime.Runtime.AttemptRetainNSObject(System.IntPtr) @@ -721,6 +722,8 @@ Microsoft.MacCatalyst.dll:System.IntPtr CoreFoundation.CFRange::len Microsoft.MacCatalyst.dll:System.IntPtr CoreFoundation.CFRange::loc Microsoft.MacCatalyst.dll:System.IntPtr Foundation.NSObject::__data Microsoft.MacCatalyst.dll:System.IntPtr Foundation.NSObject/NSObject_Disposer::class_ptr +Microsoft.MacCatalyst.dll:System.IntPtr Foundation.TrackedMemory::k__BackingField +Microsoft.MacCatalyst.dll:System.IntPtr Foundation.TrackedMemory::Value() Microsoft.MacCatalyst.dll:System.IntPtr ObjCRuntime.BlockCollector::block Microsoft.MacCatalyst.dll:System.IntPtr ObjCRuntime.Libraries/CoreFoundation::Handle Microsoft.MacCatalyst.dll:System.IntPtr ObjCRuntime.NativeHandle::handle @@ -852,9 +855,10 @@ Microsoft.MacCatalyst.dll:System.Object ObjCRuntime.Class::verification_lock Microsoft.MacCatalyst.dll:System.Object ObjCRuntime.Runtime::lock_obj Microsoft.MacCatalyst.dll:System.Reflection.Assembly Foundation.NSObject::PlatformAssembly Microsoft.MacCatalyst.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::data_table +Microsoft.MacCatalyst.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::super_map Microsoft.MacCatalyst.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Runtime::block_lifetime_table Microsoft.MacCatalyst.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Class::assembly_to_name -Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.GCHandle Foundation.NSObjectDataHandle::handle +Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.GCHandle Foundation.TrackedMemory::handle Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::height Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::width Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt index 67e1e583c978..48f7aafc2dad 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt @@ -1,10 +1,10 @@ -AppBundleSize: 16,323,580 bytes (15,941.0 KB = 15.6 MB) +AppBundleSize: 16,325,774 bytes (15,943.1 KB = 15.6 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 4,134 bytes (4.0 KB = 0.0 MB) -Contents/Info.plist: 1,101 bytes (1.1 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 13,812,384 bytes (13,488.7 KB = 13.2 MB) +Contents/Info.plist: 1,119 bytes (1.1 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 13,814,640 bytes (13,490.9 KB = 13.2 MB) Contents/MonoBundle/aot-instances.aotdata.arm64: 1,045,032 bytes (1,020.5 KB = 1.0 MB) -Contents/MonoBundle/Microsoft.MacCatalyst.aotdata.arm64: 36,368 bytes (35.5 KB = 0.0 MB) +Contents/MonoBundle/Microsoft.MacCatalyst.aotdata.arm64: 36,288 bytes (35.4 KB = 0.0 MB) Contents/MonoBundle/Microsoft.MacCatalyst.dll: 50,688 bytes (49.5 KB = 0.0 MB) Contents/MonoBundle/runtimeconfig.bin: 1,481 bytes (1.4 KB = 0.0 MB) Contents/MonoBundle/SizeTestApp.aotdata.arm64: 1,552 bytes (1.5 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-TrimmableStatic-size.txt index 59ab57bf44ef..ac4c45f624c1 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-TrimmableStatic-size.txt @@ -1,7 +1,7 @@ -AppBundleSize: 8,792,419 bytes (8,586.3 KB = 8.4 MB) +AppBundleSize: 8,808,853 bytes (8,602.4 KB = 8.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 2,358 bytes (2.3 KB = 0.0 MB) -Contents/Info.plist: 1,101 bytes (1.1 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 8,787,056 bytes (8,581.1 KB = 8.4 MB) +Contents/Info.plist: 1,119 bytes (1.1 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 8,803,472 bytes (8,597.1 KB = 8.4 MB) Contents/MonoBundle/runtimeconfig.bin: 1,896 bytes (1.9 KB = 0.0 MB) Contents/PkgInfo: 8 bytes (0.0 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-size.txt index 8a8b69daa88a..f35ae9613d96 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-size.txt @@ -1,7 +1,7 @@ -AppBundleSize: 2,781,131 bytes (2,715.9 KB = 2.7 MB) +AppBundleSize: 2,781,149 bytes (2,716.0 KB = 2.7 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 2,358 bytes (2.3 KB = 0.0 MB) -Contents/Info.plist: 1,101 bytes (1.1 KB = 0.0 MB) +Contents/Info.plist: 1,119 bytes (1.1 KB = 0.0 MB) Contents/MacOS/SizeTestApp: 2,775,856 bytes (2,710.8 KB = 2.6 MB) Contents/MonoBundle/runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) Contents/PkgInfo: 8 bytes (0.0 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt index 067f24eb504d..726ac17a14c8 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt @@ -1,12 +1,12 @@ -AppBundleSize: 260,130,342 bytes (254,033.5 KB = 248.1 MB) +AppBundleSize: 260,131,912 bytes (254,035.1 KB = 248.1 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 67,868 bytes (66.3 KB = 0.1 MB) -Contents/Info.plist: 732 bytes (0.7 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 7,431,264 bytes (7,257.1 KB = 7.1 MB) +Contents/Info.plist: 750 bytes (0.7 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 7,431,792 bytes (7,257.6 KB = 7.1 MB) Contents/MonoBundle/.xamarin/osx-arm64/_Microsoft.macOS.TypeMap.dll: 4,847,616 bytes (4,734.0 KB = 4.6 MB) Contents/MonoBundle/.xamarin/osx-arm64/_SizeTestApp.TypeMap.dll: 3,072 bytes (3.0 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll: 893,192 bytes (872.3 KB = 0.9 MB) -Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: 38,523,904 bytes (37,621.0 KB = 36.7 MB) +Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: 38,524,416 bytes (37,621.5 KB = 36.7 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.Core.dll: 1,335,096 bytes (1,303.8 KB = 1.3 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll: 17,680 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.Win32.Primitives.dll: 16,144 bytes (15.8 KB = 0.0 MB) @@ -182,7 +182,7 @@ Contents/MonoBundle/.xamarin/osx-arm64/WindowsBase.dll: 16,688 bytes (16.3 KB = Contents/MonoBundle/.xamarin/osx-x64/_Microsoft.macOS.TypeMap.dll: 4,847,616 bytes (4,734.0 KB = 4.6 MB) Contents/MonoBundle/.xamarin/osx-x64/_SizeTestApp.TypeMap.dll: 3,072 bytes (3.0 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.CSharp.dll: 796,432 bytes (777.8 KB = 0.8 MB) -Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: 38,523,904 bytes (37,621.0 KB = 36.7 MB) +Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: 38,524,416 bytes (37,621.5 KB = 36.7 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.Core.dll: 1,166,648 bytes (1,139.3 KB = 1.1 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.dll: 17,680 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.Win32.Primitives.dll: 16,176 bytes (15.8 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-size.txt index 83d57f54803e..c7f0715b7541 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-size.txt @@ -1,10 +1,10 @@ -AppBundleSize: 248,292,016 bytes (242,472.7 KB = 236.8 MB) +AppBundleSize: 248,293,554 bytes (242,474.2 KB = 236.8 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 67,160 bytes (65.6 KB = 0.1 MB) -Contents/Info.plist: 732 bytes (0.7 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 8,087,552 bytes (7,898.0 KB = 7.7 MB) +Contents/Info.plist: 750 bytes (0.7 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 8,088,048 bytes (7,898.5 KB = 7.7 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll: 893,192 bytes (872.3 KB = 0.9 MB) -Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: 37,127,168 bytes (36,257.0 KB = 35.4 MB) +Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: 37,127,680 bytes (36,257.5 KB = 35.4 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.Core.dll: 1,335,096 bytes (1,303.8 KB = 1.3 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll: 17,680 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.Win32.Primitives.dll: 16,144 bytes (15.8 KB = 0.0 MB) @@ -178,7 +178,7 @@ Contents/MonoBundle/.xamarin/osx-arm64/System.Xml.XPath.dll: 16,144 bytes (15.8 Contents/MonoBundle/.xamarin/osx-arm64/System.Xml.XPath.XDocument.dll: 17,672 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/WindowsBase.dll: 16,688 bytes (16.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.CSharp.dll: 796,432 bytes (777.8 KB = 0.8 MB) -Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: 37,127,168 bytes (36,257.0 KB = 35.4 MB) +Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: 37,127,680 bytes (36,257.5 KB = 35.4 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.Core.dll: 1,166,648 bytes (1,139.3 KB = 1.1 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.dll: 17,680 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.Win32.Primitives.dll: 16,176 bytes (15.8 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-TrimmableStatic-size.txt index 9a45e4dcfb44..03086f1a3700 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-TrimmableStatic-size.txt @@ -1,7 +1,7 @@ -AppBundleSize: 21,531,678 bytes (21,027.0 KB = 20.5 MB) +AppBundleSize: 21,531,696 bytes (21,027.0 KB = 20.5 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 3,489 bytes (3.4 KB = 0.0 MB) -Contents/Info.plist: 732 bytes (0.7 KB = 0.0 MB) +Contents/Info.plist: 750 bytes (0.7 KB = 0.0 MB) Contents/MacOS/SizeTestApp: 18,538,016 bytes (18,103.5 KB = 17.7 MB) Contents/MonoBundle/libSystem.Globalization.Native.dylib: 252,176 bytes (246.3 KB = 0.2 MB) Contents/MonoBundle/libSystem.IO.Compression.Native.dylib: 2,005,440 bytes (1,958.4 KB = 1.9 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt index 40ce560ec153..73e9feab2def 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt @@ -1,7 +1,7 @@ -AppBundleSize: 8,835,196 bytes (8,628.1 KB = 8.4 MB) +AppBundleSize: 8,835,214 bytes (8,628.1 KB = 8.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 3,489 bytes (3.4 KB = 0.0 MB) -Contents/Info.plist: 732 bytes (0.7 KB = 0.0 MB) +Contents/Info.plist: 750 bytes (0.7 KB = 0.0 MB) Contents/MacOS/SizeTestApp: 5,841,616 bytes (5,704.7 KB = 5.6 MB) Contents/MonoBundle/libSystem.Globalization.Native.dylib: 252,176 bytes (246.3 KB = 0.2 MB) Contents/MonoBundle/libSystem.IO.Compression.Native.dylib: 2,005,440 bytes (1,958.4 KB = 1.9 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-preservedapis.txt b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-preservedapis.txt index 9dc7e411bbb3..5dc7ee998d0d 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-preservedapis.txt @@ -187,12 +187,9 @@ Microsoft.tvOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/ Microsoft.tvOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::InitialSet Microsoft.tvOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::None Microsoft.tvOS.dll:Foundation.NSObjectData -Microsoft.tvOS.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::data Microsoft.tvOS.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::Data() Microsoft.tvOS.dll:Foundation.NSObjectDataHandle Microsoft.tvOS.dll:Foundation.NSObjectDataHandle..ctor() -Microsoft.tvOS.dll:Foundation.NSObjectDataHandle.CreateHandle(Foundation.NSObject) -Microsoft.tvOS.dll:Foundation.NSObjectDataHandle.Finalize() Microsoft.tvOS.dll:Foundation.NSObjectDataHandle.get_Data() Microsoft.tvOS.dll:Foundation.NSObjectFlag Microsoft.tvOS.dll:Foundation.NSObjectFlag Foundation.NSObjectFlag::Empty @@ -244,6 +241,12 @@ Microsoft.tvOS.dll:Foundation.RegisterAttribute.get_IsStubClass() Microsoft.tvOS.dll:Foundation.RegisterAttribute.get_IsWrapper() Microsoft.tvOS.dll:Foundation.RegisterAttribute.get_Name() Microsoft.tvOS.dll:Foundation.RegisterAttribute.get_SkipRegistration() +Microsoft.tvOS.dll:Foundation.TrackedMemory +Microsoft.tvOS.dll:Foundation.TrackedMemory..ctor(System.UIntPtr) +Microsoft.tvOS.dll:Foundation.TrackedMemory.CreateHandle(Foundation.NSObject) +Microsoft.tvOS.dll:Foundation.TrackedMemory.Finalize() +Microsoft.tvOS.dll:Foundation.TrackedMemory.get_Value() +Microsoft.tvOS.dll:Foundation.TrackedMemory.set_Value(System.IntPtr) Microsoft.tvOS.dll:Foundation.You_Should_Not_Call_base_In_This_Method Microsoft.tvOS.dll:Foundation.You_Should_Not_Call_base_In_This_Method..ctor() Microsoft.tvOS.dll:ObjCRuntime.__Registrar__ @@ -490,7 +493,6 @@ Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::class_ptr Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::ClassHandle() Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::handle() Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::Handle() -Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::classHandle Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::handle Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSString::class_ptr Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSString::ClassHandle() @@ -588,7 +590,6 @@ Microsoft.tvOS.dll:ObjCRuntime.Runtime.g__ConstructINati Microsoft.tvOS.dll:ObjCRuntime.Runtime.g__ConstructNSObjectViaFactoryMethod|288_0`1(ObjCRuntime.NativeHandle) Microsoft.tvOS.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object, System.Runtime.InteropServices.GCHandleType) Microsoft.tvOS.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object) -Microsoft.tvOS.dll:ObjCRuntime.Runtime.AllocZeroed`1() Microsoft.tvOS.dll:ObjCRuntime.Runtime.AppendAdditionalInformation(System.Text.StringBuilder, System.IntPtr, System.RuntimeMethodHandle) Microsoft.tvOS.dll:ObjCRuntime.Runtime.attempt_retain_nsobject(System.IntPtr, System.IntPtr*) Microsoft.tvOS.dll:ObjCRuntime.Runtime.AttemptRetainNSObject(System.IntPtr) @@ -1324,6 +1325,8 @@ Microsoft.tvOS.dll:System.IntPtr CoreFoundation.CFRange::len Microsoft.tvOS.dll:System.IntPtr CoreFoundation.CFRange::loc Microsoft.tvOS.dll:System.IntPtr Foundation.NSObject::__data Microsoft.tvOS.dll:System.IntPtr Foundation.NSObject/NSObject_Disposer::class_ptr +Microsoft.tvOS.dll:System.IntPtr Foundation.TrackedMemory::k__BackingField +Microsoft.tvOS.dll:System.IntPtr Foundation.TrackedMemory::Value() Microsoft.tvOS.dll:System.IntPtr ObjCRuntime.AdoptsAttribute::ProtocolHandle() Microsoft.tvOS.dll:System.IntPtr ObjCRuntime.BlockCollector::block Microsoft.tvOS.dll:System.IntPtr ObjCRuntime.Libraries/CoreFoundation::Handle @@ -1485,9 +1488,10 @@ Microsoft.tvOS.dll:System.Reflection.MethodBase Registrar.Registrar/ObjCMethod:: Microsoft.tvOS.dll:System.Reflection.PropertyInfo Registrar.Registrar/ObjCProperty::Property Microsoft.tvOS.dll:System.Reflection.TypeFilter Registrar.SharedDynamic/<>c::<>9__0_0 Microsoft.tvOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::data_table +Microsoft.tvOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::super_map Microsoft.tvOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Runtime::block_lifetime_table Microsoft.tvOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Class::assembly_to_name -Microsoft.tvOS.dll:System.Runtime.InteropServices.GCHandle Foundation.NSObjectDataHandle::handle +Microsoft.tvOS.dll:System.Runtime.InteropServices.GCHandle Foundation.TrackedMemory::handle Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::height Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::width Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x diff --git a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt index 50aaf0f31b2f..aa1d0fe08b6c 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 3,616,420 bytes (3,531.7 KB = 3.4 MB) +AppBundleSize: 3,616,438 bytes (3,531.7 KB = 3.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 3,999 bytes (3.9 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,120 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,138 bytes (1.1 KB = 0.0 MB) Microsoft.tvOS.dll: 154,624 bytes (151.0 KB = 0.1 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,405 bytes (1.4 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-preservedapis.txt b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-preservedapis.txt index 897cc8c3871e..fef21b016f75 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-preservedapis.txt @@ -170,12 +170,9 @@ Microsoft.tvOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/ Microsoft.tvOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::InitialSet Microsoft.tvOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::None Microsoft.tvOS.dll:Foundation.NSObjectData -Microsoft.tvOS.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::data Microsoft.tvOS.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::Data() Microsoft.tvOS.dll:Foundation.NSObjectDataHandle Microsoft.tvOS.dll:Foundation.NSObjectDataHandle..ctor() -Microsoft.tvOS.dll:Foundation.NSObjectDataHandle.CreateHandle(Foundation.NSObject) -Microsoft.tvOS.dll:Foundation.NSObjectDataHandle.Finalize() Microsoft.tvOS.dll:Foundation.NSObjectDataHandle.get_Data() Microsoft.tvOS.dll:Foundation.NSObjectFlag Microsoft.tvOS.dll:Foundation.NSObjectFlag Foundation.NSObjectFlag::Empty @@ -186,6 +183,12 @@ Microsoft.tvOS.dll:Foundation.RegisterAttribute Microsoft.tvOS.dll:Foundation.RegisterAttribute..ctor(System.String, System.Boolean) Microsoft.tvOS.dll:Foundation.RegisterAttribute..ctor(System.String) Microsoft.tvOS.dll:Foundation.RegisterAttribute.get_IsWrapper() +Microsoft.tvOS.dll:Foundation.TrackedMemory +Microsoft.tvOS.dll:Foundation.TrackedMemory..ctor(System.UIntPtr) +Microsoft.tvOS.dll:Foundation.TrackedMemory.CreateHandle(Foundation.NSObject) +Microsoft.tvOS.dll:Foundation.TrackedMemory.Finalize() +Microsoft.tvOS.dll:Foundation.TrackedMemory.get_Value() +Microsoft.tvOS.dll:Foundation.TrackedMemory.set_Value(System.IntPtr) Microsoft.tvOS.dll:Foundation.You_Should_Not_Call_base_In_This_Method Microsoft.tvOS.dll:Foundation.You_Should_Not_Call_base_In_This_Method..ctor() Microsoft.tvOS.dll:ObjCRuntime.__Registrar__ @@ -370,7 +373,6 @@ Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::class_ptr Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::ClassHandle() Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::handle() Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::Handle() -Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::classHandle Microsoft.tvOS.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::handle Microsoft.tvOS.dll:ObjCRuntime.NativeHandle ObjCRuntime.Class::handle Microsoft.tvOS.dll:ObjCRuntime.NativeHandle ObjCRuntime.Class::Handle() @@ -446,7 +448,6 @@ Microsoft.tvOS.dll:ObjCRuntime.Runtime.g__ConstructINati Microsoft.tvOS.dll:ObjCRuntime.Runtime.g__ConstructNSObjectViaFactoryMethod|288_0`1(ObjCRuntime.NativeHandle) Microsoft.tvOS.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object, System.Runtime.InteropServices.GCHandleType) Microsoft.tvOS.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object) -Microsoft.tvOS.dll:ObjCRuntime.Runtime.AllocZeroed`1() Microsoft.tvOS.dll:ObjCRuntime.Runtime.AppendAdditionalInformation(System.Text.StringBuilder, System.IntPtr, System.RuntimeMethodHandle) Microsoft.tvOS.dll:ObjCRuntime.Runtime.attempt_retain_nsobject(System.IntPtr, System.IntPtr*) Microsoft.tvOS.dll:ObjCRuntime.Runtime.AttemptRetainNSObject(System.IntPtr) @@ -716,6 +717,8 @@ Microsoft.tvOS.dll:System.IntPtr CoreFoundation.CFRange::len Microsoft.tvOS.dll:System.IntPtr CoreFoundation.CFRange::loc Microsoft.tvOS.dll:System.IntPtr Foundation.NSObject::__data Microsoft.tvOS.dll:System.IntPtr Foundation.NSObject/NSObject_Disposer::class_ptr +Microsoft.tvOS.dll:System.IntPtr Foundation.TrackedMemory::k__BackingField +Microsoft.tvOS.dll:System.IntPtr Foundation.TrackedMemory::Value() Microsoft.tvOS.dll:System.IntPtr ObjCRuntime.BlockCollector::block Microsoft.tvOS.dll:System.IntPtr ObjCRuntime.Libraries/CoreFoundation::Handle Microsoft.tvOS.dll:System.IntPtr ObjCRuntime.NativeHandle::handle @@ -845,9 +848,10 @@ Microsoft.tvOS.dll:System.Object Foundation.NSObject/NSObject_Disposer::lock_obj Microsoft.tvOS.dll:System.Object ObjCRuntime.Runtime::lock_obj Microsoft.tvOS.dll:System.Reflection.Assembly Foundation.NSObject::PlatformAssembly Microsoft.tvOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::data_table +Microsoft.tvOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::super_map Microsoft.tvOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Runtime::block_lifetime_table Microsoft.tvOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Class::assembly_to_name -Microsoft.tvOS.dll:System.Runtime.InteropServices.GCHandle Foundation.NSObjectDataHandle::handle +Microsoft.tvOS.dll:System.Runtime.InteropServices.GCHandle Foundation.TrackedMemory::handle Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::height Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::width Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x diff --git a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-size.txt b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-size.txt index 756f7510197e..35b977aeb175 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-size.txt @@ -1,10 +1,10 @@ -AppBundleSize: 9,364,234 bytes (9,144.8 KB = 8.9 MB) +AppBundleSize: 9,364,148 bytes (9,144.7 KB = 8.9 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 5,233 bytes (5.1 KB = 0.0 MB) aot-instances.aotdata.arm64: 827,592 bytes (808.2 KB = 0.8 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,120 bytes (1.1 KB = 0.0 MB) -Microsoft.tvOS.aotdata.arm64: 22,984 bytes (22.4 KB = 0.0 MB) +Info.plist: 1,138 bytes (1.1 KB = 0.0 MB) +Microsoft.tvOS.aotdata.arm64: 22,880 bytes (22.3 KB = 0.0 MB) Microsoft.tvOS.dll: 48,640 bytes (47.5 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,481 bytes (1.4 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt index ce409263e277..756b1289a4de 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 7,922,022 bytes (7,736.3 KB = 7.6 MB) +AppBundleSize: 7,922,040 bytes (7,736.4 KB = 7.6 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,120 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,138 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,889 bytes (1.8 KB = 0.0 MB) SizeTestApp: 7,916,032 bytes (7,730.5 KB = 7.5 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-size.txt index 7ede3e0bb7e8..15f36c7f55eb 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 2,783,125 bytes (2,717.9 KB = 2.7 MB) +AppBundleSize: 2,783,159 bytes (2,717.9 KB = 2.7 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,120 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,138 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 2,777,216 bytes (2,712.1 KB = 2.6 MB) +SizeTestApp: 2,777,232 bytes (2,712.1 KB = 2.6 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-preservedapis.txt b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-preservedapis.txt index c2062123e2fc..0178f1e5c2e3 100644 --- a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-preservedapis.txt @@ -187,12 +187,9 @@ Microsoft.iOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/X Microsoft.iOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::InitialSet Microsoft.iOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::None Microsoft.iOS.dll:Foundation.NSObjectData -Microsoft.iOS.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::data Microsoft.iOS.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::Data() Microsoft.iOS.dll:Foundation.NSObjectDataHandle Microsoft.iOS.dll:Foundation.NSObjectDataHandle..ctor() -Microsoft.iOS.dll:Foundation.NSObjectDataHandle.CreateHandle(Foundation.NSObject) -Microsoft.iOS.dll:Foundation.NSObjectDataHandle.Finalize() Microsoft.iOS.dll:Foundation.NSObjectDataHandle.get_Data() Microsoft.iOS.dll:Foundation.NSObjectFlag Microsoft.iOS.dll:Foundation.NSObjectFlag Foundation.NSObjectFlag::Empty @@ -244,6 +241,12 @@ Microsoft.iOS.dll:Foundation.RegisterAttribute.get_IsStubClass() Microsoft.iOS.dll:Foundation.RegisterAttribute.get_IsWrapper() Microsoft.iOS.dll:Foundation.RegisterAttribute.get_Name() Microsoft.iOS.dll:Foundation.RegisterAttribute.get_SkipRegistration() +Microsoft.iOS.dll:Foundation.TrackedMemory +Microsoft.iOS.dll:Foundation.TrackedMemory..ctor(System.UIntPtr) +Microsoft.iOS.dll:Foundation.TrackedMemory.CreateHandle(Foundation.NSObject) +Microsoft.iOS.dll:Foundation.TrackedMemory.Finalize() +Microsoft.iOS.dll:Foundation.TrackedMemory.get_Value() +Microsoft.iOS.dll:Foundation.TrackedMemory.set_Value(System.IntPtr) Microsoft.iOS.dll:Foundation.You_Should_Not_Call_base_In_This_Method Microsoft.iOS.dll:Foundation.You_Should_Not_Call_base_In_This_Method..ctor() Microsoft.iOS.dll:ObjCRuntime.__Registrar__ @@ -490,7 +493,6 @@ Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::class_ptr Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::ClassHandle() Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::handle() Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::Handle() -Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::classHandle Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::handle Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSString::class_ptr Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSString::ClassHandle() @@ -588,7 +590,6 @@ Microsoft.iOS.dll:ObjCRuntime.Runtime.g__ConstructINativ Microsoft.iOS.dll:ObjCRuntime.Runtime.g__ConstructNSObjectViaFactoryMethod|288_0`1(ObjCRuntime.NativeHandle) Microsoft.iOS.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object, System.Runtime.InteropServices.GCHandleType) Microsoft.iOS.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object) -Microsoft.iOS.dll:ObjCRuntime.Runtime.AllocZeroed`1() Microsoft.iOS.dll:ObjCRuntime.Runtime.AppendAdditionalInformation(System.Text.StringBuilder, System.IntPtr, System.RuntimeMethodHandle) Microsoft.iOS.dll:ObjCRuntime.Runtime.attempt_retain_nsobject(System.IntPtr, System.IntPtr*) Microsoft.iOS.dll:ObjCRuntime.Runtime.AttemptRetainNSObject(System.IntPtr) @@ -1324,6 +1325,8 @@ Microsoft.iOS.dll:System.IntPtr CoreFoundation.CFRange::len Microsoft.iOS.dll:System.IntPtr CoreFoundation.CFRange::loc Microsoft.iOS.dll:System.IntPtr Foundation.NSObject::__data Microsoft.iOS.dll:System.IntPtr Foundation.NSObject/NSObject_Disposer::class_ptr +Microsoft.iOS.dll:System.IntPtr Foundation.TrackedMemory::k__BackingField +Microsoft.iOS.dll:System.IntPtr Foundation.TrackedMemory::Value() Microsoft.iOS.dll:System.IntPtr ObjCRuntime.AdoptsAttribute::ProtocolHandle() Microsoft.iOS.dll:System.IntPtr ObjCRuntime.BlockCollector::block Microsoft.iOS.dll:System.IntPtr ObjCRuntime.Libraries/CoreFoundation::Handle @@ -1485,9 +1488,10 @@ Microsoft.iOS.dll:System.Reflection.MethodBase Registrar.Registrar/ObjCMethod::M Microsoft.iOS.dll:System.Reflection.PropertyInfo Registrar.Registrar/ObjCProperty::Property Microsoft.iOS.dll:System.Reflection.TypeFilter Registrar.SharedDynamic/<>c::<>9__0_0 Microsoft.iOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::data_table +Microsoft.iOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::super_map Microsoft.iOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Runtime::block_lifetime_table Microsoft.iOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Class::assembly_to_name -Microsoft.iOS.dll:System.Runtime.InteropServices.GCHandle Foundation.NSObjectDataHandle::handle +Microsoft.iOS.dll:System.Runtime.InteropServices.GCHandle Foundation.TrackedMemory::handle Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::height Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::width Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x diff --git a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt index 0c9c253439b5..ebe56d99fd5b 100644 --- a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt @@ -1,12 +1,12 @@ -AppBundleSize: 3,602,873 bytes (3,518.4 KB = 3.4 MB) +AppBundleSize: 3,616,747 bytes (3,532.0 KB = 3.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 3,997 bytes (3.9 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,143 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,161 bytes (1.1 KB = 0.0 MB) Microsoft.iOS.dll: 154,624 bytes (151.0 KB = 0.1 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,405 bytes (1.4 KB = 0.0 MB) -SizeTestApp: 2,390,848 bytes (2,334.8 KB = 2.3 MB) +SizeTestApp: 2,404,704 bytes (2,348.3 KB = 2.3 MB) SizeTestApp.dll: 7,680 bytes (7.5 KB = 0.0 MB) System.Private.CoreLib.aotdata.arm64: 41,312 bytes (40.3 KB = 0.0 MB) System.Private.CoreLib.dll: 988,160 bytes (965.0 KB = 0.9 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-MonoVM-preservedapis.txt b/tests/dotnet/UnitTests/expected/iOS-MonoVM-preservedapis.txt index 0b5ab2d71783..5971b208e5d3 100644 --- a/tests/dotnet/UnitTests/expected/iOS-MonoVM-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/iOS-MonoVM-preservedapis.txt @@ -170,12 +170,9 @@ Microsoft.iOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/X Microsoft.iOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::InitialSet Microsoft.iOS.dll:Foundation.NSObject/XamarinGCHandleFlags Foundation.NSObject/XamarinGCHandleFlags::None Microsoft.iOS.dll:Foundation.NSObjectData -Microsoft.iOS.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::data Microsoft.iOS.dll:Foundation.NSObjectData* Foundation.NSObjectDataHandle::Data() Microsoft.iOS.dll:Foundation.NSObjectDataHandle Microsoft.iOS.dll:Foundation.NSObjectDataHandle..ctor() -Microsoft.iOS.dll:Foundation.NSObjectDataHandle.CreateHandle(Foundation.NSObject) -Microsoft.iOS.dll:Foundation.NSObjectDataHandle.Finalize() Microsoft.iOS.dll:Foundation.NSObjectDataHandle.get_Data() Microsoft.iOS.dll:Foundation.NSObjectFlag Microsoft.iOS.dll:Foundation.NSObjectFlag Foundation.NSObjectFlag::Empty @@ -186,6 +183,12 @@ Microsoft.iOS.dll:Foundation.RegisterAttribute Microsoft.iOS.dll:Foundation.RegisterAttribute..ctor(System.String, System.Boolean) Microsoft.iOS.dll:Foundation.RegisterAttribute..ctor(System.String) Microsoft.iOS.dll:Foundation.RegisterAttribute.get_IsWrapper() +Microsoft.iOS.dll:Foundation.TrackedMemory +Microsoft.iOS.dll:Foundation.TrackedMemory..ctor(System.UIntPtr) +Microsoft.iOS.dll:Foundation.TrackedMemory.CreateHandle(Foundation.NSObject) +Microsoft.iOS.dll:Foundation.TrackedMemory.Finalize() +Microsoft.iOS.dll:Foundation.TrackedMemory.get_Value() +Microsoft.iOS.dll:Foundation.TrackedMemory.set_Value(System.IntPtr) Microsoft.iOS.dll:Foundation.You_Should_Not_Call_base_In_This_Method Microsoft.iOS.dll:Foundation.You_Should_Not_Call_base_In_This_Method..ctor() Microsoft.iOS.dll:ObjCRuntime.__Registrar__ @@ -370,7 +373,6 @@ Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::class_ptr Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::ClassHandle() Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::handle() Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObject::Handle() -Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::classHandle Microsoft.iOS.dll:ObjCRuntime.NativeHandle Foundation.NSObjectData::handle Microsoft.iOS.dll:ObjCRuntime.NativeHandle ObjCRuntime.Class::handle Microsoft.iOS.dll:ObjCRuntime.NativeHandle ObjCRuntime.Class::Handle() @@ -446,7 +448,6 @@ Microsoft.iOS.dll:ObjCRuntime.Runtime.g__ConstructINativ Microsoft.iOS.dll:ObjCRuntime.Runtime.g__ConstructNSObjectViaFactoryMethod|288_0`1(ObjCRuntime.NativeHandle) Microsoft.iOS.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object, System.Runtime.InteropServices.GCHandleType) Microsoft.iOS.dll:ObjCRuntime.Runtime.AllocGCHandle(System.Object) -Microsoft.iOS.dll:ObjCRuntime.Runtime.AllocZeroed`1() Microsoft.iOS.dll:ObjCRuntime.Runtime.AppendAdditionalInformation(System.Text.StringBuilder, System.IntPtr, System.RuntimeMethodHandle) Microsoft.iOS.dll:ObjCRuntime.Runtime.attempt_retain_nsobject(System.IntPtr, System.IntPtr*) Microsoft.iOS.dll:ObjCRuntime.Runtime.AttemptRetainNSObject(System.IntPtr) @@ -716,6 +717,8 @@ Microsoft.iOS.dll:System.IntPtr CoreFoundation.CFRange::len Microsoft.iOS.dll:System.IntPtr CoreFoundation.CFRange::loc Microsoft.iOS.dll:System.IntPtr Foundation.NSObject::__data Microsoft.iOS.dll:System.IntPtr Foundation.NSObject/NSObject_Disposer::class_ptr +Microsoft.iOS.dll:System.IntPtr Foundation.TrackedMemory::k__BackingField +Microsoft.iOS.dll:System.IntPtr Foundation.TrackedMemory::Value() Microsoft.iOS.dll:System.IntPtr ObjCRuntime.BlockCollector::block Microsoft.iOS.dll:System.IntPtr ObjCRuntime.Libraries/CoreFoundation::Handle Microsoft.iOS.dll:System.IntPtr ObjCRuntime.NativeHandle::handle @@ -845,9 +848,10 @@ Microsoft.iOS.dll:System.Object Foundation.NSObject/NSObject_Disposer::lock_obj Microsoft.iOS.dll:System.Object ObjCRuntime.Runtime::lock_obj Microsoft.iOS.dll:System.Reflection.Assembly Foundation.NSObject::PlatformAssembly Microsoft.iOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::data_table +Microsoft.iOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 Foundation.NSObject::super_map Microsoft.iOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Runtime::block_lifetime_table Microsoft.iOS.dll:System.Runtime.CompilerServices.ConditionalWeakTable`2 ObjCRuntime.Class::assembly_to_name -Microsoft.iOS.dll:System.Runtime.InteropServices.GCHandle Foundation.NSObjectDataHandle::handle +Microsoft.iOS.dll:System.Runtime.InteropServices.GCHandle Foundation.TrackedMemory::handle Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::height Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::width Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x diff --git a/tests/dotnet/UnitTests/expected/iOS-MonoVM-size.txt b/tests/dotnet/UnitTests/expected/iOS-MonoVM-size.txt index 12ad8c361f6a..4b084761bcb9 100644 --- a/tests/dotnet/UnitTests/expected/iOS-MonoVM-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-MonoVM-size.txt @@ -1,14 +1,14 @@ -AppBundleSize: 9,338,573 bytes (9,119.7 KB = 8.9 MB) +AppBundleSize: 9,380,687 bytes (9,160.8 KB = 8.9 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 5,229 bytes (5.1 KB = 0.0 MB) aot-instances.aotdata.arm64: 827,592 bytes (808.2 KB = 0.8 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,143 bytes (1.1 KB = 0.0 MB) -Microsoft.iOS.aotdata.arm64: 23,336 bytes (22.8 KB = 0.0 MB) +Info.plist: 1,161 bytes (1.1 KB = 0.0 MB) +Microsoft.iOS.aotdata.arm64: 23,224 bytes (22.7 KB = 0.0 MB) Microsoft.iOS.dll: 48,640 bytes (47.5 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,481 bytes (1.4 KB = 0.0 MB) -SizeTestApp: 7,235,120 bytes (7,065.5 KB = 6.9 MB) +SizeTestApp: 7,277,328 bytes (7,106.8 KB = 6.9 MB) SizeTestApp.aotdata.arm64: 1,464 bytes (1.4 KB = 0.0 MB) SizeTestApp.dll: 7,680 bytes (7.5 KB = 0.0 MB) System.Private.CoreLib.aotdata.arm64: 640,656 bytes (625.6 KB = 0.6 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-TrimmableStatic-size.txt index c3b634481f8f..5c14be37f20a 100644 --- a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-TrimmableStatic-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 9,072,044 bytes (8,859.4 KB = 8.7 MB) +AppBundleSize: 9,124,974 bytes (8,911.1 KB = 8.7 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,143 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,161 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,888 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 9,066,032 bytes (8,853.5 KB = 8.6 MB) +SizeTestApp: 9,118,944 bytes (8,905.2 KB = 8.7 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt index 12ee378aba96..9977114c7ece 100644 --- a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 2,783,868 bytes (2,718.6 KB = 2.7 MB) +AppBundleSize: 2,800,030 bytes (2,734.4 KB = 2.7 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,143 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,161 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 2,777,936 bytes (2,712.8 KB = 2.6 MB) +SizeTestApp: 2,794,080 bytes (2,728.6 KB = 2.7 MB) From 7717e7b6275597b0b3a028e0e915b9b043af5adc Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 22 May 2026 11:21:56 +0200 Subject: [PATCH 03/97] [mtouch] Make mtouch work without Xcode, since we don't really need Xcode for what mtouch currently does. (#25430) mtouch only needs to know the Xcode version to do what it currently does (create the partial static registrar code during our build), so just pass that instead of the path to Xcode. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/common/Application.cs | 8 ++++++++ tools/common/Driver.cs | 12 +++++++++++- tools/common/FileCopier.cs | 2 +- tools/common/PathUtils.cs | 2 +- tools/common/Target.cs | 2 +- tools/mtouch/Makefile | 16 +++++++++++----- tools/mtouch/mtouch.cs | 2 -- 7 files changed, 33 insertions(+), 11 deletions(-) diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 6ee57f4c9b12..56f008d19b66 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -265,6 +265,14 @@ public bool PackageManagedDebugSymbols { public Version GetMacCatalystiOSVersion (Version macOSVersion) { +#if LEGACY_TOOLS + if (macOSVersion.Major >= 26 && Driver.SdkRoot is null) { + // this shouldn't happen for normal builds, nor for customers, so just show an internal 99 warning. + ErrorHelper.Warning (99, Errors.MX0099, $"No Xcode configured, assuming the macOS version {macOSVersion} is identical to the Mac Catalyst/iOS version."); + return macOSVersion; + } +#endif + if (!MacCatalystSupport.TryGetiOSVersion (Driver.GetFrameworkDirectory (this), macOSVersion, out var value, out var knownMacOSVersions)) throw ErrorHelper.CreateError (184, Errors.MX0184 /* Could not map the macOS version {0} to a corresponding Mac Catalyst version. Valid macOS versions are: {1} */, macOSVersion.ToString (), string.Join (", ", knownMacOSVersions.OrderBy (v => v))); diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index 50bd8dbd3cce..5ff3ad4d8fe9 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -69,6 +69,11 @@ static void ParseOptions (Application app, Mono.Options.OptionSet options, strin options.Add ("rid=", "The runtime identifier we're building for", v => { app.RuntimeIdentifier = v; }); + options.Add ("xcode-version=", "The Xcode version we're building with", v => { + if (!Version.TryParse (v, out var xcodeVersion)) + throw ErrorHelper.CreateError (26, Errors.MX0026, $"xcode-version:{v}", "Expected a valid version string."); + Driver.XcodeVersion = xcodeVersion; + }); try { app.RootAssemblies.AddRange (options.Parse (args)); @@ -230,7 +235,12 @@ public static string? XcodeProductVersion { static Version? xcode_version; public static Version XcodeVersion { get { - return xcode_version!; + if (xcode_version is null) + throw ErrorHelper.CreateError (99, Errors.MX0099, "The Xcode version has not been configured. Pass --xcode-version or configure an Xcode installation."); + return xcode_version; + } + set { + xcode_version = value; } } diff --git a/tools/common/FileCopier.cs b/tools/common/FileCopier.cs index 98a8e1a27f5d..06d1176453bc 100644 --- a/tools/common/FileCopier.cs +++ b/tools/common/FileCopier.cs @@ -394,7 +394,7 @@ static bool IsUptodate (IEnumerable sources, IEnumerable targets return true; } - [DllImport ("/usr/lib/libSystem.dylib", SetLastError = true, EntryPoint = "strerror")] + [DllImport ("libc", SetLastError = true, EntryPoint = "strerror")] static extern IntPtr _strerror (int errno); internal static string strerror (int errno) diff --git a/tools/common/PathUtils.cs b/tools/common/PathUtils.cs index 19dcc31df8fe..c1675692caef 100644 --- a/tools/common/PathUtils.cs +++ b/tools/common/PathUtils.cs @@ -38,7 +38,7 @@ static char ToOrdinalIgnoreCase (char c) return path; } - [DllImport ("/usr/lib/libc.dylib")] + [DllImport ("libc")] static extern IntPtr realpath (string path, IntPtr buffer); #if NET diff --git a/tools/common/Target.cs b/tools/common/Target.cs index 021457e59cba..5ba0f42feb8a 100644 --- a/tools/common/Target.cs +++ b/tools/common/Target.cs @@ -64,7 +64,7 @@ public void ExtractNativeLinkInfo (List exceptions) } #endif // !LEGACY_TOOLS - [DllImport (Constants.libSystemLibrary, SetLastError = true)] + [DllImport ("libc", SetLastError = true)] static extern string realpath (string path, IntPtr zero); public static string GetRealPath (string path, bool warnIfNoSuchPathExists = true) diff --git a/tools/mtouch/Makefile b/tools/mtouch/Makefile index 0ad70dd239a7..d48cc4bd1eaf 100644 --- a/tools/mtouch/Makefile +++ b/tools/mtouch/Makefile @@ -35,18 +35,21 @@ $(abspath Constants.cs): Constants.cs.in Makefile $(TOP)/Make.config.inc # define RunRegistrar .libs/Microsoft.$(9).registrar.$(10)%m .libs/Microsoft.$(9).registrar.$(10)%h: $(TOP)/src/build/dotnet/$(1)/$(3)/Microsoft.$(9).dll $(LOCAL_MTOUCH) | .libs - $$(Q_GEN) $$(LOCAL_MTOUCH_COMMAND) $$(MTOUCH_VERBOSITY) --runregistrar:$$(abspath $$(basename $$@).m) --sdkroot $$(XCODE_DEVELOPER_ROOT) --sdk $(4) $$< --target-framework .NETCoreApp,Version=$(subst net,,$(DOTNET_TFM)),Profile=$(1) --abi $(2) --reference:$(DOTNET_BCL_DIR)/System.Runtime.dll --reference:$(DOTNET_BCL_DIR)/System.Runtime.InteropServices.dll --rid $(10) + $$(Q_GEN) $$(LOCAL_MTOUCH_COMMAND) $$(MTOUCH_VERBOSITY) --runregistrar:$$(abspath $$(basename $$@).m) --xcode-version $$(XCODE_VERSION) --sdk $(4) $$< --target-framework .NETCoreApp,Version=$(subst net,,$(DOTNET_TFM)),Profile=$(1) --abi $(2) --reference:$(DOTNET_BCL_DIR)/System.Runtime.dll --reference:$(DOTNET_BCL_DIR)/System.Runtime.InteropServices.dll --rid $(10) $$(Q) touch $$(basename $$@).m $$(basename $$@).h .libs/Microsoft.$(9).registrar.$(10).a: .libs/Microsoft.$(9).registrar.$(10).m .libs/Microsoft.$(9).registrar.$(10).h | .libs $$(Q_CC) $$(CLANG) -DDEBUG -g -gdwarf-2 $(6) -stdlib=libc++ -std=c++14 -x objective-c++ -o $$@ -c $$< -Wall -Wno-unguarded-availability-new -I$(TOP)/runtime .libs/Microsoft.$(9).registrar.coreclr.$(10)%m .libs/Microsoft.$(9).registrar.coreclr.$(10)%h: $(TOP)/src/build/dotnet/$(1)/$(3)/Microsoft.$(9).dll $(LOCAL_MTOUCH) | .libs - $$(Q_GEN) $$(LOCAL_MTOUCH_COMMAND) $$(MTOUCH_VERBOSITY) --runregistrar:$$(abspath $$(basename $$@).m) --sdkroot $$(XCODE_DEVELOPER_ROOT) --sdk $(4) $$< --target-framework .NETCoreApp,Version=$(subst net,,$(DOTNET_TFM)),Profile=$(1) --abi $(2) --reference:$(DOTNET_BCL_DIR)/System.Runtime.dll --reference:$(DOTNET_BCL_DIR)/System.Runtime.InteropServices.dll --rid $(10) --xamarin-runtime CoreCLR + $$(Q_GEN) $$(LOCAL_MTOUCH_COMMAND) $$(MTOUCH_VERBOSITY) --runregistrar:$$(abspath $$(basename $$@).m) --xcode-version $$(XCODE_VERSION) --sdk $(4) $$< --target-framework .NETCoreApp,Version=$(subst net,,$(DOTNET_TFM)),Profile=$(1) --abi $(2) --reference:$(DOTNET_BCL_DIR)/System.Runtime.dll --reference:$(DOTNET_BCL_DIR)/System.Runtime.InteropServices.dll --rid $(10) --xamarin-runtime CoreCLR $$(Q) touch $$(basename $$@).m $$(basename $$@).h .libs/Microsoft.$(9).registrar.coreclr.$(10).a: .libs/Microsoft.$(9).registrar.coreclr.$(10).m .libs/Microsoft.$(9).registrar.$(10).h | .libs $$(Q_CC) $$(CLANG) -DDEBUG -g -gdwarf-2 $(6) -stdlib=libc++ -std=c++14 -x objective-c++ -o $$@ -c $$< -Wall -Wno-unguarded-availability-new -I$(TOP)/runtime + +no-xcode-build:: .libs/Microsoft.$(9).registrar.$(10).m .libs/Microsoft.$(9).registrar.$(10).h +no-xcode-build:: .libs/Microsoft.$(9).registrar.coreclr.$(10).m .libs/Microsoft.$(9).registrar.$(10).h endef $(eval $(call RunRegistrar,ios,x86_64,64,$(IOS_SDK_VERSION),iOS,$(iossimulator-x64_CFLAGS),,,iOS,iossimulator-x64)) $(eval $(call RunRegistrar,ios,arm64,64,$(IOS_SDK_VERSION),iOS,$(iossimulator-arm64_CFLAGS),,,iOS,iossimulator-arm64)) @@ -86,12 +89,15 @@ endef $(foreach platform,$(DOTNET_PLATFORMS_MTOUCH),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(eval $(call InstallRegistrar,$(platform),$(rid))))) -ifndef NO_XCODE +ifdef NO_XCODE +dotnet:: no-xcode-build +else dotnet: $(TARGETS_DOTNET) -install-local:: $(TARGETS_DOTNET) -all-local:: $(TARGETS_DOTNET) endif +install-local:: dotnet +all-local:: dotnet + clean-local:: rm -Rf bin obj diff --git a/tools/mtouch/mtouch.cs b/tools/mtouch/mtouch.cs index 21349adc0aaf..42ae6901f1a0 100644 --- a/tools/mtouch/mtouch.cs +++ b/tools/mtouch/mtouch.cs @@ -22,8 +22,6 @@ static int Main2 (string [] args) var os = new OptionSet (); ParseOptions (app, os, args); - ValidateXcode (app, false, false); - app.InitializeCommon (); app.RunRegistrar (); From c56223b22f6b93da9212c06b572e46b8b9dfa076 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 12:18:20 +0000 Subject: [PATCH 04/97] [main] Update dependencies from dotnet/macios (#25499) This pull request updates the following dependencies ## From https://github.com/dotnet/macios - **Subscription**: [c0371266-dd6f-4959-822b-decc72d2d668](https://maestro.dot.net/subscriptions?search=c0371266-dd6f-4959-822b-decc72d2d668) - **Build**: [20260521.4](https://dev.azure.com/devdiv/DevDiv/_build/results?buildId=14158846) ([315351](https://maestro.dot.net/channel/3884/github:dotnet:macios/build/315351)) - **Date Produced**: May 21, 2026 2:11:24 PM UTC - **Commit**: [5a29bbfbaac5941a8b229c91f1b606081b49ea10](https://github.com/dotnet/macios/commit/5a29bbfbaac5941a8b229c91f1b606081b49ea10) - **Branch**: [release/9.0.1xx](https://github.com/dotnet/macios/tree/release/9.0.1xx) - **Dependency Updates**: - From [26.5.9002 to 26.5.9003][1] - Microsoft.iOS.Sdk.net9.0_26.5 - Microsoft.MacCatalyst.Sdk.net9.0_26.5 - Microsoft.macOS.Sdk.net9.0_26.5 - Microsoft.tvOS.Sdk.net9.0_26.5 [1]: https://github.com/dotnet/macios/compare/7075bb3cf9...5a29bbfbaa --- NuGet.config | 3 +-- eng/Version.Details.props | 8 ++++---- eng/Version.Details.xml | 16 ++++++++-------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/NuGet.config b/NuGet.config index 03a2d9eabbfd..31b55049d0f8 100644 --- a/NuGet.config +++ b/NuGet.config @@ -13,8 +13,7 @@ - - + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index b44948c83ce7..2580fca25dba 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -19,16 +19,16 @@ This file should be imported by eng/Versions.props 26.0.11017 18.5.9227 - 26.5.9002 + 26.5.9003 26.0.11017 18.5.9227 - 26.5.9002 + 26.5.9003 26.0.11017 15.5.9227 - 26.5.9002 + 26.5.9003 26.0.11017 18.5.9227 - 26.5.9002 + 26.5.9003 11.0.0-prerelease.26264.1 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c23401f2a72e..21e29be673c0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -60,21 +60,21 @@ 797d30720e5e629d23eb146935da94cb1b61047e - + https://github.com/dotnet/macios - 7075bb3cf977326a4e0a01108b748478ce08091e + 5a29bbfbaac5941a8b229c91f1b606081b49ea10 - + https://github.com/dotnet/macios - 7075bb3cf977326a4e0a01108b748478ce08091e + 5a29bbfbaac5941a8b229c91f1b606081b49ea10 - + https://github.com/dotnet/macios - 7075bb3cf977326a4e0a01108b748478ce08091e + 5a29bbfbaac5941a8b229c91f1b606081b49ea10 - + https://github.com/dotnet/macios - 7075bb3cf977326a4e0a01108b748478ce08091e + 5a29bbfbaac5941a8b229c91f1b606081b49ea10 From 97204e747fcc6d5a9ccc55957b90b330aa92bf44 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 22 May 2026 16:11:37 +0200 Subject: [PATCH 05/97] Fix 'dotnet checkout' typos in docs/guides/HowToBranch.md (#25500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Lines 23-24 of `docs/guides/HowToBranch.md` incorrectly use `dotnet checkout` instead of `git checkout`. The `dotnet` CLI has no `checkout` subcommand, so anyone following the guide literally would get an error. Later commands in the same document correctly use `git`, confirming this was a typo. ## Changes - Line 23: `$ dotnet checkout net10.0` → `$ git checkout net10.0` - Line 24: `$ dotnet checkout -b release/10.0.1xx-preview42` → `$ git checkout -b release/10.0.1xx-preview42` Markdown-only change — no build or test impact. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/guides/HowToBranch.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/HowToBranch.md b/docs/guides/HowToBranch.md index ec95a74c03aa..46048712427c 100644 --- a/docs/guides/HowToBranch.md +++ b/docs/guides/HowToBranch.md @@ -20,8 +20,8 @@ sequence of events would be: 3. `dotnet/macios` branches `release/10.0.1xx-preview42` from `net10.0`: ```shell - $ dotnet checkout net10.0 - $ dotnet checkout -b release/10.0.1xx-preview42 + $ git checkout net10.0 + $ git checkout -b release/10.0.1xx-preview42 ``` Note that release candidates will use values such as `rc.1`, `rc.2`, etc. From 3c6edceb8270b34f44f7f21b93bcfd7430a5f27e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 22 May 2026 17:08:42 +0200 Subject: [PATCH 06/97] [tests] Upgrade remaining tests to NUnit v4 Assert.That syntax. (#25501) Migrate all remaining classic assertions (AreEqual, IsTrue, IsNull, etc.) to Assert.That constraint syntax. --- Directory.Build.props | 2 + tests/BundledResources/ResourcesTest.cs | 6 +- tests/EmbeddedResources/ResourcesTest.cs | 14 +- tests/bindings-test/ProtocolTest.cs | 90 +++--- tests/bindings-test/RegistrarBindingTest.cs | 36 +-- tests/bindings-test/RuntimeTest.cs | 16 +- tests/bindings-test2/BindingTest.cs | 4 +- tests/common/Assert.cs | 2 +- tests/common/AssertHelpers.cs | 2 +- tests/common/BundlerTool.cs | 2 +- tests/common/ProductTests.cs | 4 +- tests/common/TestRuntime.cs | 4 +- .../Touch.Client/Runner/TestResultElement.cs | 2 +- .../Touch.Client/Runner/TouchRunner.cs | 12 +- .../Touch.Client/dotnet/shared.csproj | 4 +- tests/common/shared-dotnet.csproj | 2 +- tests/framework-test/FrameworkTests.cs | 8 +- tests/fsharp/FSharpTests.fs | 2 +- tests/interdependent-binding-projects/Main.cs | 2 +- tests/test-libraries/testgenerator.cs | 274 +++++++++--------- tests/xcframework-test/XCFrameworkTests.cs | 4 +- tests/xtro-sharpie/UnitTests/UnitTests.csproj | 6 +- tests/xtro-sharpie/UnitTests/Xtro.cs | 2 +- 23 files changed, 252 insertions(+), 248 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index d4a1086d9d07..f054d696a5d3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -22,6 +22,8 @@ 6.1.0 4.7.0 4.4.0 + 3.12.0 + 3.6.0 latest diff --git a/tests/BundledResources/ResourcesTest.cs b/tests/BundledResources/ResourcesTest.cs index e22cdc7ec85e..573102ee7133 100644 --- a/tests/BundledResources/ResourcesTest.cs +++ b/tests/BundledResources/ResourcesTest.cs @@ -22,9 +22,9 @@ public void Bundled () // files are extracted (by MonoDevelop) so we can see them in the file system // that's true for simulator or devices and whatever the linker settings are var dir = NSBundle.MainBundle.ResourcePath!; - Assert.True (File.Exists (Path.Combine (dir, "basn3p08.png")), "file-basn3p08.png"); - Assert.True (File.Exists (Path.Combine (dir, "basn3p08_with_loc.png")), "file-basn3p08_with_loc.png"); - Assert.True (File.Exists (Path.Combine (dir, "xamvideotest.mp4")), "xamvideotest.mp4"); + Assert.That (File.Exists (Path.Combine (dir, "basn3p08.png")), Is.True, "file-basn3p08.png"); + Assert.That (File.Exists (Path.Combine (dir, "basn3p08_with_loc.png")), Is.True, "file-basn3p08_with_loc.png"); + Assert.That (File.Exists (Path.Combine (dir, "xamvideotest.mp4")), Is.True, "xamvideotest.mp4"); // resources are removed by the linker or an extra step (e.g. "link sdk" or "don't link") but that // extra step is done only on device (to keep the simulator builds as fast as possible) diff --git a/tests/EmbeddedResources/ResourcesTest.cs b/tests/EmbeddedResources/ResourcesTest.cs index 04f35a6856e8..e8c83f5d9b08 100644 --- a/tests/EmbeddedResources/ResourcesTest.cs +++ b/tests/EmbeddedResources/ResourcesTest.cs @@ -23,13 +23,13 @@ public class ResourcesTest { public void Embedded () { var manager = new ResourceManager ("EmbeddedResources.Welcome", typeof (ResourcesTest).Assembly); - Assert.AreEqual ("Welcome", manager.GetString ("String1", new CultureInfo ("en")), "en"); - Assert.AreEqual ("G'day", manager.GetString ("String1", new CultureInfo ("en-AU")), "en-AU"); - Assert.AreEqual ("Willkommen", manager.GetString ("String1", new CultureInfo ("de")), "de"); - Assert.AreEqual ("Willkommen", manager.GetString ("String1", new CultureInfo ("de-DE")), "de-DE"); - Assert.AreEqual ("Bienvenido", manager.GetString ("String1", new CultureInfo ("es")), "es"); - Assert.AreEqual ("Bienvenido", manager.GetString ("String1", new CultureInfo ("es-AR")), "es-AR"); - Assert.AreEqual ("Bienvenido", manager.GetString ("String1", new CultureInfo ("es-ES")), "es-ES"); + Assert.That (manager.GetString ("String1", new CultureInfo ("en")), Is.EqualTo ("Welcome"), "en"); + Assert.That (manager.GetString ("String1", new CultureInfo ("en-AU")), Is.EqualTo ("G'day"), "en-AU"); + Assert.That (manager.GetString ("String1", new CultureInfo ("de")), Is.EqualTo ("Willkommen"), "de"); + Assert.That (manager.GetString ("String1", new CultureInfo ("de-DE")), Is.EqualTo ("Willkommen"), "de-DE"); + Assert.That (manager.GetString ("String1", new CultureInfo ("es")), Is.EqualTo ("Bienvenido"), "es"); + Assert.That (manager.GetString ("String1", new CultureInfo ("es-AR")), Is.EqualTo ("Bienvenido"), "es-AR"); + Assert.That (manager.GetString ("String1", new CultureInfo ("es-ES")), Is.EqualTo ("Bienvenido"), "es-ES"); } } } diff --git a/tests/bindings-test/ProtocolTest.cs b/tests/bindings-test/ProtocolTest.cs index b9d2ae0495e9..f94355ccc85f 100644 --- a/tests/bindings-test/ProtocolTest.cs +++ b/tests/bindings-test/ProtocolTest.cs @@ -46,23 +46,23 @@ public void Constructors () using var dateNow = (NSDate) DateTime.Now; using (var obj = IConstructorProtocol.CreateInstance ("Hello world")!) { - Assert.AreEqual ("Hello world", obj.StringValue, "A StringValue"); - Assert.IsNull (obj.DateValue, "A DateValue"); + Assert.That (obj.StringValue, Is.EqualTo ("Hello world"), "A StringValue"); + Assert.That (obj.DateValue, Is.Null, "A DateValue"); } using (var obj = IConstructorProtocol.CreateInstance (dateNow)!) { - Assert.IsNull (obj.StringValue, "B StringValue"); - Assert.AreEqual (dateNow, obj.DateValue, "B DateValue"); + Assert.That (obj.StringValue, Is.Null, "B StringValue"); + Assert.That (obj.DateValue, Is.EqualTo (dateNow), "B DateValue"); } using (var obj = IConstructorProtocol.CreateInstance ("Hello Subclassed")!) { - Assert.AreEqual ("Hello Subclassed", obj.StringValue, "C1 StringValue"); - Assert.IsNull (obj.DateValue, "C1 DateValue"); + Assert.That (obj.StringValue, Is.EqualTo ("Hello Subclassed"), "C1 StringValue"); + Assert.That (obj.DateValue, Is.Null, "C1 DateValue"); } using (var obj = IConstructorProtocol.CreateInstance (dateNow)!) { - Assert.IsNull (obj.StringValue, "C2 StringValue"); - Assert.AreEqual (dateNow, obj.DateValue, "C2 DateValue"); + Assert.That (obj.StringValue, Is.Null, "C2 StringValue"); + Assert.That (obj.DateValue, Is.EqualTo (dateNow), "C2 DateValue"); } if (global::XamarinTests.ObjCRuntime.Registrar.IsDynamicRegistrar) { @@ -71,8 +71,8 @@ public void Constructors () }, "D1 Exception"); } else { using (var obj = IConstructorProtocol.CreateInstance ("Hello Subclassed 2")!) { - Assert.AreEqual ("Managed interceptor! Hello Subclassed 2", obj.StringValue, "D1 StringValue"); - Assert.IsNull (obj.DateValue, "D1 DateValue"); + Assert.That (obj.StringValue, Is.EqualTo ("Managed interceptor! Hello Subclassed 2"), "D1 StringValue"); + Assert.That (obj.DateValue, Is.Null, "D1 DateValue"); } } @@ -82,8 +82,8 @@ public void Constructors () }, "D2 Exception"); } else { using (var obj = IConstructorProtocol.CreateInstance (dateNow)!) { - Assert.IsNull (obj.StringValue, "D2 StringValue"); - Assert.AreEqual (dateNow.AddSeconds (42), obj.DateValue, "D2 DateValue"); + Assert.That (obj.StringValue, Is.Null, "D2 StringValue"); + Assert.That (obj.DateValue, Is.EqualTo (dateNow.AddSeconds (42)), "D2 DateValue"); } } } @@ -118,27 +118,27 @@ public void OnlyProtocol () // the interface must be created var IP1 = bindingAssembly.GetType ("Bindings.Test.Protocol.IP1")!; - Assert.IsNotNull (IP1, "IP1"); + Assert.That (IP1, Is.Not.Null, "IP1"); // with a [Protocol] attribute var IP1Attributes = IP1.GetCustomAttributes (typeof (ProtocolAttribute), false); if (HasProtocolAttributes) { - Assert.AreEqual (1, IP1Attributes.Length, "[Protocol] IP1"); + Assert.That (IP1Attributes.Length, Is.EqualTo (1), "[Protocol] IP1"); var IP1Protocol = (ProtocolAttribute) IP1Attributes [0]; - Assert.AreEqual ("P1", IP1Protocol.Name, "Name"); + Assert.That (IP1Protocol.Name, Is.EqualTo ("P1"), "Name"); // and a wrapper type var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P1Wrapper"); - Assert.IsNotNull (wrapperType, "P1_Wrapper"); - Assert.AreEqual (wrapperType, IP1Protocol.WrapperType, "WrapperType"); + Assert.That (wrapperType, Is.Not.Null, "P1_Wrapper"); + Assert.That (IP1Protocol.WrapperType, Is.EqualTo (wrapperType), "WrapperType"); } else { - Assert.AreEqual (0, IP1Attributes.Length, "[Protocol] IP1"); + Assert.That (IP1Attributes.Length, Is.EqualTo (0), "[Protocol] IP1"); // and a wrapper type var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P1Wrapper"); - Assert.IsNotNull (wrapperType, "P1_Wrapper"); + Assert.That (wrapperType, Is.Not.Null, "P1_Wrapper"); } // but not the model - Assert.IsNull (bindingAssembly.GetType ("Bindings.Test.Protocol.P1"), "P1"); + Assert.That (bindingAssembly.GetType ("Bindings.Test.Protocol.P1"), Is.Null, "P1"); } [Test] @@ -150,32 +150,32 @@ public void ProtocolWithBaseType () // the interface must be created var IP2 = bindingAssembly.GetType ("Bindings.Test.Protocol.IP2")!; - Assert.IsNotNull (IP2, "IP2"); + Assert.That (IP2, Is.Not.Null, "IP2"); // with a [Protocol] attribute var IP2Attributes = IP2.GetCustomAttributes (typeof (ProtocolAttribute), false); if (HasProtocolAttributes) { - Assert.AreEqual (1, IP2Attributes.Length, "[Protocol] IP2"); + Assert.That (IP2Attributes.Length, Is.EqualTo (1), "[Protocol] IP2"); var IP2Protocol = (ProtocolAttribute) IP2Attributes [0]; - Assert.AreEqual ("P2", IP2Protocol.Name, "Name"); + Assert.That (IP2Protocol.Name, Is.EqualTo ("P2"), "Name"); // and a wrapper type var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P2Wrapper"); - Assert.IsNotNull (wrapperType, "P2_Wrapper"); - Assert.AreEqual (wrapperType, IP2Protocol.WrapperType, "WrapperType"); + Assert.That (wrapperType, Is.Not.Null, "P2_Wrapper"); + Assert.That (IP2Protocol.WrapperType, Is.EqualTo (wrapperType), "WrapperType"); } else { - Assert.AreEqual (0, IP2Attributes.Length, "[Protocol] IP2"); + Assert.That (IP2Attributes.Length, Is.EqualTo (0), "[Protocol] IP2"); // and a wrapper type var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P2Wrapper"); - Assert.IsNotNull (wrapperType, "P2_Wrapper"); + Assert.That (wrapperType, Is.Not.Null, "P2_Wrapper"); } // and a model-like class var model = bindingAssembly.GetType ("Bindings.Test.Protocol.P2")!; - Assert.IsNotNull (model, "P2"); + Assert.That (model, Is.Not.Null, "P2"); // but without the [Model] attribute - Assert.False (model.IsDefined (typeof (ModelAttribute), false), "model"); + Assert.That (model.IsDefined (typeof (ModelAttribute), false), Is.False, "model"); } [Test] @@ -187,32 +187,32 @@ public void ProtocolWithBaseTypeAndModel () // the interface must be created var IP3 = bindingAssembly.GetType ("Bindings.Test.Protocol.IP3")!; - Assert.IsNotNull (IP3, "IP3"); + Assert.That (IP3, Is.Not.Null, "IP3"); // with a [Protocol] attribute var IP3Attributes = IP3.GetCustomAttributes (typeof (ProtocolAttribute), false); if (HasProtocolAttributes) { - Assert.AreEqual (1, IP3Attributes.Length, "[Protocol] IP3"); + Assert.That (IP3Attributes.Length, Is.EqualTo (1), "[Protocol] IP3"); var IP3Protocol = (ProtocolAttribute) IP3Attributes [0]; - Assert.AreEqual ("P3", IP3Protocol.Name, "Name"); + Assert.That (IP3Protocol.Name, Is.EqualTo ("P3"), "Name"); // and a wrapper type var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P3Wrapper"); - Assert.IsNotNull (wrapperType, "P3_Wrapper"); - Assert.AreEqual (wrapperType, IP3Protocol.WrapperType, "WrapperType"); + Assert.That (wrapperType, Is.Not.Null, "P3_Wrapper"); + Assert.That (IP3Protocol.WrapperType, Is.EqualTo (wrapperType), "WrapperType"); } else { - Assert.AreEqual (0, IP3Attributes.Length, "[Protocol] IP3"); + Assert.That (IP3Attributes.Length, Is.EqualTo (0), "[Protocol] IP3"); // and a wrapper type var wrapperType = bindingAssembly.GetType ("Bindings.Test.Protocol.P3Wrapper"); - Assert.IsNotNull (wrapperType, "P3_Wrapper"); + Assert.That (wrapperType, Is.Not.Null, "P3_Wrapper"); } // and a model class var model = bindingAssembly.GetType ("Bindings.Test.Protocol.P3")!; - Assert.IsNotNull (model, "P3"); + Assert.That (model, Is.Not.Null, "P3"); // with a [Model] attribute - Assert.True (model.IsDefined (typeof (ModelAttribute), false), "model"); + Assert.That (model.IsDefined (typeof (ModelAttribute), false), Is.True, "model"); } class MembersImplementation : NSObject, Bindings.Test.Protocol.IMemberAttributes { @@ -241,14 +241,14 @@ void CleanupSignatures (objc_method_description [] methods) public void ProtocolMembers () { IntPtr protocol = objc_getProtocol ("MemberAttributes"); - Assert.AreNotEqual (IntPtr.Zero, protocol, "a"); + Assert.That (protocol, Is.Not.EqualTo (IntPtr.Zero), "a"); objc_method_description [] methods; // Required instance methods methods = protocol_copyMethodDescriptionList (protocol, true, true); CleanupSignatures (methods); - Assert.AreEqual (4, methods.Length, "Required Instance Methods: Count"); + Assert.That (methods.Length, Is.EqualTo (4), "Required Instance Methods: Count"); AssertContains (methods, new objc_method_description ("requiredInstanceMethod", "v@:"), "Required Instance Methods: requiredInstanceMethod"); AssertContains (methods, new objc_method_description ("requiredInstanceProperty", "@@:"), "Required Instance Methods: requiredInstanceProperty"); AssertContains (methods, new objc_method_description ("setRequiredInstanceProperty:", "v@:@"), "Required Instance Methods: setRequiredInstanceProperty"); @@ -257,7 +257,7 @@ public void ProtocolMembers () // Required static methods methods = protocol_copyMethodDescriptionList (protocol, true, false); CleanupSignatures (methods); - Assert.AreEqual (3, methods.Length, "Required Static Methods: Count"); + Assert.That (methods.Length, Is.EqualTo (3), "Required Static Methods: Count"); AssertContains (methods, new objc_method_description ("requiredStaticMethod", "v@:"), "Required Static Methods: requiredStaticMethod"); AssertContains (methods, new objc_method_description ("setRequiredStaticProperty:", "v@:@"), "Required Static Methods: setRequiredStaticProperty:"); AssertContains (methods, new objc_method_description ("requiredStaticProperty", "@@:"), "Required Static Methods: requiredStaticProperty"); @@ -265,7 +265,7 @@ public void ProtocolMembers () // Optional instance methods methods = protocol_copyMethodDescriptionList (protocol, false, true); CleanupSignatures (methods); - Assert.AreEqual (19, methods.Length, "Optional Instance Methods: Count"); + Assert.That (methods.Length, Is.EqualTo (19), "Optional Instance Methods: Count"); AssertContains (methods, new objc_method_description ("variadicMethod:", "v@:^v"), "Optional Instance Methods: variadicMethod:"); AssertContains (methods, new objc_method_description ("methodWithReturnType", "@@:"), "Optional Instance Methods: methodWithReturnType"); AssertContains (methods, new objc_method_description ("methodWithParameter:", "v@:i"), "Optional Instance Methods: methodWithParameter:"); @@ -289,7 +289,7 @@ public void ProtocolMembers () // Optional static methods methods = protocol_copyMethodDescriptionList (protocol, false, false); CleanupSignatures (methods); - Assert.AreEqual (3, methods.Length, "Optional Static Methods: Count"); + Assert.That (methods.Length, Is.EqualTo (3), "Optional Static Methods: Count"); AssertContains (methods, new objc_method_description ("optionalStaticMethod", "v@:"), "Optional Static Methods: optionalStaticMethod"); AssertContains (methods, new objc_method_description ("optionalStaticProperty", "@@:"), "Optional Static Methods: optionalStaticProperty"); AssertContains (methods, new objc_method_description ("setOptionalStaticProperty:", "v@:@"), "Optional Static Methods: setOptionalStaticProperty:"); @@ -301,9 +301,9 @@ public void ProtocolMembers () // see file objc4-647/runtime/objc-runtime-old.mm in Apple's open source code), // so we need to verify differently for the dynamic registrar. if (XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) { - Assert.AreEqual (9, properties.Length, "Properties: Count"); + Assert.That (properties.Length, Is.EqualTo (9), "Properties: Count"); } else { - Assert.AreEqual (2, properties.Length, "Properties: Count"); + Assert.That (properties.Length, Is.EqualTo (2), "Properties: Count"); } AssertContains (properties, new objc_property ("requiredInstanceProperty", "T@\"NSString\",N", new objc_property_attribute [] { diff --git a/tests/bindings-test/RegistrarBindingTest.cs b/tests/bindings-test/RegistrarBindingTest.cs index 93bba2d9c9e2..fb3cf6821c83 100644 --- a/tests/bindings-test/RegistrarBindingTest.cs +++ b/tests/bindings-test/RegistrarBindingTest.cs @@ -57,7 +57,7 @@ public static void OptionalStaticCallback (Action completionHandler) public Action RequiredReturnValue () { return new Action ((v) => { - Assert.AreEqual (42, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (42), "RequiredReturnValue"); }); } @@ -65,7 +65,7 @@ public Action RequiredReturnValue () public Action OptionalReturnValue () { return new Action ((v) => { - Assert.AreEqual (42, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (42), "RequiredReturnValue"); }); } @@ -73,7 +73,7 @@ public Action OptionalReturnValue () public static Action RequiredStaticReturnValue () { return new Action ((v) => { - Assert.AreEqual (42, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (42), "RequiredReturnValue"); }); } @@ -81,7 +81,7 @@ public static Action RequiredStaticReturnValue () public static Action OptionalStaticReturnValue () { return new Action ((v) => { - Assert.AreEqual (42, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (42), "RequiredReturnValue"); }); } } @@ -99,10 +99,10 @@ public void DerivedClassBlockCallback () ObjCBlockTester.CallOptionalStaticCallback (); DerivedBlockCallbackClass.Answer = 2; - Assert.IsFalse (obj.InvokeNullableCallbackNatively (null), "NullableCallback A rv"); + Assert.That (obj.InvokeNullableCallbackNatively (null), Is.False, "NullableCallback A rv"); int nullableResult = -1; - Assert.IsTrue (obj.InvokeNullableCallbackNatively ((v) => nullableResult = v), "NullableCallback B rv"); - Assert.AreEqual (24, nullableResult, "NullableCallback result"); + Assert.That (obj.InvokeNullableCallbackNatively ((v) => nullableResult = v), Is.True, "NullableCallback B rv"); + Assert.That (nullableResult, Is.EqualTo (24), "NullableCallback result"); } } @@ -141,7 +141,7 @@ public static void OptionalStaticCallback (Action completionHandler) public override Action RequiredReturnValue () { return new Action ((v) => { - Assert.AreEqual (Answer, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (Answer), "RequiredReturnValue"); }); } @@ -150,7 +150,7 @@ public Action OptionalReturnValue () { return new Action ((v) => { Console.WriteLine ("OptionalReturnValue"); - Assert.AreEqual (Answer, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (Answer), "RequiredReturnValue"); }); } @@ -158,7 +158,7 @@ public Action OptionalReturnValue () public static Action RequiredStaticReturnValue () { return new Action ((v) => { - Assert.AreEqual (Answer, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (Answer), "RequiredReturnValue"); }); } @@ -167,7 +167,7 @@ public static Action OptionalStaticReturnValue () { return new Action ((v) => { Console.WriteLine ("OptionalStaticReturnValue"); - Assert.AreEqual (Answer, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (Answer), "RequiredReturnValue"); }); } } @@ -201,7 +201,7 @@ public static void OptionalRequiredCallback (Action completionHandler) Action IObjCProtocolBlockTest.RequiredReturnValue () { return new Action ((v) => { - Assert.AreEqual (42, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (42), "RequiredReturnValue"); }); } @@ -209,7 +209,7 @@ Action IObjCProtocolBlockTest.RequiredReturnValue () public Action OptionalReturnValue () { return new Action ((v) => { - Assert.AreEqual (42, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (42), "RequiredReturnValue"); }); } @@ -217,7 +217,7 @@ public Action OptionalReturnValue () public static Action RequiredStaticReturnValue () { return new Action ((v) => { - Assert.AreEqual (42, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (42), "RequiredReturnValue"); }); } @@ -225,7 +225,7 @@ public static Action RequiredStaticReturnValue () public static Action OptionalStaticReturnValue () { return new Action ((v) => { - Assert.AreEqual (42, v, "RequiredReturnValue"); + Assert.That (v, Is.EqualTo (42), "RequiredReturnValue"); }); } } @@ -311,7 +311,7 @@ public void ProtocolWithBlockProperties (bool required, bool instance) } } ObjCBlockTester.CallProtocolWithBlockProperties (pb, required, instance); - Assert.IsTrue (callbackCalled, "Callback"); + Assert.That (callbackCalled, Is.True, "Callback"); } } @@ -338,7 +338,7 @@ public void ProtocolWithNativeBlockProperties (bool required, bool instance) PropertyBlock.MyOptionalStaticProperty! (); } } - Assert.AreEqual (calledCounter + 1, ObjCBlockTester.CalledBlockCount, "Blocks called"); + Assert.That (ObjCBlockTester.CalledBlockCount, Is.EqualTo (calledCounter + 1), "Blocks called"); } } [Test] @@ -372,7 +372,7 @@ public void LinkedAway (bool required, bool instance) if (re.Code == 8009) { Assert.That (re.Message, Does.StartWith ("Unable to locate the block to delegate conversion method for the method Xamarin.BindingTests.RegistrarBindingTest+FakePropertyBlock.set_"), re.Message, "Message"); } else { - Assert.AreEqual ("The runtime function get_block_wrapper_creator has been linked away.", re.Message, "Message"); + Assert.That (re.Message, Is.EqualTo ("The runtime function get_block_wrapper_creator has been linked away."), "Message"); } } } diff --git a/tests/bindings-test/RuntimeTest.cs b/tests/bindings-test/RuntimeTest.cs index aea647585f60..f445ec7bca9e 100644 --- a/tests/bindings-test/RuntimeTest.cs +++ b/tests/bindings-test/RuntimeTest.cs @@ -9,7 +9,7 @@ public class RuntimeTest { [Test] public void GlobalStringTest () { - Assert.AreEqual ("There's nothing cruvus here!", (string) Globals.GlobalString, "Global string"); + Assert.That ((string) Globals.GlobalString, Is.EqualTo ("There's nothing cruvus here!"), "Global string"); } [Test] @@ -95,7 +95,7 @@ public void SwiftTest () { TestRuntime.AssertXcodeVersion (13, 0); using var obj = new SwiftTestClass (); - Assert.AreEqual ("Hello from Swift", obj.SayHello (), "Hello"); + Assert.That (obj.SayHello (), Is.EqualTo ("Hello from Swift"), "Hello"); } [Test] @@ -105,18 +105,18 @@ public void SwiftTypeEncodings () using var obj = new SwiftTestClass (); - Assert.AreEqual ("42", obj.DoSomething ("42"), "DoSomething"); + Assert.That (obj.DoSomething ("42"), Is.EqualTo ("42"), "DoSomething"); string? asyncResult = null; obj.DoSomethingAsync ("dolphins", (v) => asyncResult = v); var done = TestRuntime.RunAsync (TimeSpan.FromSeconds (5), () => asyncResult is not null); - Assert.AreEqual ("dolphins", asyncResult, "DoSomethingAsync"); - Assert.IsTrue (done, "Done"); + Assert.That (asyncResult, Is.EqualTo ("dolphins"), "DoSomethingAsync"); + Assert.That (done, Is.True, "Done"); obj.DoSomethingComplexAsync ("fish", IntPtr.Zero, (v) => asyncResult = v); done = TestRuntime.RunAsync (TimeSpan.FromSeconds (5), () => asyncResult is not null); - Assert.AreEqual ("fish", asyncResult, "DoSomethingComplexAsync"); - Assert.IsTrue (done, "Done 2"); + Assert.That (asyncResult, Is.EqualTo ("fish"), "DoSomethingComplexAsync"); + Assert.That (done, Is.True, "Done 2"); } [Test] @@ -124,7 +124,7 @@ public void SwiftTestClass2 () { TestRuntime.AssertXcodeVersion (13, 0); using var obj = new SwiftTestClass2 (); - Assert.AreEqual ("Hello from Swift 2", obj.SayHello2 (), "Hello"); + Assert.That (obj.SayHello2 (), Is.EqualTo ("Hello from Swift 2"), "Hello"); } [Test] diff --git a/tests/bindings-test2/BindingTest.cs b/tests/bindings-test2/BindingTest.cs index 7da68e8bbfc5..803e3bf91624 100644 --- a/tests/bindings-test2/BindingTest.cs +++ b/tests/bindings-test2/BindingTest.cs @@ -9,8 +9,8 @@ public class BindingTest { [Test] public void Test () { - Assert.AreEqual (42, CFunctions.getIntOfChocolate (), "chocolate"); - Assert.AreEqual (42, Bindings.Test.CFunctions.theUltimateAnswer (), "theUltimateAnswer"); + Assert.That (CFunctions.getIntOfChocolate (), Is.EqualTo (42), "chocolate"); + Assert.That (Bindings.Test.CFunctions.theUltimateAnswer (), Is.EqualTo (42), "theUltimateAnswer"); } } } diff --git a/tests/common/Assert.cs b/tests/common/Assert.cs index 5f1b7bb0f344..e16866ee8362 100644 --- a/tests/common/Assert.cs +++ b/tests/common/Assert.cs @@ -92,7 +92,7 @@ protected virtual void SetUp () public static void Assert (string msg, bool condition) { - NUnit.Framework.Assert.True (condition, msg); + Assert.That (condition, Is.True, msg); } public static void AssertEquals (object a, object b) diff --git a/tests/common/AssertHelpers.cs b/tests/common/AssertHelpers.cs index cb4aba3fe4ca..4975c7d26110 100644 --- a/tests/common/AssertHelpers.cs +++ b/tests/common/AssertHelpers.cs @@ -11,7 +11,7 @@ public static void Throws (Action action, string expectedExceptionMessage, st action (); throw new AssertionException (string.Format ("Expected {0}, but no exception was thrown. {1}.", typeof (T).FullName, message)); } catch (T ex) { - Assert.AreEqual (expectedExceptionMessage, ex.Message, message); + Assert.That (ex.Message, Is.EqualTo (expectedExceptionMessage), message); } } diff --git a/tests/common/BundlerTool.cs b/tests/common/BundlerTool.cs index 7a5754620653..3f45c28b1bf7 100644 --- a/tests/common/BundlerTool.cs +++ b/tests/common/BundlerTool.cs @@ -321,7 +321,7 @@ public virtual void AssertExecute (string message = null) public void AssertExecuteFailure (string message = null) { - Assert.AreEqual (1, Execute (), message); + Assert.That (Execute (), Is.EqualTo (1), message); } public abstract void CreateTemporaryApp (Profile profile, string appName = "testApp", string code = null, IList extraArgs = null, string extraCode = null, string usings = null); diff --git a/tests/common/ProductTests.cs b/tests/common/ProductTests.cs index ec1d1bc53569..8d4a4d3d1063 100644 --- a/tests/common/ProductTests.cs +++ b/tests/common/ProductTests.cs @@ -71,7 +71,7 @@ public void MinOSVersion (Profile profile, MachO.LoadCommands load_command, Mach Version lc_min_version; var mincmd = lc as MinCommand; if (mincmd is not null) { - Assert.AreEqual (load_command, mincmd.Command, "Unexpected min load command"); + Assert.That (mincmd.Command, Is.EqualTo (load_command), "Unexpected min load command"); lc_min_version = mincmd.Version; } else { // starting from iOS SDK 12 the LC_BUILD_VERSION is used instead @@ -159,7 +159,7 @@ public void MinOSVersion (Profile profile, MachO.LoadCommands load_command, Mach failed.Add ($"No minOS version found in {machoFile}."); } } - CollectionAssert.IsEmpty (failed, "Failures"); + Assert.That (failed, Is.Empty, "Failures"); } } diff --git a/tests/common/TestRuntime.cs b/tests/common/TestRuntime.cs index 68ab6699c66b..296c348ac2cf 100644 --- a/tests/common/TestRuntime.cs +++ b/tests/common/TestRuntime.cs @@ -210,7 +210,7 @@ public static void AssertXcodeVersion (int major, int minor, int build = 0) if (CheckXcodeVersion (major, minor, build)) return; - NUnit.Framework.Assert.Ignore ("Requires the platform version shipped with Xcode {0}.{1}", major, minor); + NUnit.Framework.Assert.Ignore ($"Requires the platform version shipped with Xcode {major}.{minor}"); } public static void AssertDevice (string message = "This test only runs on device.") @@ -1867,7 +1867,7 @@ public static void AssertNoNonNUnitException (Exception ex, string message) case InconclusiveException: throw new InconclusiveException (ex.Message, ex); case ResultStateException: throw ex; default: - Assert.IsNull (ex, message); + Assert.That (ex, Is.Null, message); break; } } diff --git a/tests/common/Touch.Unit/Touch.Client/Runner/TestResultElement.cs b/tests/common/Touch.Unit/Touch.Client/Runner/TestResultElement.cs index 880741dcc9a2..c9f1ef8af194 100644 --- a/tests/common/Touch.Unit/Touch.Client/Runner/TestResultElement.cs +++ b/tests/common/Touch.Unit/Touch.Client/Runner/TestResultElement.cs @@ -32,7 +32,7 @@ namespace MonoTouch.NUnit.UI { class TestResultElement : StyledMultilineElement { public TestResultElement (TestResult result) : - base (result.Message ?? "Unknown error", result.StackTrace, UITableViewCellStyle.Subtitle) + base (result.Message ?? "Unknown error", result.StackTrace ?? "", UITableViewCellStyle.Subtitle) { } } diff --git a/tests/common/Touch.Unit/Touch.Client/Runner/TouchRunner.cs b/tests/common/Touch.Unit/Touch.Client/Runner/TouchRunner.cs index a2a873e133e8..1c23046fb905 100644 --- a/tests/common/Touch.Unit/Touch.Client/Runner/TouchRunner.cs +++ b/tests/common/Touch.Unit/Touch.Client/Runner/TouchRunner.cs @@ -568,8 +568,8 @@ public virtual void TestFinished (ITestResult r) #endif string? stacktrace = result.StackTrace; - if (!String.IsNullOrEmpty (result.StackTrace)) { - string [] lines = stacktrace.Split (new char [] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + if (!String.IsNullOrEmpty (stacktrace)) { + string [] lines = stacktrace!.Split (new char [] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in lines) writer.WriteLine ("\t\t{0}", line); } @@ -689,9 +689,11 @@ public void Run (Test test) var tsr = new TestSuiteResult (suite); foreach (var runner in runners) { - var rv = (TestResult) (find_result (runner.Result) ?? runner.Result); - if (rv is not null) - tsr.AddResult (rv); + var runnerResult = runner.Result is not null ? (find_result (runner.Result) ?? runner.Result) : null; + if (runnerResult is null) + continue; + var rv = (TestResult) runnerResult; + tsr.AddResult (rv); } Result = tsr; #else diff --git a/tests/common/Touch.Unit/Touch.Client/dotnet/shared.csproj b/tests/common/Touch.Unit/Touch.Client/dotnet/shared.csproj index ada75cd79283..97edb9f41e74 100644 --- a/tests/common/Touch.Unit/Touch.Client/dotnet/shared.csproj +++ b/tests/common/Touch.Unit/Touch.Client/dotnet/shared.csproj @@ -55,10 +55,10 @@ - 3.12.0 + $(NUnitLitePackageVersion) - 3.6.0 + $(NUnitV2ResultWriterPackageVersion) 6.12.0.148 diff --git a/tests/common/shared-dotnet.csproj b/tests/common/shared-dotnet.csproj index 06243b83489f..8c3e83197315 100644 --- a/tests/common/shared-dotnet.csproj +++ b/tests/common/shared-dotnet.csproj @@ -107,7 +107,7 @@ - + diff --git a/tests/framework-test/FrameworkTests.cs b/tests/framework-test/FrameworkTests.cs index da279d818e21..6092954415c4 100644 --- a/tests/framework-test/FrameworkTests.cs +++ b/tests/framework-test/FrameworkTests.cs @@ -19,10 +19,10 @@ public class FrameworkTests { [Test] public void CFunction () { - Assert.AreEqual (42, CFunctions.theUltimateAnswer (), "a"); + Assert.That (CFunctions.theUltimateAnswer (), Is.EqualTo (42), "a"); #if !__MACOS__ - Assert.AreEqual (42, CFunctions.object_theUltimateAnswer (), "object"); - Assert.AreEqual (42, CFunctions.ar_theUltimateAnswer (), "ar"); + Assert.That (CFunctions.object_theUltimateAnswer (), Is.EqualTo (42), "object"); + Assert.That (CFunctions.ar_theUltimateAnswer (), Is.EqualTo (42), "ar"); #endif } @@ -30,7 +30,7 @@ public void CFunction () public void ObjCClass () { using (var obj = new FrameworkTest ()) { - Assert.AreEqual (42, obj.Func (), "a"); + Assert.That (obj.Func (), Is.EqualTo (42), "a"); } } } diff --git a/tests/fsharp/FSharpTests.fs b/tests/fsharp/FSharpTests.fs index ed4c75bb2031..5e05fcec9dc8 100644 --- a/tests/fsharp/FSharpTests.fs +++ b/tests/fsharp/FSharpTests.fs @@ -24,4 +24,4 @@ type FSharpTest () = let e = 5555 let pr = sprintf "%d %d %d %d %d" a b c d e - Assert.AreEqual ("1111 2222 3333 4444 5555", pr) + Assert.That (pr, Is.EqualTo ("1111 2222 3333 4444 5555")) diff --git a/tests/interdependent-binding-projects/Main.cs b/tests/interdependent-binding-projects/Main.cs index 2c08bd2dc725..892a12493f39 100644 --- a/tests/interdependent-binding-projects/Main.cs +++ b/tests/interdependent-binding-projects/Main.cs @@ -15,6 +15,6 @@ static partial void AddTestAssembliesImpl (HashSet assemblies) public class LoaderTest { public void TestAssemblyCount () { - Assert.AreEqual (3, TestLoader.GetTestAssemblies ().Count (), "Test assembly count"); + Assert.That (TestLoader.GetTestAssemblies ().Count (), Is.EqualTo (3), "Test assembly count"); } } diff --git a/tests/test-libraries/testgenerator.cs b/tests/test-libraries/testgenerator.cs index b50e7b191a8c..c118503a9a17 100644 --- a/tests/test-libraries/testgenerator.cs +++ b/tests/test-libraries/testgenerator.cs @@ -953,7 +953,7 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t\tusing (var tc = new ObjCRegistrarTest ()) {"); w.AppendLine ($"\t\t\t\tvar s = tc.PS{s};"); for (int i = 0; i < s.Length; i++) - w.AppendLine ($"\t\t\t\tAssert.AreEqual (0, s.x{i}, \"pre-#{i}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (s.x{i}, Is.EqualTo (0), \"pre-#{i}\");"); w.Append ($"\t\t\t\tvar k = new S{s} () {{ "); for (int i = 0; i < s.Length; i++) w.Append ($"x{i} = ").Append (GetValue (s [i], i)).Append (", "); @@ -962,7 +962,7 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ($"\t\t\t\ttc.PS{s} = k;"); w.AppendLine ($"\t\t\t\ts = tc.PS{s};"); for (int i = 0; i < s.Length; i++) - w.AppendLine ($"\t\t\t\tAssert.AreEqual (k.x{i}, s.x{i}, \"post-#{i}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (s.x{i}, Is.EqualTo (k.x{i}), \"post-#{i}\");"); w.AppendLine (); w.Append ($"\t\t\t\tvar v = new S{s} () {{ "); @@ -975,7 +975,7 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ($"\t\t\t\ttc.SetProperty{s} (v);"); w.AppendLine ($"\t\t\t\ts = tc.PS{s};"); for (int i = 0; i < s.Length; i++) - w.AppendLine ($"\t\t\t\tAssert.AreEqual (v.x{i}, s.x{i}, \"set-#{i}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (s.x{i}, Is.EqualTo (v.x{i}), \"set-#{i}\");"); w.AppendLine ("\t\t\t}"); w.AppendLine ("\t\t}"); @@ -1066,32 +1066,32 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new ObjCRegistrarTest ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}Number, \"initial null property\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}NumberNullable, \"initial nullable null property\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.Get{v.Managed}NumberNullable (), \"initial null method\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Number, Is.Null, \"initial null property\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}NumberNullable, Is.Null, \"initial nullable null property\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}NumberNullable (), Is.Null, \"initial null method\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\t{v.Managed}? value = default ({v.Managed});"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}NumberNonNullable (value.Value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.P{v.Managed}NumberNullable, \"nullable property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.P{v.Managed}NumberNonNullable, \"non-nullable property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.Get{v.Managed}NumberNullable (), \"nullable get method after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.Get{v.Managed}NumberNonNullable (), \"non-nullable get method after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}NumberNullable, Is.EqualTo (value), \"nullable property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}NumberNonNullable, Is.EqualTo (value.Value), \"non-nullable property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}NumberNullable (), Is.EqualTo (value), \"nullable get method after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}NumberNonNullable (), Is.EqualTo (value.Value), \"non-nullable get method after setting default value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}NumberNonNullable (value.Value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.P{v.Managed}NumberNullable, \"nullable property after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.P{v.Managed}NumberNonNullable, \"non-nullable property after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.Get{v.Managed}NumberNullable (), \"nullable get method after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.Get{v.Managed}NumberNonNullable (), \"non-nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}NumberNullable, Is.EqualTo (value), \"nullable property after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}NumberNonNullable, Is.EqualTo (value.Value), \"non-nullable property after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}NumberNullable (), Is.EqualTo (value), \"nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}NumberNonNullable (), Is.EqualTo (value.Value), \"non-nullable get method after setting custom value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = null;"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}NumberNullable (value);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}Number, \"null property after setting null value\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}NumberNullable, \"nullable null property after setting null value\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.Get{v.Managed}NumberNullable (), \"null method after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Number, Is.Null, \"null property after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}NumberNullable, Is.Null, \"nullable null property after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}NumberNullable (), Is.Null, \"null method after setting null value\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); w.AppendLine (); @@ -1102,43 +1102,43 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new BindAsTestClassGenerated ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Number, \"initial null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Number, Is.Null, \"initial null\");"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}NumberNullable:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Number, \"null after setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Number, Is.Null, \"null after setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Number = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}NumberNullable:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Number, \"null after re-setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Number, Is.Null, \"null after re-setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvar value = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tusing (var input = new NSNumber ({v.ToNSNumberCastExpression}value))"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}NumberNullable:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.{v.Managed}Number, \"after setting A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Number, Is.EqualTo (value), \"after setting A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Number = null;"); w.AppendLine ($"\t\t\t\tusing (var input = new NSNumber ({v.ToNSNumberCastExpression}value))"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}NumberNonNullable:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.{v.Managed}Number.Value, \"after setting B\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Number.Value, Is.EqualTo (value), \"after setting B\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Number = null;"); w.AppendLine ($"\t\t\t\tvar number = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}NumberNullable\")));"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (number, \"null from getter A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (number, Is.Null, \"null from getter A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Number = value;"); w.AppendLine ($"\t\t\t\tnumber = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}NumberNullable\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, {v.FromNSNumberCastExpression}number{v.Map}, \"getter B\");"); + w.AppendLine ($"\t\t\t\tAssert.That ({v.FromNSNumberCastExpression}number{v.Map}, Is.EqualTo (value), \"getter B\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Number = value;"); w.AppendLine ($"\t\t\t\tnumber = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}NumberNonNullable\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, {v.FromNSNumberCastExpression}number{v.Map}, \"getter C\");"); + w.AppendLine ($"\t\t\t\tAssert.That ({v.FromNSNumberCastExpression}number{v.Map}, Is.EqualTo (value), \"getter C\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); @@ -1148,28 +1148,28 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new ObjCRegistrarTest ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}Array, \"initial null property\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.Get{v.Managed}Array (), \"initial null method\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array, Is.Null, \"initial null property\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array (), Is.Null, \"initial null method\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\t{v.Managed}[] value = null;"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}Array (value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.P{v.Managed}Array, \"nullable property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.Get{v.Managed}Array (), \"nullable get method after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array, Is.EqualTo (value), \"nullable property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array (), Is.EqualTo (value), \"nullable get method after setting default value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}Array (value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (1, obj.P{v.Managed}Array.Length, \"nullable property after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (1, obj.Get{v.Managed}Array ().Length, \"nullable get method after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.P{v.Managed}Array [0], \"nullable property after setting custom value element\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.Get{v.Managed}Array () [0], \"nullable get method after setting custom value element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array.Length, Is.EqualTo (1), \"nullable property after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array ().Length, Is.EqualTo (1), \"nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array [0], Is.EqualTo (value [0]), \"nullable property after setting custom value element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array () [0], Is.EqualTo (value [0]), \"nullable get method after setting custom value element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = null;"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}Array (value);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}Array, \"null property after setting null value\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.Get{v.Managed}Array (), \"null method after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array, Is.Null, \"null property after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array (), Is.Null, \"null method after setting null value\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); w.AppendLine (); @@ -1180,40 +1180,40 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new BindAsTestClassGenerated ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Array, \"initial null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array, Is.Null, \"initial null\");"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}Array:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Array, \"null after setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array, Is.Null, \"null after setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Array = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}Array:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Array, \"null after re-setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array, Is.Null, \"null after re-setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvar value = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tusing (var input = NSArray.FromNSObjects<{v.Managed}> ((v) => new NSNumber ({v.ToNSNumberCastExpression}v), value))"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}Array:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Length, obj.{v.Managed}Array.Length, \"after setting A\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.{v.Managed}Array [0], \"after setting A element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array.Length, Is.EqualTo (value.Length), \"after setting A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array [0], Is.EqualTo (value [0]), \"after setting A element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Array = null;"); w.AppendLine ($"\t\t\t\tusing (var input = NSArray.FromNSObjects<{v.Managed}> ((v) => new NSNumber ({v.ToNSNumberCastExpression}v), value))"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}Array:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Length, obj.{v.Managed}Array.Length, \"after setting B\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.{v.Managed}Array [0], \"after setting B element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array.Length, Is.EqualTo (value.Length), \"after setting B\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array [0], Is.EqualTo (value [0]), \"after setting B element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Array = null;"); w.AppendLine ($"\t\t\t\tvar array = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}Array\")));"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (array, \"null from getter A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (array, Is.Null, \"null from getter A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Array = value;"); w.AppendLine ($"\t\t\t\tarray = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}Array\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual ((nuint) value.Length, array.Count, \"getter B\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], {v.FromNSNumberCastExpression}array.GetItem (0){v.Map}, \"getter B element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (array.Count, Is.EqualTo ((nuint) value.Length), \"getter B\");"); + w.AppendLine ($"\t\t\t\tAssert.That ({v.FromNSNumberCastExpression}array.GetItem (0){v.Map}, Is.EqualTo (value [0]), \"getter B element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t}}"); @@ -1236,32 +1236,32 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new ObjCRegistrarTest ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}Value, \"initial null property\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}ValueNullable, \"initial nullable null property\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.Get{v.Managed}ValueNullable (), \"initial null method\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Value, Is.Null, \"initial null property\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}ValueNullable, Is.Null, \"initial nullable null property\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}ValueNullable (), Is.Null, \"initial null method\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\t{v.Managed}? value = default ({v.Managed});"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}ValueNonNullable (value.Value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.P{v.Managed}ValueNullable, \"nullable property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.P{v.Managed}ValueNonNullable, \"non-nullable property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.Get{v.Managed}ValueNullable (), \"nullable get method after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.Get{v.Managed}ValueNonNullable (), \"non-nullable get method after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}ValueNullable, Is.EqualTo (value), \"nullable property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}ValueNonNullable, Is.EqualTo (value.Value), \"non-nullable property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}ValueNullable (), Is.EqualTo (value), \"nullable get method after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}ValueNonNullable (), Is.EqualTo (value.Value), \"non-nullable get method after setting default value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}ValueNonNullable (value.Value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.P{v.Managed}ValueNullable, \"nullable property after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.P{v.Managed}ValueNonNullable, \"non-nullable property after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.Get{v.Managed}ValueNullable (), \"nullable get method after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.Get{v.Managed}ValueNonNullable (), \"non-nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}ValueNullable, Is.EqualTo (value), \"nullable property after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}ValueNonNullable, Is.EqualTo (value.Value), \"non-nullable property after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}ValueNullable (), Is.EqualTo (value), \"nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}ValueNonNullable (), Is.EqualTo (value.Value), \"non-nullable get method after setting custom value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = null;"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}ValueNullable (value);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}Value, \"null property after setting null value\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}ValueNullable, \"nullable null property after setting null value\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.Get{v.Managed}ValueNullable (), \"null method after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Value, Is.Null, \"null property after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}ValueNullable, Is.Null, \"nullable null property after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}ValueNullable (), Is.Null, \"null method after setting null value\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); w.AppendLine (); @@ -1272,43 +1272,43 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new BindAsTestClassGenerated ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Value, \"initial null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Value, Is.Null, \"initial null\");"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}ValueNullable:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Value, \"null after setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Value, Is.Null, \"null after setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Value = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}ValueNullable:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Value, \"null after re-setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Value, Is.Null, \"null after re-setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvar value = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tusing (var input = NSValue.{v.MapFrom} (value))"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}ValueNullable:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.{v.Managed}Value, \"after setting A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Value, Is.EqualTo (value), \"after setting A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Value = null;"); w.AppendLine ($"\t\t\t\tusing (var input = NSValue.{v.MapFrom} (value))"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}ValueNonNullable:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.{v.Managed}Value, \"after setting B\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Value, Is.EqualTo (value), \"after setting B\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Value = null;"); w.AppendLine ($"\t\t\t\tvar Value = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}ValueNullable\")));"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (Value, \"null from getter A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (Value, Is.Null, \"null from getter A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Value = value;"); w.AppendLine ($"\t\t\t\tValue = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}ValueNullable\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, Value{v.Map}, \"getter B\");"); + w.AppendLine ($"\t\t\t\tAssert.That (Value{v.Map}, Is.EqualTo (value), \"getter B\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Value = value;"); w.AppendLine ($"\t\t\t\tValue = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}ValueNonNullable\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, Value{v.Map}, \"getter C\");"); + w.AppendLine ($"\t\t\t\tAssert.That (Value{v.Map}, Is.EqualTo (value), \"getter C\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); @@ -1318,28 +1318,28 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new ObjCRegistrarTest ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}Array, \"initial null property\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.Get{v.Managed}Array (), \"initial null method\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array, Is.Null, \"initial null property\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array (), Is.Null, \"initial null method\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\t{v.Managed}[] value = null;"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}Array (value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.P{v.Managed}Array, \"nullable property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.Get{v.Managed}Array (), \"nullable get method after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array, Is.EqualTo (value), \"nullable property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array (), Is.EqualTo (value), \"nullable get method after setting default value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}Array (value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (1, obj.P{v.Managed}Array.Length, \"nullable property after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (1, obj.Get{v.Managed}Array ().Length, \"nullable get method after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.P{v.Managed}Array [0], \"nullable property after setting custom value element\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.Get{v.Managed}Array () [0], \"nullable get method after setting custom value element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array.Length, Is.EqualTo (1), \"nullable property after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array ().Length, Is.EqualTo (1), \"nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array [0], Is.EqualTo (value [0]), \"nullable property after setting custom value element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array () [0], Is.EqualTo (value [0]), \"nullable get method after setting custom value element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = null;"); w.AppendLine ($"\t\t\t\tobj.Set{v.Managed}Array (value);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.P{v.Managed}Array, \"null property after setting null value\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.Get{v.Managed}Array (), \"null method after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.P{v.Managed}Array, Is.Null, \"null property after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.Get{v.Managed}Array (), Is.Null, \"null method after setting null value\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); w.AppendLine (); @@ -1350,40 +1350,40 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new BindAsTestClassGenerated ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Array, \"initial null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array, Is.Null, \"initial null\");"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}Array:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Array, \"null after setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array, Is.Null, \"null after setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Array = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}Array:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.{v.Managed}Array, \"null after re-setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array, Is.Null, \"null after re-setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvar value = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tusing (var input = NSArray.FromNSObjects<{v.Managed}> ((v) => NSValue.{v.MapFrom} (v), value))"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}Array:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Length, obj.{v.Managed}Array.Length, \"after setting A\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.{v.Managed}Array [0], \"after setting A element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array.Length, Is.EqualTo (value.Length), \"after setting A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array [0], Is.EqualTo (value [0]), \"after setting A element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Array = null;"); w.AppendLine ($"\t\t\t\tusing (var input = NSArray.FromNSObjects<{v.Managed}> ((v) => NSValue.{v.MapFrom} (v), value))"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"set{v.Managed}Array:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Length, obj.{v.Managed}Array.Length, \"after setting B\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.{v.Managed}Array [0], \"after setting B element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array.Length, Is.EqualTo (value.Length), \"after setting B\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.{v.Managed}Array [0], Is.EqualTo (value [0]), \"after setting B element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Array = null;"); w.AppendLine ($"\t\t\t\tvar array = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}Array\")));"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (array, \"null from getter A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (array, Is.Null, \"null from getter A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tobj.{v.Managed}Array = value;"); w.AppendLine ($"\t\t\t\tarray = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"get{v.Managed}Array\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual ((nuint) value.Length, array.Count, \"getter B\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], array.GetItem (0){v.Map}, \"getter B element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (array.Count, Is.EqualTo ((nuint) value.Length), \"getter B\");"); + w.AppendLine ($"\t\t\t\tAssert.That (array.GetItem (0){v.Map}, Is.EqualTo (value [0]), \"getter B element\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); w.AppendLine (); @@ -1406,28 +1406,28 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ($"\t\t\tusing (var obj = new ObjCRegistrarTest ()) {{"); w.AppendLine ($"\t\t\t\tAssert.Throws (() => {{ Console.WriteLine (obj.PSmart{v.Managed}Property); }}, \"initial zero property\");"); w.AppendLine ($"\t\t\t\tAssert.Throws (() => {{ Console.WriteLine (obj.GetSmart{v.Managed}Value ()); }}, \"initial zero method\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.GetSmartNullable{v.Managed}Value (), \"initial null method\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmartNullable{v.Managed}Value (), Is.Null, \"initial null method\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\t{v.Managed}? value = default ({v.Managed});"); w.AppendLine ($"\t\t\t\tobj.SetSmartNullable{v.Managed}Value (value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.PSmart{v.Managed}Property, \"zero property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.GetSmart{v.Managed}Value (), \"non-nullable property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.GetSmartNullable{v.Managed}Value (), \"nullable get method after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Property, Is.EqualTo (value.Value), \"zero property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmart{v.Managed}Value (), Is.EqualTo (value.Value), \"non-nullable property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmartNullable{v.Managed}Value (), Is.EqualTo (value), \"nullable get method after setting default value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.SetSmart{v.Managed}Value (value.Value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.PSmart{v.Managed}Property, \"non-nullable property after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.GetSmartNullable{v.Managed}Value (), \"nullable get method after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Value, obj.GetSmart{v.Managed}Value (), \"non-nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Property, Is.EqualTo (value.Value), \"non-nullable property after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmartNullable{v.Managed}Value (), Is.EqualTo (value), \"nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmart{v.Managed}Value (), Is.EqualTo (value.Value), \"non-nullable get method after setting custom value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = null;"); w.AppendLine ($"\t\t\t\tobj.SetSmartNullable{v.Managed}Value (value);"); w.AppendLine ($"\t\t\t\tAssert.Throws (() => {{ Console.WriteLine (obj.PSmart{v.Managed}Property); }}, \"null property after setting null value\");"); w.AppendLine ($"\t\t\t\tAssert.Throws (() => {{ Console.WriteLine (obj.GetSmart{v.Managed}Value ()); }}, \"non-nullable method after setting null value\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.GetSmartNullable{v.Managed}Value (), \"null method after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmartNullable{v.Managed}Value (), Is.Null, \"null method after setting null value\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); w.AppendLine (); @@ -1451,30 +1451,30 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ($"\t\t\t\tvar value = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tusing (var input = value.GetConstant ())"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"setSmartNullable{v.Managed}Value:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.PSmart{v.Managed}Property, \"after setting A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Property, Is.EqualTo (value), \"after setting A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.PSmart{v.Managed}Property = 0;"); w.AppendLine ($"\t\t\t\tusing (var input = value.GetConstant ())"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"setSmart{v.Managed}Value:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.PSmart{v.Managed}Property, \"after setting B\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Property, Is.EqualTo (value), \"after setting B\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.PSmart{v.Managed}Property = 0;"); w.AppendLine ($"\t\t\t\tvar Value = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"getSmartNullable{v.Managed}Value\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (default ({v.Managed}).GetConstant ().ToString (), Value.ToString (), \"zero from getter A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (Value.ToString (), Is.EqualTo (default ({v.Managed}).GetConstant ().ToString ()), \"zero from getter A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.PSmart{v.Managed}Property = value;"); w.AppendLine ($"\t\t\t\tValue = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"getSmartNullable{v.Managed}Value\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, {v.Managed}Extensions.GetValue (Value), \"getter B\");"); + w.AppendLine ($"\t\t\t\tAssert.That ({v.Managed}Extensions.GetValue (Value), Is.EqualTo (value), \"getter B\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = {v.ManagedNewExpression};"); w.AppendLine ($"\t\t\t\tobj.PSmart{v.Managed}Property = value;"); w.AppendLine ($"\t\t\t\tValue = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"getSmart{v.Managed}Value\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, {v.Managed}Extensions.GetValue (Value), \"getter C\");"); + w.AppendLine ($"\t\t\t\tAssert.That ({v.Managed}Extensions.GetValue (Value), Is.EqualTo (value), \"getter C\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); @@ -1484,28 +1484,28 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new ObjCRegistrarTest ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.PSmart{v.Managed}Properties, \"initial null property\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.GetSmart{v.Managed}Values (), \"initial null method\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties, Is.Null, \"initial null property\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmart{v.Managed}Values (), Is.Null, \"initial null method\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\t{v.Managed}[] value = null;"); w.AppendLine ($"\t\t\t\tobj.SetSmart{v.Managed}Values (value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.PSmart{v.Managed}Properties, \"nullable property after setting default value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value, obj.GetSmart{v.Managed}Values (), \"nullable get method after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties, Is.EqualTo (value), \"nullable property after setting default value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmart{v.Managed}Values (), Is.EqualTo (value), \"nullable get method after setting default value\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tobj.SetSmart{v.Managed}Values (value);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (1, obj.PSmart{v.Managed}Properties.Length, \"nullable property after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (1, obj.GetSmart{v.Managed}Values ().Length, \"nullable get method after setting custom value\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.PSmart{v.Managed}Properties [0], \"nullable property after setting custom value element\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.GetSmart{v.Managed}Values () [0], \"nullable get method after setting custom value element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties.Length, Is.EqualTo (1), \"nullable property after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmart{v.Managed}Values ().Length, Is.EqualTo (1), \"nullable get method after setting custom value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties [0], Is.EqualTo (value [0]), \"nullable property after setting custom value element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmart{v.Managed}Values () [0], Is.EqualTo (value [0]), \"nullable get method after setting custom value element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = null;"); w.AppendLine ($"\t\t\t\tobj.SetSmart{v.Managed}Values (value);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.PSmart{v.Managed}Properties, \"null property after setting null value\");"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.GetSmart{v.Managed}Values (), \"null method after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties, Is.Null, \"null property after setting null value\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.GetSmart{v.Managed}Values (), Is.Null, \"null method after setting null value\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); w.AppendLine (); @@ -1516,40 +1516,40 @@ public partial class RegistrarTestGenerated {"); w.AppendLine ("\t\t{"); WriteAsserts (w, v); w.AppendLine ($"\t\t\tusing (var obj = new BindAsTestClassGenerated ()) {{"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.PSmart{v.Managed}Properties, \"initial null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties, Is.Null, \"initial null\");"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"setSmart{v.Managed}Values:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.PSmart{v.Managed}Properties, \"null after setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties, Is.Null, \"null after setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.PSmart{v.Managed}Properties = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"setSmart{v.Managed}Values:\"), IntPtr.Zero);"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (obj.PSmart{v.Managed}Properties, \"null after re-setting null\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties, Is.Null, \"null after re-setting null\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvar value = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tusing (var input = NSArray.FromNSObjects<{v.Managed}> ((v) => v.GetConstant (), value))"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"setSmart{v.Managed}Values:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Length, obj.PSmart{v.Managed}Properties.Length, \"after setting A\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.PSmart{v.Managed}Properties [0], \"after setting A element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties.Length, Is.EqualTo (value.Length), \"after setting A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties [0], Is.EqualTo (value [0]), \"after setting A element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.PSmart{v.Managed}Properties = null;"); w.AppendLine ($"\t\t\t\tusing (var input = NSArray.FromNSObjects<{v.Managed}> ((v) => v.GetConstant (), value))"); w.AppendLine ($"\t\t\t\t\tMessaging.void_objc_msgSend_IntPtr (obj.Handle, Selector.GetHandle (\"setSmart{v.Managed}Values:\"), input.Handle);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value.Length, obj.PSmart{v.Managed}Properties.Length, \"after setting B\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], obj.PSmart{v.Managed}Properties [0], \"after setting B element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties.Length, Is.EqualTo (value.Length), \"after setting B\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.PSmart{v.Managed}Properties [0], Is.EqualTo (value [0]), \"after setting B element\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tobj.PSmart{v.Managed}Properties = null;"); w.AppendLine ($"\t\t\t\tvar array = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"getSmart{v.Managed}Values\")));"); - w.AppendLine ($"\t\t\t\tAssert.IsNull (array, \"null from getter A\");"); + w.AppendLine ($"\t\t\t\tAssert.That (array, Is.Null, \"null from getter A\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\tvalue = new {v.Managed} [] {{ {v.ManagedNewExpression} }};"); w.AppendLine ($"\t\t\t\tobj.PSmart{v.Managed}Properties = value;"); w.AppendLine ($"\t\t\t\tarray = Runtime.GetNSObject (Messaging.IntPtr_objc_msgSend (obj.Handle, Selector.GetHandle (\"getSmart{v.Managed}Values\")));"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual ((nuint) value.Length, array.Count, \"getter B\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (value [0], {v.Managed}Extensions.GetValue (array.GetItem (0)), \"getter B element\");"); + w.AppendLine ($"\t\t\t\tAssert.That (array.Count, Is.EqualTo ((nuint) value.Length), \"getter B\");"); + w.AppendLine ($"\t\t\t\tAssert.That ({v.Managed}Extensions.GetValue (array.GetItem (0)), Is.EqualTo (value [0]), \"getter B element\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine ("\t\t}"); w.AppendLine (); @@ -1720,7 +1720,7 @@ public partial class TrampolineTestGenerated {"); w.AppendLine ($"\t\t\t\t\trv = S{s}_objc_msgSend (obj.Handle, new Selector (\"Test_{s}Struct\").Handle);"); w.AppendLine ($"\t\t\t\t}}"); } - w.AppendLine ($"\t\t\t\tAssert.AreEqual (({GenerateNewExpression (s, 1)}).ToString (), rv.ToString (), \"a\");"); + w.AppendLine ($"\t\t\t\tAssert.That (rv.ToString (), Is.EqualTo (({GenerateNewExpression (s, 1)}).ToString ()), \"a\");"); w.AppendLine (); WriteStretConditions (w, s, out never); @@ -1732,7 +1732,7 @@ public partial class TrampolineTestGenerated {"); w.AppendLine ($"\t\t\t\t\trv = S{s}_objc_msgSend (class_ptr, new Selector (\"Test_Static{s}Struct\").Handle);"); w.AppendLine ($"\t\t\t\t}}"); } - w.AppendLine ($"\t\t\t\tAssert.AreEqual (({GenerateNewExpression (s, 2)}).ToString (), rv.ToString (), \"a\");"); + w.AppendLine ($"\t\t\t\tAssert.That (rv.ToString (), Is.EqualTo (({GenerateNewExpression (s, 2)}).ToString ()), \"a\");"); w.AppendLine (); WriteStretConditions (w, s, out never); @@ -1744,7 +1744,7 @@ public partial class TrampolineTestGenerated {"); w.AppendLine ($"\t\t\t\t\trv = S{s}_objc_msgSend (obj.Handle, new Selector (\"Test_{s}StructProperty\").Handle);"); w.AppendLine ($"\t\t\t\t}}"); } - w.AppendLine ($"\t\t\t\tAssert.AreEqual (({GenerateNewExpression (s, 3)}).ToString (), rv.ToString (), \"a\");"); + w.AppendLine ($"\t\t\t\tAssert.That (rv.ToString (), Is.EqualTo (({GenerateNewExpression (s, 3)}).ToString ()), \"a\");"); w.AppendLine (); WriteStretConditions (w, s, out never); @@ -1756,7 +1756,7 @@ public partial class TrampolineTestGenerated {"); w.AppendLine ($"\t\t\t\t\trv = S{s}_objc_msgSend (class_ptr, new Selector (\"Test_Static{s}StructProperty\").Handle);"); w.AppendLine ($"\t\t\t\t}}"); } - w.AppendLine ($"\t\t\t\tAssert.AreEqual (({GenerateNewExpression (s, 4)}).ToString (), rv.ToString (), \"a\");"); + w.AppendLine ($"\t\t\t\tAssert.That (rv.ToString (), Is.EqualTo (({GenerateNewExpression (s, 4)}).ToString ()), \"a\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\trvd = 0;"); @@ -1769,8 +1769,8 @@ public partial class TrampolineTestGenerated {"); w.AppendLine ($"\t\t\t\t\trv = S{s}_objc_msgSend_out_double (obj.Handle, new Selector (\"Test_{s}Struct_out_double:\").Handle, out rvd);"); w.AppendLine ($"\t\t\t\t}}"); } - w.AppendLine ($"\t\t\t\tAssert.AreEqual (({GenerateNewExpression (s, 5)}).ToString (), rv.ToString (), \"a\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (3.14, rvd, \"double out\");"); + w.AppendLine ($"\t\t\t\tAssert.That (rv.ToString (), Is.EqualTo (({GenerateNewExpression (s, 5)}).ToString ()), \"a\");"); + w.AppendLine ($"\t\t\t\tAssert.That (rvd, Is.EqualTo (3.14), \"double out\");"); w.AppendLine (); w.AppendLine ($"\t\t\t\trvf = 0;"); @@ -1783,8 +1783,8 @@ public partial class TrampolineTestGenerated {"); w.AppendLine ($"\t\t\t\t\trv = S{s}_objc_msgSend_out_float (class_ptr, new Selector (\"Test_Static{s}Struct_out_float:\").Handle, out rvf);"); w.AppendLine ($"\t\t\t\t}}"); } - w.AppendLine ($"\t\t\t\tAssert.AreEqual (({GenerateNewExpression (s, 6)}).ToString (), rv.ToString (), \"a\");"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (3.15f, rvf, \"float out\");"); + w.AppendLine ($"\t\t\t\tAssert.That (rv.ToString (), Is.EqualTo (({GenerateNewExpression (s, 6)}).ToString ()), \"a\");"); + w.AppendLine ($"\t\t\t\tAssert.That (rvf, Is.EqualTo (3.15f), \"float out\");"); w.AppendLine (); w.AppendLine ($"\t\t\t}}"); @@ -1793,7 +1793,7 @@ public partial class TrampolineTestGenerated {"); w.AppendLine ($"\t\t\tusing (var obj = new OverrideRegistrarTest ()) {{"); w.AppendLine ($"\t\t\t\tvar structValue = {GenerateNewExpression (s, 7)};"); w.AppendLine ($"\t\t\t\tvoid_objc_msgSend_{s} (obj.Handle, Selector.GetHandle (\"setProperty{s}:\"), structValue);"); - w.AppendLine ($"\t\t\t\tAssert.AreEqual (structValue.ToString (), obj.V{s}.ToString (), \"SetProperty#1\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.V{s}.ToString (), Is.EqualTo (structValue.ToString ()), \"SetProperty#1\");"); w.AppendLine ($"\t\t\t}}"); w.AppendLine (); @@ -1813,9 +1813,9 @@ public partial class TrampolineTestGenerated {"); for (var x = 1; x <= i + 1; x++) { if (x == i) { - w.AppendLine ($"\t\t\t\tAssert.AreEqual (structValue.ToString (), obj.V{s}.ToString (), \"SetProperty#2-S{x}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.V{s}.ToString (), Is.EqualTo (structValue.ToString ()), \"SetProperty#2-S{x}\");"); } else { - w.AppendLine ($"\t\t\t\tAssert.AreEqual ((nint) {x}, obj.ManagedVoidArg{x}, \"SetProperty#2-X{x}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.ManagedVoidArg{x}, Is.EqualTo ((nint) {x}), \"SetProperty#2-X{x}\");"); } } w.AppendLine ($"\t\t\t}}"); @@ -1838,11 +1838,11 @@ public partial class TrampolineTestGenerated {"); for (var x = 1; x <= i + 1; x++) { if (x == 1) { - w.AppendLine ($"\t\t\t\tAssert.AreEqual ((float) {x}, obj.ManagedFloatArg{x}, \"SetProperty#3-X{x}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.ManagedFloatArg{x}, Is.EqualTo ((float) {x}), \"SetProperty#3-X{x}\");"); } else if (x == i) { - w.AppendLine ($"\t\t\t\tAssert.AreEqual (structValue.ToString (), obj.V{s}.ToString (), \"SetProperty#3-S{x}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.V{s}.ToString (), Is.EqualTo (structValue.ToString ()), \"SetProperty#3-S{x}\");"); } else { - w.AppendLine ($"\t\t\t\tAssert.AreEqual ((nint) {x}, obj.ManagedVoidArg{x}, \"SetProperty#3-X{x}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.ManagedVoidArg{x}, Is.EqualTo ((nint) {x}), \"SetProperty#3-X{x}\");"); } } w.AppendLine ($"\t\t\t}}"); @@ -1865,11 +1865,11 @@ public partial class TrampolineTestGenerated {"); for (var x = 1; x <= i + 1; x++) { if (x == 1) { - w.AppendLine ($"\t\t\t\tAssert.AreEqual ((double) {x}, obj.ManagedDoubleArg{x}, \"SetProperty#3-X{x}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.ManagedDoubleArg{x}, Is.EqualTo ((double) {x}), \"SetProperty#3-X{x}\");"); } else if (x == i) { - w.AppendLine ($"\t\t\t\tAssert.AreEqual (structValue.ToString (), obj.V{s}.ToString (), \"SetProperty#3-S{x}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.V{s}.ToString (), Is.EqualTo (structValue.ToString ()), \"SetProperty#3-S{x}\");"); } else { - w.AppendLine ($"\t\t\t\tAssert.AreEqual ((nint) {x}, obj.ManagedVoidArg{x}, \"SetProperty#3-X{x}\");"); + w.AppendLine ($"\t\t\t\tAssert.That (obj.ManagedVoidArg{x}, Is.EqualTo ((nint) {x}), \"SetProperty#3-X{x}\");"); } } w.AppendLine ($"\t\t\t}}"); diff --git a/tests/xcframework-test/XCFrameworkTests.cs b/tests/xcframework-test/XCFrameworkTests.cs index 206951ea19b0..2926ee397ed8 100644 --- a/tests/xcframework-test/XCFrameworkTests.cs +++ b/tests/xcframework-test/XCFrameworkTests.cs @@ -19,14 +19,14 @@ public class FrameworkTests { [Test] public void CFunction () { - Assert.AreEqual (42, CFunctions.theUltimateAnswer (), "a"); + Assert.That (CFunctions.theUltimateAnswer (), Is.EqualTo (42), "a"); } [Test] public void ObjCClass () { using (var obj = new FrameworkTest ()) { - Assert.AreEqual (42, obj.Func (), "a"); + Assert.That (obj.Func (), Is.EqualTo (42), "a"); } } } diff --git a/tests/xtro-sharpie/UnitTests/UnitTests.csproj b/tests/xtro-sharpie/UnitTests/UnitTests.csproj index dd9a60e4b579..5bd7b152f1f2 100644 --- a/tests/xtro-sharpie/UnitTests/UnitTests.csproj +++ b/tests/xtro-sharpie/UnitTests/UnitTests.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/tests/xtro-sharpie/UnitTests/Xtro.cs b/tests/xtro-sharpie/UnitTests/Xtro.cs index 9b4dd819c721..6037f79e44c1 100644 --- a/tests/xtro-sharpie/UnitTests/Xtro.cs +++ b/tests/xtro-sharpie/UnitTests/Xtro.cs @@ -29,7 +29,7 @@ public void RunTest () TestContext.AddTestAttachment (zippedReport, "HTML report (zipped)"); } - Assert.AreEqual (0, rv, "ExitCode"); + Assert.That (rv, Is.EqualTo (0), "ExitCode"); } } } From e3f0f385b69236b37f80ec5cfc3d06c71a4e6a8f Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 25 May 2026 08:18:40 +0200 Subject: [PATCH 07/97] [Foundation] Simplify memory management of NSObjectData for CoreCLR. (#25507) On CoreCLR, the tagged memory returned by ObjectiveCMarshal.CreateReferenceTrackingHandle is guaranteed to be stable (same pointer per object) and its lifetime is tied to the GC freeing the instance. This means we can use it directly as our NSObjectData storage, removing the need for: - The TrackedObjectInfo indirection struct (both in C# and native code). - The ConditionalWeakTable used to prevent premature collection of separately-allocated native memory. - The Interlocked.CompareExchange synchronization (since the pointer is always the same). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- runtime/coreclr-bridge.m | 16 ++++++--------- src/Foundation/NSObject2.cs | 33 ++++++++++++++++++------------ src/ObjCRuntime/Runtime.CoreCLR.cs | 27 +++++++++++++++--------- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/runtime/coreclr-bridge.m b/runtime/coreclr-bridge.m index 8554e64980c9..e55c125461c8 100644 --- a/runtime/coreclr-bridge.m +++ b/runtime/coreclr-bridge.m @@ -213,10 +213,6 @@ * Ref: https://github.com/dotnet/designs/blob/1bb5844c165195e2f633cb1dbe042c4b92aefc4d/accepted/2021/objectivec-interop.md */ -struct TrackedObjectInfo { - struct NSObjectData* data; -}; - void xamarin_bridge_setup () { @@ -296,10 +292,10 @@ // But we can access the native memory given to us when the object was toggled // (and which is passed as the 'ptr' argument), so let's get the data we need from there. int rv = 0; - struct TrackedObjectInfo *info = (struct TrackedObjectInfo *) ptr; - enum NSObjectFlags flags = (enum NSObjectFlags) info->data->flags; + struct NSObjectData *data = (struct NSObjectData *) ptr; + enum NSObjectFlags flags = (enum NSObjectFlags) data->flags; bool isRegisteredToggleRef = (flags & NSObjectFlagsRegisteredToggleRef) == NSObjectFlagsRegisteredToggleRef; - id handle = info->data->handle; + id handle = data->handle; MonoToggleRefStatus res = (MonoToggleRefStatus) 0; if (isRegisteredToggleRef) { @@ -337,9 +333,9 @@ void xamarin_coreclr_reference_tracking_tracked_object_entered_finalization (void* ptr) { - struct TrackedObjectInfo *info = (struct TrackedObjectInfo *) ptr; - info->data->flags = (enum NSObjectFlags) (info->data->flags | NSObjectFlagsInFinalizerQueue); - LOG_CORECLR (stderr, "%s (%p) flags: %i\n", __func__, ptr, (int) info->flags); + struct NSObjectData *data = (struct NSObjectData *) ptr; + data->flags = (enum NSObjectFlags) (data->flags | NSObjectFlagsInFinalizerQueue); + LOG_CORECLR (stderr, "%s (%p) flags: %i\n", __func__, ptr, (int) data->flags); } void diff --git a/src/Foundation/NSObject2.cs b/src/Foundation/NSObject2.cs index 92657093752f..22498aeed364 100644 --- a/src/Foundation/NSObject2.cs +++ b/src/Foundation/NSObject2.cs @@ -120,7 +120,7 @@ internal interface INSObjectFactory { #if !COREBUILD // Allocated in native memory, so that it can be accessed from native code without having to deal with the GC. // This is mirrored in runtime.h and the definition needs to be in sync. - struct NSObjectData { + internal struct NSObjectData { public NativeHandle handle; public NSObject.Flags flags; } @@ -228,18 +228,24 @@ unsafe NativeHandle handle { if (data != IntPtr.Zero) return (NSObjectData*) data; - var data_handle = new NSObjectDataHandle (); - var existing_data = Interlocked.CompareExchange (ref __data, (IntPtr) data_handle.Data, IntPtr.Zero); - if (existing_data != IntPtr.Zero) { - // return the existing data, the GC will collect the other one we just created - return (NSObjectData*) existing_data; + if (Runtime.IsCoreCLR) { + data = (IntPtr) Runtime.GetTaggedMemory (this); + __data = data; // Runtime.GetTaggedMemory will always return the same pointer for the same object, so no synchronization is needed here (redundant writes are benign). + return (NSObjectData*) data; + } else { + var data_handle = new NSObjectDataHandle (); + var existing_data = Interlocked.CompareExchange (ref __data, (IntPtr) data_handle.Data, IntPtr.Zero); + if (existing_data != IntPtr.Zero) { + // return the existing data, the GC will collect the other one we just created + return (NSObjectData*) existing_data; + } + // tell the data handle we just created to track us + data_handle.CreateHandle (this); + // make sure the data isn't freed before this NSObject is collected, but also + // that it is freed after this NSObject is collected. + data_table.Add (this, data_handle); + return data_handle.Data; } - // tell the data handle we just created to track us - data_handle.CreateHandle (this); - // make sure the data isn't freed before this NSObject is collected, but also - // that it is freed after this NSObject is collected. - data_table.Add (this, data_handle); - return data_handle.Data; } unsafe Flags flags { @@ -417,7 +423,8 @@ unsafe NativeHandle GetSuper () internal static NativeHandle Initialize () { - data_table = new ConditionalWeakTable (); + if (!Runtime.IsCoreCLR) + data_table = new ConditionalWeakTable (); super_map = new ConditionalWeakTable (); return class_ptr; } diff --git a/src/ObjCRuntime/Runtime.CoreCLR.cs b/src/ObjCRuntime/Runtime.CoreCLR.cs index fff4e97daf4f..c4cf3636de4b 100644 --- a/src/ObjCRuntime/Runtime.CoreCLR.cs +++ b/src/ObjCRuntime/Runtime.CoreCLR.cs @@ -205,28 +205,35 @@ static void RaiseAppDomainUnhandledExceptionEvent (IntPtr gchandle) ExceptionHandling.RaiseAppDomainUnhandledExceptionEvent (exception); } - // Size: 2 pointers - internal struct TrackedObjectInfo { - public unsafe NSObjectData* Data; - } - [SupportedOSPlatform ("macos")] internal static GCHandle CreateTrackingGCHandle (NSObject obj, IntPtr handle) { var gchandle = ObjectiveCMarshal.CreateReferenceTrackingHandle (obj, out var info); unsafe { - TrackedObjectInfo* tracked_info; fixed (void* ptr = info) - tracked_info = (TrackedObjectInfo*) ptr; - tracked_info->Data = obj.GetData (); - - log_coreclr ($"GetOrCreateTrackingGCHandle ({obj.GetType ().FullName}, 0x{handle.ToString ("x")}) => Info=0x{((IntPtr) tracked_info).ToString ("x")} Data=0x{(IntPtr) tracked_info->Data:x} Created new"); + log_coreclr ($"GetOrCreateTrackingGCHandle ({obj.GetType ().FullName}, 0x{handle.ToString ("x")}) => 0x{((IntPtr) ptr).ToString ("x")} Created new"); } return gchandle; } + internal unsafe static void* GetTaggedMemory (NSObject obj) + { + // If https://github.com/dotnet/runtime/issues/128476 is accepted and implemented, + // can just call that new API instead of calling ObjectiveCMarshal.CreateReferenceTrackingHandle and + // freeing the returned handle. + + var gchandle = ObjectiveCMarshal.CreateReferenceTrackingHandle (obj, out var info); + // We only care about the tagged memory ('info'), so just free the GCHandle. + // We might want to request an API to just get the tagged memory at some point. + gchandle.Free (); + // The tagged memory pointer is guaranteed to be the same for every call to ObjectiveCMarshal.CreateReferenceTrackingHandle, + // and it will automatically be freed once the 'obj' is freed by the GC (in particular _once the instance is freed_, + // not _once the instance is collectible_, which is a very important distinction for us). + return Unsafe.AsPointer (ref info.GetPinnableReference ()); + } + // See "Toggle-ref support for CoreCLR" in coreclr-bridge.m for more information. internal static void RegisterToggleReferenceCoreCLR (NSObject obj, IntPtr handle, bool isCustomType) { From c284e3eba0f0fcb5030c5b43dee4bb09d72c31a4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 25 May 2026 11:28:00 +0200 Subject: [PATCH 08/97] [devops] Remove the fix to avoid using a leaked GitHub ssh key. (#25504) The current script breaks the build: + ssh-keygen -R github.com Cannot stat /Users/builder/.ssh/known_hosts: No such file or directory ##[error]Bash exited with code '255'. The script was added over three years ago, and all our bots have been re-imaged multiple times since then, thus this isn't an issue anymore. So just remove the script. --- .../automation/scripts/bash/fix-github-ssh-key.sh | 10 ---------- tools/devops/automation/templates/common/setup.yml | 3 --- 2 files changed, 13 deletions(-) delete mode 100755 tools/devops/automation/scripts/bash/fix-github-ssh-key.sh diff --git a/tools/devops/automation/scripts/bash/fix-github-ssh-key.sh b/tools/devops/automation/scripts/bash/fix-github-ssh-key.sh deleted file mode 100755 index a603c99bc60d..000000000000 --- a/tools/devops/automation/scripts/bash/fix-github-ssh-key.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -ex - -# ensure that we do remove the old ssh key from github which was leaked: https://blog.gitguardian.com/github-exposed-private-ssh-key/ - -ssh-keygen -R github.com -{ - echo "github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl" - echo "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=" - echo "github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=" -} >> ~/.ssh/known_hosts diff --git a/tools/devops/automation/templates/common/setup.yml b/tools/devops/automation/templates/common/setup.yml index a713adac8593..a80833941c2f 100644 --- a/tools/devops/automation/templates/common/setup.yml +++ b/tools/devops/automation/templates/common/setup.yml @@ -22,9 +22,6 @@ steps: name: disableCodeQLOnArm64 condition: and(succeeded(), eq('${{ parameters.disableCodeQL }}', 'true')) -- bash: $(Build.SourcesDirectory)/$(BUILD_REPOSITORY_TITLE)/tools/devops/automation/scripts/bash/fix-github-ssh-key.sh - displayName: 'Fix GitHub SSH host key' - - pwsh: '& "$Env:SYSTEM_DEFAULTWORKINGDIRECTORY/$Env:BUILD_REPOSITORY_TITLE/tools/devops/automation/scripts/show_bot_info.ps1"' displayName: 'Show Bot Info' From 97a86d5a903c299cede26d90e48a37fd29d684f2 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 25 May 2026 18:20:55 +0200 Subject: [PATCH 09/97] [tests] Upload expected app size files as artifacts on test failure (#25482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When app size tests fail in Azure DevOps, write the updated expected files to `$(Build.ArtifactStagingDirectory)/updated-expected-sizes/` so they get published as a pipeline artifact. This makes it easy to download and apply the corrected files without having to reproduce the build locally. Also add a Copilot skill that can fetch the artifacts and apply them locally. Changes: - **AppSizeTest.cs**: When differences are detected (either app size exceeds tolerance OR the file list changed), write the updated expected file to the artifact staging directory. Falls back to a temp directory when not running in CI. - **run-tests.yml**: Add a `PublishPipelineArtifact@1` step to upload the `updated-expected-sizes` directory after tests complete. - **Tolerance fix**: Previously, artifacts were only uploaded when the total app size exceeded tolerance. If files were added/removed from the bundle but the total size stayed within tolerance, no artifact was produced. Now both size and file-list changes trigger artifact upload. - **Expected file updates**: Update expected files to match current CI output. - **Copilot skill** (`.github/skills/update-expected-app-size/`): Helps download these artifacts from Azure DevOps and apply them locally. 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/update-expected-app-size/SKILL.md | 126 ++++++++++++++++++ tests/dotnet/UnitTests/AppSizeTest.cs | 69 +++++++--- .../expected/MacCatalyst-MonoVM-size.txt | 8 +- ...reCLR-Interpreter-TrimmableStatic-size.txt | 10 +- .../expected/TVOS-MonoVM-interpreter-size.txt | 6 +- .../TVOS-NativeAOT-TrimmableStatic-size.txt | 6 +- .../expected/iOS-MonoVM-interpreter-size.txt | 6 +- .../UnitTests/expected/iOS-NativeAOT-size.txt | 6 +- .../automation/templates/tests/run-tests.yml | 9 ++ 9 files changed, 206 insertions(+), 40 deletions(-) create mode 100644 .github/skills/update-expected-app-size/SKILL.md diff --git a/.github/skills/update-expected-app-size/SKILL.md b/.github/skills/update-expected-app-size/SKILL.md new file mode 100644 index 000000000000..88d888c17a0e --- /dev/null +++ b/.github/skills/update-expected-app-size/SKILL.md @@ -0,0 +1,126 @@ +--- +name: update-expected-app-size +description: >- + Download updated expected app size files from Azure DevOps CI artifacts for + the current branch. Use when app size tests fail in CI and the user wants to + update the expected files locally. Trigger on "update app size", "download + expected files", "fix app size test", or "update expected app size files". +--- + +# Update Expected App Size Files + +Download updated expected app size files from Azure DevOps artifacts for the current branch's PR build, and apply them to the repository. + +## When to Use + +- App size tests failed in CI and the user wants to update the expected files +- User asks to "update app size", "download expected files", or "fix app size test failures" +- User wants to pull the updated expected files from a recent CI build + +## Background + +The app size tests (`tests/dotnet/UnitTests/AppSizeTest.cs`) compare the built app's size and preserved APIs against expected files stored in `tests/dotnet/UnitTests/expected/`. When the test detects a difference and `WRITE_KNOWN_FAILURES` is not set, it writes the updated expected file to `$(Build.ArtifactStagingDirectory)/updated-expected-sizes/`, and a pipeline step publishes this directory as a build artifact. + +The artifact name follows the pattern `updated-expected-sizes-{testPrefix}-{attempt}` (e.g., `updated-expected-sizes-dotnettests_ios-1`). Inside the artifact, files are named: +- `{Platform}-{Runtime}-size.txt` — e.g., `iOS-MonoVM-size.txt` +- `{Platform}-{Runtime}-preservedapis.txt` — e.g., `iOS-MonoVM-preservedapis.txt` + +The expected files on disk are at: +- `tests/dotnet/UnitTests/expected/{Platform}-{Runtime}-size.txt` +- `tests/dotnet/UnitTests/expected/{Platform}-{Runtime}-preservedapis.txt` + +## Workflow + +### 1. Determine the current branch and PR + +```bash +BRANCH=$(git branch --show-current) +# Find the PR number for this branch +gh pr list --head "$BRANCH" --repo dotnet/macios --json number,url --jq '.[0]' +``` + +If no PR is found, inform the user that this skill requires a PR to exist for the current branch (so that CI has run). + +### 2. Find the Azure DevOps build + +The CI builds for PRs in dotnet/macios run in the `devdiv` Azure DevOps organization, project `DevDiv`. + +Use the GitHub PR checks to find the Azure DevOps build URL: + +```bash +gh pr checks --repo dotnet/macios +``` + +Look for a check that links to Azure DevOps. The build URL will look like: +``` +https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=XXXXXXX +``` + +Extract the `buildId` from the URL. + +### 3. Download the artifacts + +Use the Azure DevOps REST API to list and download artifacts: + +```bash +# List artifacts for the build +TOKEN=$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv) +curl -s "https://devdiv.visualstudio.com/DevDiv/_apis/build/builds/{buildId}/artifacts?api-version=7.0" \ + -H "Authorization: Bearer $TOKEN" +``` + +Look for artifacts whose names contain `updated-expected-sizes` (e.g., `updated-expected-sizes-dotnettests_ios-1`). Get the artifact's `downloadUrl` and download it: + +```bash +# Get the download URL for a specific artifact +ARTIFACT_INFO=$(curl -s "https://devdiv.visualstudio.com/DevDiv/_apis/build/builds/{buildId}/artifacts?artifactName={artifactName}&api-version=7.0" \ + -H "Authorization: Bearer $TOKEN") +DOWNLOAD_URL=$(echo "$ARTIFACT_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['resource']['downloadUrl'])") + +# Download the artifact zip +curl -sL "$DOWNLOAD_URL" -H "Authorization: Bearer $TOKEN" -o artifact.zip +``` + +If `az` is not available or not authenticated, direct the user to download manually from the Azure DevOps build artifacts page. + +### 4. Place the files + +Extract the downloaded artifact zip and place the files in the expected directory: + +```bash +EXPECTED_DIR="tests/dotnet/UnitTests/expected" +unzip -o artifact.zip -d /tmp/updated-sizes/ +cp /tmp/updated-sizes/*/*.txt "$EXPECTED_DIR/" +``` + +The files inside the zip already have the correct names (e.g., `iOS-MonoVM-size.txt`) and can be copied directly. + +### 5. Verify and commit + +After placing the files: +1. Run `git diff` to show what changed +2. Ask the user if the changes look correct +3. If confirmed, commit the changes: + ```bash + git add tests/dotnet/UnitTests/expected/ + git commit -m "[tests] Update expected app size files" + ``` + +## Fallback: Manual Download + +If automated download fails (auth issues, etc.), provide the user with: +1. The Azure DevOps build URL +2. Instructions to navigate to the build → Summary → Artifacts section +3. Look for individual artifacts whose names match the patterns above +4. Download each file and place it as `tests/dotnet/UnitTests/expected/{artifactName}.txt` + +## Fallback: Run Locally + +If the user can build locally, they can update the expected files directly: + +```bash +WRITE_KNOWN_FAILURES=1 tests-dotnet AppSizeTest +``` + +This runs the tests, updates the expected files in place, and marks the tests as passed. + diff --git a/tests/dotnet/UnitTests/AppSizeTest.cs b/tests/dotnet/UnitTests/AppSizeTest.cs index 047d3613c45f..db6d4d432dbf 100644 --- a/tests/dotnet/UnitTests/AppSizeTest.cs +++ b/tests/dotnet/UnitTests/AppSizeTest.cs @@ -114,7 +114,8 @@ void Run (ApplePlatform platform, string runtimeIdentifiers, string configuratio DotNet.AssertBuild (project_path, properties); // FORCE_UPDATE_KNOWN_FAILURES will update the known failures files even if the test doesn't actually fail - // WRITE_KNOWN_FAILURES will only update the known failures files if the test fails + // WRITE_KNOWN_FAILURES will only update the known failures files if the test fails (and mark the test as passed) + // If neither is set, the updated expected file is uploaded as an Azure DevOps artifact. var forceUpdate = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("FORCE_UPDATE_KNOWN_FAILURES")); var update = forceUpdate || !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("WRITE_KNOWN_FAILURES")); @@ -167,30 +168,21 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, msg = $"App size changed significantly ({FormatBytes (appSizeDifference, true)} different > tolerance of +-{FormatBytes (toleranceInBytes)}). Expected app size: {FormatBytes (expectedAppBundleSize)}, actual app size: {FormatBytes (appBundleSize)}."; } - var updated = false; - if (forceUpdate || (update && !withinTolerance)) { - Directory.CreateDirectory (expectedDirectory); - File.WriteAllText (expectedSizeReportPath, report.ToString ()); - msg += " Check the modified files for more information."; - updated = true; - } else if (!withinTolerance) { - msg += " Set the environment variable WRITE_KNOWN_FAILURES=1, run the test again, and verify the modified files for more information."; - } - Console.WriteLine ($" {msg}"); + // Compare individual files in the app bundle var expectedLines = expectedSizeReport.SplitLines ().Skip (2).Where (v => v.IndexOf (':') >= 0).ToDictionary (v => v [..v.IndexOf (':')], v => v [(v.IndexOf (':') + 1)..]); var actualLines = report.ToString ().SplitLines ().Skip (2).Where (v => v.IndexOf (':') >= 0).ToDictionary (v => v [..v.IndexOf (':')], v => v [(v.IndexOf (':') + 1)..]); var allKeys = expectedLines.Keys.Union (actualLines.Keys).OrderBy (v => v); + var filesAdded = new List (); + var filesRemoved = new List (); foreach (var key in allKeys) { if (!expectedLines.TryGetValue (key, out var expectedLine)) { Console.WriteLine ($" File '{key}' was added to app bundle: {actualLines [key]}"); - if (!updated) - Assert.Fail ($"The file '{key}' was added to the app bundle."); + filesAdded.Add (key); } else if (!actualLines.TryGetValue (key, out var actualLine)) { Console.WriteLine ($" File '{key}' was removed from app bundle: {expectedLine}"); - if (!updated) - Assert.Fail ($"The file '{key}' was removed from the app bundle."); + filesRemoved.Add (key); } else if (expectedLine != actualLine) { Console.WriteLine ($" File '{key}' changed in app bundle:"); Console.WriteLine ($" -{expectedLine}"); @@ -198,8 +190,27 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, } } - if (!updated && !withinTolerance) - Assert.Fail (msg); + // Determine if there are any meaningful differences + var hasFileDifferences = filesAdded.Count > 0 || filesRemoved.Count > 0; + var hasSizeDifference = !withinTolerance; + var hasDifferences = hasFileDifferences || hasSizeDifference; + + if (forceUpdate || (update && hasDifferences)) { + Directory.CreateDirectory (expectedDirectory); + File.WriteAllText (expectedSizeReportPath, report.ToString ()); + Console.WriteLine ($" Updated expected file: {expectedSizeReportPath}"); + } else if (hasDifferences) { + UploadUpdatedExpectedFile (expectedSizeReportPath, report.ToString ()); + if (hasFileDifferences) { + var details = new List (); + foreach (var key in filesAdded) + details.Add ($"added: '{key}'"); + foreach (var key in filesRemoved) + details.Add ($"removed: '{key}'"); + Assert.Fail ($"The app bundle's file list changed ({string.Join (", ", details)}). The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); + } + Assert.Fail ($"{msg} The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); + } } // Create a file with all the APIs that survived the trimmer; this can be useful to determine what is not trimmed away. @@ -238,9 +249,29 @@ void AssertAssemblyReport (ApplePlatform platform, string name, string appPath, } if (!update) { - Assert.That (addedAPIs, Is.Empty, "No added APIs (set the environment variable WRITE_KNOWN_FAILURES=1 and run the test again to update the expected set of APIs)"); - Assert.That (removedAPIs, Is.Empty, "No removed APIs (set the environment variable WRITE_KNOWN_FAILURES=1 and run the test again to update the expected set of APIs)"); + if (addedAPIs.Count > 0 || removedAPIs.Count > 0) { + UploadUpdatedExpectedFile (expectedFile, string.Join ('\n', preservedAPIs) + "\n"); + var updateMsg = " The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."; + Assert.That (addedAPIs, Is.Empty, "No added APIs." + updateMsg); + Assert.That (removedAPIs, Is.Empty, "No removed APIs." + updateMsg); + } + } + } + + static void UploadUpdatedExpectedFile (string expectedFilePath, string content) + { + var fileName = Path.GetFileName (expectedFilePath); + var artifactStagingDir = Environment.GetEnvironmentVariable ("BUILD_ARTIFACTSTAGINGDIRECTORY"); + string outputDir; + if (!string.IsNullOrEmpty (artifactStagingDir)) { + outputDir = Path.Combine (artifactStagingDir, "updated-expected-sizes"); + } else { + outputDir = Path.Combine (Cache.CreateTemporaryDirectory ("AppSizeTest"), "updated-expected-sizes"); } + Directory.CreateDirectory (outputDir); + var outputFile = Path.Combine (outputDir, fileName); + File.WriteAllText (outputFile, content); + Console.WriteLine ($" Updated expected file written to: {outputFile}"); } static string FormatBytes (long bytes, bool alwaysShowSign = false) diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt index 48f7aafc2dad..b2abcecdbae4 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt @@ -1,10 +1,10 @@ -AppBundleSize: 16,325,774 bytes (15,943.1 KB = 15.6 MB) +AppBundleSize: 16,321,420 bytes (15,938.9 KB = 15.6 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 4,134 bytes (4.0 KB = 0.0 MB) -Contents/Info.plist: 1,119 bytes (1.1 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 13,814,640 bytes (13,490.9 KB = 13.2 MB) +Contents/Info.plist: 1,085 bytes (1.1 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 13,810,240 bytes (13,486.6 KB = 13.2 MB) Contents/MonoBundle/aot-instances.aotdata.arm64: 1,045,032 bytes (1,020.5 KB = 1.0 MB) -Contents/MonoBundle/Microsoft.MacCatalyst.aotdata.arm64: 36,288 bytes (35.4 KB = 0.0 MB) +Contents/MonoBundle/Microsoft.MacCatalyst.aotdata.arm64: 36,368 bytes (35.5 KB = 0.0 MB) Contents/MonoBundle/Microsoft.MacCatalyst.dll: 50,688 bytes (49.5 KB = 0.0 MB) Contents/MonoBundle/runtimeconfig.bin: 1,481 bytes (1.4 KB = 0.0 MB) Contents/MonoBundle/SizeTestApp.aotdata.arm64: 1,552 bytes (1.5 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt index 726ac17a14c8..e639fde89acf 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt @@ -1,12 +1,12 @@ -AppBundleSize: 260,131,912 bytes (254,035.1 KB = 248.1 MB) +AppBundleSize: 260,129,862 bytes (254,033.1 KB = 248.1 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 67,868 bytes (66.3 KB = 0.1 MB) -Contents/Info.plist: 750 bytes (0.7 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 7,431,792 bytes (7,257.6 KB = 7.1 MB) +Contents/Info.plist: 716 bytes (0.7 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 7,430,800 bytes (7,256.6 KB = 7.1 MB) Contents/MonoBundle/.xamarin/osx-arm64/_Microsoft.macOS.TypeMap.dll: 4,847,616 bytes (4,734.0 KB = 4.6 MB) Contents/MonoBundle/.xamarin/osx-arm64/_SizeTestApp.TypeMap.dll: 3,072 bytes (3.0 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll: 893,192 bytes (872.3 KB = 0.9 MB) -Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: 38,524,416 bytes (37,621.5 KB = 36.7 MB) +Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: 38,523,904 bytes (37,621.0 KB = 36.7 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.Core.dll: 1,335,096 bytes (1,303.8 KB = 1.3 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll: 17,680 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.Win32.Primitives.dll: 16,144 bytes (15.8 KB = 0.0 MB) @@ -182,7 +182,7 @@ Contents/MonoBundle/.xamarin/osx-arm64/WindowsBase.dll: 16,688 bytes (16.3 KB = Contents/MonoBundle/.xamarin/osx-x64/_Microsoft.macOS.TypeMap.dll: 4,847,616 bytes (4,734.0 KB = 4.6 MB) Contents/MonoBundle/.xamarin/osx-x64/_SizeTestApp.TypeMap.dll: 3,072 bytes (3.0 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.CSharp.dll: 796,432 bytes (777.8 KB = 0.8 MB) -Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: 38,524,416 bytes (37,621.5 KB = 36.7 MB) +Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: 38,523,904 bytes (37,621.0 KB = 36.7 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.Core.dll: 1,166,648 bytes (1,139.3 KB = 1.1 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.dll: 17,680 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.Win32.Primitives.dll: 16,176 bytes (15.8 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt index aa1d0fe08b6c..4a3f73193711 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt @@ -1,12 +1,12 @@ -AppBundleSize: 3,616,438 bytes (3,531.7 KB = 3.4 MB) +AppBundleSize: 3,616,596 bytes (3,531.8 KB = 3.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 3,999 bytes (3.9 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,138 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,104 bytes (1.1 KB = 0.0 MB) Microsoft.tvOS.dll: 154,624 bytes (151.0 KB = 0.1 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,405 bytes (1.4 KB = 0.0 MB) -SizeTestApp: 2,404,416 bytes (2,348.1 KB = 2.3 MB) +SizeTestApp: 2,404,608 bytes (2,348.2 KB = 2.3 MB) SizeTestApp.dll: 7,680 bytes (7.5 KB = 0.0 MB) System.Private.CoreLib.aotdata.arm64: 41,312 bytes (40.3 KB = 0.0 MB) System.Private.CoreLib.dll: 988,160 bytes (965.0 KB = 0.9 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt index 756b1289a4de..e01435700c48 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 7,922,040 bytes (7,736.4 KB = 7.6 MB) +AppBundleSize: 7,922,198 bytes (7,736.5 KB = 7.6 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,138 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,104 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,889 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 7,916,032 bytes (7,730.5 KB = 7.5 MB) +SizeTestApp: 7,916,224 bytes (7,730.7 KB = 7.5 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt index ebe56d99fd5b..93230234dc12 100644 --- a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt @@ -1,12 +1,12 @@ -AppBundleSize: 3,616,747 bytes (3,532.0 KB = 3.4 MB) +AppBundleSize: 3,603,385 bytes (3,518.9 KB = 3.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 3,997 bytes (3.9 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,161 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,127 bytes (1.1 KB = 0.0 MB) Microsoft.iOS.dll: 154,624 bytes (151.0 KB = 0.1 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,405 bytes (1.4 KB = 0.0 MB) -SizeTestApp: 2,404,704 bytes (2,348.3 KB = 2.3 MB) +SizeTestApp: 2,391,376 bytes (2,335.3 KB = 2.3 MB) SizeTestApp.dll: 7,680 bytes (7.5 KB = 0.0 MB) System.Private.CoreLib.aotdata.arm64: 41,312 bytes (40.3 KB = 0.0 MB) System.Private.CoreLib.dll: 988,160 bytes (965.0 KB = 0.9 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt index 9977114c7ece..5cc462cd43e5 100644 --- a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 2,800,030 bytes (2,734.4 KB = 2.7 MB) +AppBundleSize: 2,784,380 bytes (2,719.1 KB = 2.7 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,161 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,127 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 2,794,080 bytes (2,728.6 KB = 2.7 MB) +SizeTestApp: 2,778,464 bytes (2,713.3 KB = 2.6 MB) diff --git a/tools/devops/automation/templates/tests/run-tests.yml b/tools/devops/automation/templates/tests/run-tests.yml index 188474aa28ea..40c85db23a1a 100644 --- a/tools/devops/automation/templates/tests/run-tests.yml +++ b/tools/devops/automation/templates/tests/run-tests.yml @@ -172,6 +172,15 @@ steps: continueOnError: true condition: succeededOrFailed() +# Upload updated expected app size files if the app size tests produced any. +- task: PublishPipelineArtifact@1 + displayName: 'Publish Artifact: Updated expected app size files' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)/updated-expected-sizes' + artifactName: '${{ parameters.uploadPrefix }}updated-expected-sizes-${{ parameters.testPrefix }}-$(System.JobAttempt)' + continueOnError: true + condition: succeededOrFailed() + - pwsh: | $summaryName = "TestSummary-${{ parameters.testPrefix }}.md" $summaryPath = "$Env:SYSTEM_DEFAULTWORKINGDIRECTORY/$(BUILD_REPOSITORY_TITLE)/tests/TestSummary.md" From 26af1ee903a23bb2c8def747be34ae4ac8e84fcf Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 25 May 2026 18:23:03 +0200 Subject: [PATCH 10/97] [msbuild] Improve how we handle the path to Xcode. (#24021) * Add support for an MSBuild property ('XcodeLocation') to set the Xcode location. * Use a new facility in macios-devtools (the 'XcodeLocator' class) to find Xcode. This required a bump to the corresponding submodule. * Deprecate the file-based locations. * Always set DEVELOPER_DIR when executing subprocesses. * Don't use static state to store the Xcode location. This fixes a problem where the static state would remain in memory when MSBuild's build server is being used, and then we'd do the wrong thing when building a different project using a different version of Xcode. Misc improvements: * We now warn if the selected Xcode is a symlink (ref: #21762). * The settings files are deprecated, and won't be used in .NET 11 (we'll warn about this in .NET 10). * The environment variable MD_APPLE_SDK_ROOT is deprecated, and won't be used in .NET 11 (we'll warn about this in .NET 10). This will hopefully remove some confusion about which Xcode is being used; now we just look at the system's Xcode, unless it's overridden by the MSBuild property 'XcodeLocation'. Fixes https://github.com/dotnet/macios/issues/3931. Fixes https://github.com/dotnet/macios/issues/11172. Fixes https://github.com/dotnet/macios/issues/21762. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Rolf Bjarne Kvinge --- Make.config | 1 - docs/building-apps/build-properties.md | 15 +++++ .../MSBStrings.resx | 16 +++++ msbuild/Xamarin.MacDev.Tasks/Sdks.cs | 61 ------------------- .../Tasks/CompileAppManifest.cs | 14 ++--- .../Tasks/CompileEntitlements.cs | 2 +- .../Tasks/DetectSdkLocation.cs | 60 +++++++++--------- .../Tasks/DetectSigningIdentity.cs | 8 +-- .../Tasks/ReadAppManifest.cs | 2 +- .../Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs | 46 ++++++++++++++ msbuild/Xamarin.Shared/Xamarin.Shared.targets | 5 ++ tests/common/BinLog.cs | 3 + tests/common/Configuration.cs | 2 +- tests/dotnet/UnitTests/XcodeVersionTest.cs | 13 ++-- .../AssemblySetup.cs | 2 +- .../TaskTests/ACToolTaskTest.cs | 5 +- .../TaskTests/CompileAppManifestTaskTests.cs | 2 +- .../GeneratePlistTaskTests_Core.cs | 1 + .../GeneratePlistTaskTests_iOS.cs | 2 +- .../GeneratePlistTaskTests_tvOS.cs | 2 +- .../TaskTests/IBToolTaskTests.cs | 15 +++-- tests/package-mac-tests.sh | 2 +- .../Jenkins/TestTasks/AppleTestTask.cs | 2 +- 23 files changed, 146 insertions(+), 135 deletions(-) delete mode 100644 msbuild/Xamarin.MacDev.Tasks/Sdks.cs diff --git a/Make.config b/Make.config index 37e7563a336d..1a27f14f8f87 100644 --- a/Make.config +++ b/Make.config @@ -239,7 +239,6 @@ endif # Tell both Xcode and our build logic which Xcode we're using. export DEVELOPER_DIR=$(XCODE_DEVELOPER_ROOT) -export MD_APPLE_SDK_ROOT=$(abspath $(XCODE_DEVELOPER_ROOT)/../..) else # Without Xcode, set placeholder Xcode values but honor XCODE_CHANNEL so we configure bots correctly ifeq ($(XCODE_CHANNEL),Beta) diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 8126bb6cd0ed..461d2986fdca 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -1505,6 +1505,21 @@ Consider using the unified [AppBundleResourcePrefix](#appbundleresourceprefix) p See also [IPhoneResourcePrefix](#iphoneresourceprefix) and [MonoMacResourcePrefix](#monomacresourceprefix). +## XcodeLocation + +Specifies the location of Xcode. + +When the build searches for Xcode, it's done in this order: + +1. If the `XcodeLocation` property is set, use that. Note that since all environment variables are automatically MSBuild properties as well, it's also possible to set the `XcodeLocation` environment variable for the same effect. +2. If the `MD_APPLE_SDK_ROOT` environment variable is set, use that. +3. If either of the files `~/Library/Preferences/maui/Settings.plist` or `~/Library/Preferences/Xamarin/Settings.plist` exist, and has the property list value `AppleSdkRoot`, use that. +4. Use the system version of Xcode (as determined by executing `xcode-select --print-path`). + +> [!WARNING] +> Support for the `MD_APPLE_SDK_ROOT` environment variable, and the `~/Library/Preferences/maui/Settings.plist` and `~/Library/Preferences/Xamarin/Settings.plist` files, is deprecated and will be removed in the future. +> Going forward, choose which Xcode to use by either making it the system's version of Xcode (either using `xcode-select --switch ...` on the command line, or in Xcode's settings), or by setting the `XcodeLocation` MSBuild property / environment variable. + ## ZipPath The full path to the `zip` command-line tool. diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index f85052143906..5f68eea415e1 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1627,4 +1627,20 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + diff --git a/msbuild/Xamarin.MacDev.Tasks/Sdks.cs b/msbuild/Xamarin.MacDev.Tasks/Sdks.cs deleted file mode 100644 index ca04c9c665f1..000000000000 --- a/msbuild/Xamarin.MacDev.Tasks/Sdks.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.IO; - -using Xamarin.Localization.MSBuild; -using Xamarin.Utils; -using Xamarin.MacDev.Tasks; - -#nullable enable - -namespace Xamarin.MacDev { - public static class Sdks { - public static AppleIPhoneSdk IOS { get; private set; } - public static MacOSXSdk MacOS { get; private set; } - public static AppleTVOSSdk TVOS { get; private set; } - - static Sdks () - { - IOS = new AppleIPhoneSdk (AppleSdkSettings.DeveloperRoot, AppleSdkSettings.DeveloperRootVersionPlist); - TVOS = new AppleTVOSSdk (AppleSdkSettings.DeveloperRoot, AppleSdkSettings.DeveloperRootVersionPlist); - MacOS = new MacOSXSdk (AppleSdkSettings.DeveloperRoot, AppleSdkSettings.DeveloperRootVersionPlist); - } - - public static AppleSdk GetSdk (ApplePlatform framework) - { - switch (framework) { - case ApplePlatform.iOS: - return IOS; - case ApplePlatform.TVOS: - return TVOS; - default: - throw new InvalidOperationException (string.Format (MSBStrings.InvalidFramework, framework)); - } - } - - public static AppleSdk GetSdk (string targetFrameworkMoniker) - { - return GetSdk (PlatformFrameworkHelper.GetFramework (targetFrameworkMoniker)); - } - - public static IAppleSdk GetAppleSdk (ApplePlatform framework) - { - switch (framework) { - case ApplePlatform.iOS: - return IOS; - case ApplePlatform.TVOS: - return TVOS; - case ApplePlatform.MacCatalyst: - case ApplePlatform.MacOSX: - return MacOS; - default: - throw new InvalidOperationException (string.Format (MSBStrings.InvalidFramework, framework)); - } - } - - public static IAppleSdk GetAppleSdk (string targetFrameworkMoniker) - { - return GetAppleSdk (PlatformFrameworkHelper.GetFramework (targetFrameworkMoniker)); - } - - } -} diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs index fa798ad621be..f9650087d607 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs @@ -236,7 +236,7 @@ bool SetMinimumOSVersion (PDictionary plist) if (Platform == ApplePlatform.MacCatalyst && !string.IsNullOrEmpty (SupportedOSPlatformVersion)) { // SupportedOSPlatformVersion is the iOS version for Mac Catalyst. // But we need to store the macOS version in the app manifest, so convert it to the macOS version here. - if (!MacCatalystSupport.TryGetMacOSVersion (Sdks.GetAppleSdk (Platform).GetSdkPath (SdkVersion), SupportedOSPlatformVersion, out var convertedVersion, out var knowniOSVersions)) { + if (!MacCatalystSupport.TryGetMacOSVersion (CurrentSdk.GetSdkPath (SdkVersion), SupportedOSPlatformVersion, out var convertedVersion, out var knowniOSVersions)) { Log.LogError (MSBStrings.E0188, SupportedOSPlatformVersion, string.Join (", ", knowniOSVersions.OrderBy (v => v))); return false; } @@ -250,7 +250,7 @@ bool SetMinimumOSVersion (PDictionary plist) var minimumiOSVersionInManifest = plist.Get (ManifestKeys.MinimumOSVersion)?.Value; if (!string.IsNullOrEmpty (minimumiOSVersionInManifest)) { // Convert to the macOS version - if (!MacCatalystSupport.TryGetMacOSVersion (Sdks.GetAppleSdk (Platform).GetSdkPath (SdkVersion), minimumiOSVersionInManifest!, out var convertedVersion, out var knowniOSVersions)) { + if (!MacCatalystSupport.TryGetMacOSVersion (CurrentSdk.GetSdkPath (SdkVersion), minimumiOSVersionInManifest!, out var convertedVersion, out var knowniOSVersions)) { Log.LogError (MSBStrings.E0188, minimumiOSVersionInManifest, string.Join (", ", knowniOSVersions.OrderBy (v => v))); return false; } @@ -309,14 +309,12 @@ bool Compile (PDictionary plist) return false; } - var currentSDK = Sdks.GetAppleSdk (Platform); - sdkVersion = AppleSdkVersion.Parse (DefaultSdkVersion); - if (!currentSDK.SdkIsInstalled (sdkVersion, SdkIsSimulator)) { + if (!CurrentSdk.SdkIsInstalled (sdkVersion, SdkIsSimulator)) { Log.LogError (null, null, null, null, 0, 0, 0, 0, MSBStrings.E0013, Platform, sdkVersion); return false; } - SetXcodeValues (plist, currentSDK); + SetXcodeValues (plist, CurrentSdk); } switch (Platform) { @@ -424,7 +422,7 @@ void Validation (PDictionary plist) GetMinimumOSVersion (plist, out var minimumOSVersion); if (minimumOSVersion < new Version (11, 0)) { string miniOSVersion = "?"; - if (MacCatalystSupport.TryGetiOSVersion (Sdks.GetAppleSdk (Platform).GetSdkPath (SdkVersion), minimumOSVersion, out var iOSVersion, out var _)) + if (MacCatalystSupport.TryGetiOSVersion (CurrentSdk.GetSdkPath (SdkVersion), minimumOSVersion, out var iOSVersion, out var _)) miniOSVersion = iOSVersion?.ToString () ?? "?"; LogAppManifestError (MSBStrings.E7099 /* The UIDeviceFamily value '6' requires macOS 11.0. Please set the 'SupportedOSPlatformVersion' in the project file to at least 14.0 (the Mac Catalyst version equivalent of macOS 11.0). The current value is {0} (equivalent to macOS {1}). */, miniOSVersion, minimumOSVersion); } @@ -510,7 +508,7 @@ void SetXcodeValues (PDictionary plist, IAppleSdk currentSDK) SetValueIfNotNull (plist, "DTPlatformName", PlatformUtils.GetTargetPlatform (SdkPlatform, false)); SetValueIfNotNull (plist, "DTPlatformVersion", dtSettings.DTPlatformVersion); SetValueIfNotNull (plist, "DTSDKName", sdkSettings.CanonicalName); - SetValueIfNotNull (plist, "DTXcode", AppleSdkSettings.DTXcode); + SetValueIfNotNull (plist, "DTXcode", GetXcodeLocator ().DTXcode); SetValueIfNotNull (plist, "DTXcodeBuild", dtSettings.DTXcodeBuild); } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs index 3f5ac630ef5c..3e2e85074957 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs @@ -117,7 +117,7 @@ string DefaultEntitlementsPath { return "Entitlements.plist"; } - return Path.Combine (Sdks.GetAppleSdk (TargetFrameworkMoniker).GetSdkPath (SdkVersion, false), "Entitlements.plist"); + return Path.Combine (CurrentSdk.GetSdkPath (SdkVersion, false), "Entitlements.plist"); } } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSdkLocation.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSdkLocation.cs index 1f393504e6a8..f06f132d6a7b 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSdkLocation.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSdkLocation.cs @@ -29,6 +29,7 @@ public string SdkRoot { get; set; } = ""; + // this is input too (the variable 'XcodeLocation') [Output] public new string SdkDevPath { get => base.SdkDevPath; @@ -59,12 +60,6 @@ public string XcodeVersion { #endregion Outputs - protected IAppleSdk CurrentSdk { - get { - return Sdks.GetAppleSdk (Platform); - } - } - IAppleSdkVersion GetDefaultSdkVersion () { switch (Platform) { @@ -145,33 +140,40 @@ bool ExecuteImpl () return ExecuteRemotely (); } - AppleSdkSettings.Init (); - - if (EnsureAppleSdkRoot ()) - EnsureSdkPath (); - EnsureXamarinSdkRoot (); + var isNet11OrNewer = TargetFramework.Version.Major >= 11; + var appleSdkSettings = GetXcodeLocator (initialDiscovery: true, (locator) => { + locator.SupportEnvironmentVariableLookup = !isNet11OrNewer; + locator.SupportSettingsFileLookup = !isNet11OrNewer; + }); + SetXcodeLocator (appleSdkSettings); + SdkDevPath = appleSdkSettings.DeveloperRoot; + XcodeVersion = appleSdkSettings.XcodeVersion.ToString (); + + if (appleSdkSettings.SystemHasEnvironmentVariable) { + if (isNet11OrNewer) { + Log.LogWarning (MSBStrings.W7172 /* The environment variable '{0}' is deprecated, and will be ignored. Please set use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. */, XcodeLocator.EnvironmentVariableName); + } else { + Log.LogWarning (MSBStrings.W7171 /* The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please set use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. */, XcodeLocator.EnvironmentVariableName); + } + } + foreach (var file in appleSdkSettings.SystemExistingSettingsFiles) { + if (isNet11OrNewer) { + Log.LogWarning (MSBStrings.W7174 /* The settings file '{0}' is deprecated, and will be ignored. Please set use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. */, file); + } else { + Log.LogWarning (MSBStrings.W7173 /* The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please set use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. */, file); + } + } - XcodeVersion = AppleSdkSettings.XcodeVersion.ToString (); + if (Log.HasLoggedErrors) + return false; - return !Log.HasLoggedErrors; - } + Log.LogMessage (MessageImportance.Low, "DeveloperRoot: {0}", CurrentSdk.DeveloperRoot); + Log.LogMessage (MessageImportance.Low, "GetPlatformPath: {0}", CurrentSdk.GetPlatformPath (SdkIsSimulator)); - protected bool EnsureAppleSdkRoot () - { - var currentSdk = CurrentSdk; - if (!currentSdk.IsInstalled) { - Log.LogError (MSBStrings.E0044v2 /* Could not find a valid Xcode app bundle at '{0}'. Please verify that 'xcode-select -p' points to your Xcode installation. For more information see https://aka.ms/macios-missing-xcode. */, AppleSdkSettings.InvalidDeveloperRoot); - return false; - } - Log.LogMessage (MessageImportance.Low, "DeveloperRoot: {0}", currentSdk.DeveloperRoot); - Log.LogMessage (MessageImportance.Low, "GetPlatformPath: {0}", currentSdk.GetPlatformPath (SdkIsSimulator)); + EnsureSdkPath (); + EnsureXamarinSdkRoot (); - SdkDevPath = currentSdk.DeveloperRoot; - if (string.IsNullOrEmpty (SdkDevPath)) { - Log.LogError (MSBStrings.E0086 /* Could not find a valid Xcode developer path */); - return false; - } - return true; + return !Log.HasLoggedErrors; } protected string? DirExists (string checkingFor, params string [] paths) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSigningIdentity.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSigningIdentity.cs index 224c8e37edc4..8718934d6918 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSigningIdentity.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSigningIdentity.cs @@ -29,12 +29,6 @@ public class DetectSigningIdentity : XamarinTask, ITaskCallback, ICancelableTask static readonly string [] macDirectDistributionPrefixes = { "Developer ID Application" }; static readonly string [] macDevelopmentPrefixes = { "Mac Developer", "Apple Development" }; - protected string DeveloperRoot { - get { - return Sdks.GetAppleSdk (TargetFrameworkMoniker).DeveloperRoot; - } - } - protected string [] DevelopmentPrefixes { get { switch (Platform) { @@ -583,7 +577,7 @@ bool ExecuteImpl () else if (ProvisioningProfile == AutomaticAdHocProvision) type = MobileProvisionDistributionType.AdHoc; - DetectedCodesignAllocate = Path.Combine (DeveloperRoot, "Toolchains", "XcodeDefault.xctoolchain", "usr", "bin", "codesign_allocate"); + DetectedCodesignAllocate = Path.Combine (CurrentSdk.DeveloperRoot, "Toolchains", "XcodeDefault.xctoolchain", "usr", "bin", "codesign_allocate"); DetectedDistributionType = type.ToString (); identity.BundleId = BundleIdentifier; diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ReadAppManifest.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ReadAppManifest.cs index ed31af9848f6..6e70959e2d15 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ReadAppManifest.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ReadAppManifest.cs @@ -73,7 +73,7 @@ public override bool Execute () if (Platform == ApplePlatform.MacCatalyst) { // The minimum version in the Info.plist is the macOS version. However, the rest of our tooling // expects the iOS version, so expose that. - if (!MacCatalystSupport.TryGetiOSVersion (Sdks.GetAppleSdk (Platform).GetSdkPath (), MinimumOSVersion!, out var convertedVersion, out var knownMacOSVersions)) + if (!MacCatalystSupport.TryGetiOSVersion (CurrentSdk.GetSdkPath (), MinimumOSVersion!, out var convertedVersion, out var knownMacOSVersions)) Log.LogError (MSBStrings.E0187, MinimumOSVersion, string.Join (", ", knownMacOSVersions.OrderBy (v => v))); MinimumOSVersion = convertedVersion; } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs index 1313a4d5e902..2ad8f996c312 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs @@ -35,6 +35,52 @@ public string GetSdkDevPath () return SdkDevPath; } + XcodeLocator? xcodeLocator = null; + public XcodeLocator GetXcodeLocator (bool initialDiscovery = false, Action? preprocess = null) + { + if (xcodeLocator is null) { + if (!initialDiscovery && string.IsNullOrEmpty (SdkDevPath)) { + Log.LogError (MSBStrings.E7169, /* The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. */ GetType ().Name, "SdkDevPath"); + } + + var xcodeLocator = new XcodeLocator (this); + preprocess?.Invoke (xcodeLocator); + if (!xcodeLocator.TryLocatingXcode (SdkDevPath)) + Log.LogError (MSBStrings.E0086 /* Could not find a valid Xcode developer path */); + this.xcodeLocator = xcodeLocator; + } + return xcodeLocator; + } + + protected void SetXcodeLocator (XcodeLocator xcodeLocator) + { + this.xcodeLocator = xcodeLocator; + } + + IAppleSdk? currentSdk; + public IAppleSdk CurrentSdk { + get { + if (currentSdk is null) { + var xcodeLocator = GetXcodeLocator (); + switch (Platform) { + case ApplePlatform.iOS: + currentSdk = new AppleIPhoneSdk (xcodeLocator.DeveloperRoot, xcodeLocator.DeveloperRootVersionPlist); + break; + case ApplePlatform.TVOS: + currentSdk = new AppleTVOSSdk (xcodeLocator.DeveloperRoot, xcodeLocator.DeveloperRootVersionPlist); + break; + case ApplePlatform.MacCatalyst: + case ApplePlatform.MacOSX: + currentSdk = new MacOSXSdk (xcodeLocator.DeveloperRoot, xcodeLocator.DeveloperRootVersionPlist); + break; + default: + throw new InvalidOperationException (string.Format (MSBStrings.InvalidPlatform, Platform)); + } + } + return currentSdk; + } + } + void VerifyTargetFrameworkMoniker () { if (!string.IsNullOrEmpty (TargetFrameworkMoniker)) diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 1c5787205d7e..d9965c1b6db8 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -684,6 +684,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. ProjectDir="$(MSBuildProjectDirectory)" ResourcePrefix="$(_ResourcePrefix)" ResourceRules="$(_PreparedResourceRules)" + SdkDevPath="$(_SdkDevPath)" SdkIsSimulator="$(_SdkIsSimulator)" SdkVersion="$(_SdkVersion)" SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)" @@ -710,6 +711,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. Condition="'$(IsMacEnabled)' == 'true' And '$(_BundleOriginalResources)' != 'true'" SessionId="$(BuildSessionId)" AppManifest="$(_TemporaryAppManifest)" + SdkDevPath="$(_SdkDevPath)" TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)" > @@ -797,6 +799,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. Entitlements="$(CodesignEntitlements)" CompiledEntitlements="$(_CompiledEntitlementsPath)" ProvisioningProfile="$(_ProvisioningProfile)" + SdkDevPath="$(_SdkDevPath)" SdkIsSimulator="$(_SdkIsSimulator)" SdkPlatform="$(_SdkPlatform)" SdkVersion="$(_SdkVersion)" @@ -2069,6 +2072,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. SdkIsSimulator="$(_SdkIsSimulator)" SdkPlatform="$(_SdkPlatform)" ProvisioningProfile="$(CodesignProvision)" + SdkDevPath="$(_SdkDevPath)" SigningKey="$(_SpecifiedCodesignKey)" DetectedCodeSigningKey="$(_CodeSigningKey)" TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)" @@ -2091,6 +2095,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. GetBuildLogWarnings (string path) .Where (v => v.Type == BuildLogEventType.Warning) // We're often referencing earlier .NET projects (Touch.Unit/MonoTouch.Dialog), so ignore any out-of-support warnings. .Where (v => v.Message?.Contains ("is out of support and will not receive security updates in the future") != true) + // Ignore any messages about the settings files, we get *a lot* of those + .Where (v => !(v.Message?.Contains ("The settings file '/Users/") == true && v.Message?.Contains ("/Library/Preferences/maui/Settings.plist' is deprecated, and will be ignored in .NET 11+. ") == true)) + .Where (v => !(v.Message?.Contains ("The settings file '/Users/") == true && v.Message?.Contains ("/Library/Preferences/Xamarin/Settings.plist' is deprecated, and will be ignored in .NET 11+. ") == true)) ; } diff --git a/tests/common/Configuration.cs b/tests/common/Configuration.cs index 3e048d52eab1..8154d4e9b100 100644 --- a/tests/common/Configuration.cs +++ b/tests/common/Configuration.cs @@ -694,7 +694,7 @@ public static void SetBuildVariables (ApplePlatform platform, [NotNullIfNotNull if (environment is null) environment = new Dictionary (); - environment ["MD_APPLE_SDK_ROOT"] = Path.GetDirectoryName (Path.GetDirectoryName (xcode_root)!)!; + environment ["DEVELOPER_DIR"] = Path.GetDirectoryName (Path.GetDirectoryName (xcode_root)!)!; // This is set by `dotnet test` and can cause building legacy projects to fail to build with: // Microsoft.NET.Build.Extensions.ConflictResolution.targets(30,5): diff --git a/tests/dotnet/UnitTests/XcodeVersionTest.cs b/tests/dotnet/UnitTests/XcodeVersionTest.cs index a365af92f75e..301eb5e57fe0 100644 --- a/tests/dotnet/UnitTests/XcodeVersionTest.cs +++ b/tests/dotnet/UnitTests/XcodeVersionTest.cs @@ -35,16 +35,11 @@ public void Test (ApplePlatform platform, string xcodePath, Version xcodeVersion Clean (project_path); var properties = GetDefaultProperties (runtimeIdentifiers); properties ["BundleOriginalResources"] = "true"; // this prevents a different and unrelated error message from failing to build library projects + properties ["XcodeLocation"] = xcodePath; - var existingDeveloperDir = Environment.GetEnvironmentVariable ("MD_APPLE_SDK_ROOT"); - try { - Environment.SetEnvironmentVariable ("MD_APPLE_SDK_ROOT", Path.GetDirectoryName (Path.GetDirectoryName (xcodePath))); - var rv = DotNet.AssertBuildFailure (project_path, properties); - var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).ToArray (); - AssertErrorMessages (errors, $"This version of .NET for {platform.AsString ()} ({Configuration.GetNuGetVersionNoMetadata (platform)}) requires Xcode {Configuration.XcodeVersion}. The current version of Xcode is {xcodeVersion}. Either install Xcode {Configuration.XcodeVersion}, or use a different version of .NET for {platform.AsString ()}. See https://aka.ms/xcode-requirement for more information."); - } finally { - Environment.SetEnvironmentVariable ("MD_APPLE_SDK_ROOT", existingDeveloperDir); - } + var rv = DotNet.AssertBuildFailure (project_path, properties); + var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).ToArray (); + AssertErrorMessages (errors, $"This version of .NET for {platform.AsString ()} ({Configuration.GetNuGetVersionNoMetadata (platform)}) requires Xcode {Configuration.XcodeVersion}. The current version of Xcode is {xcodeVersion}. Either install Xcode {Configuration.XcodeVersion}, or use a different version of .NET for {platform.AsString ()}. See https://aka.ms/xcode-requirement for more information."); } } } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs index db3cb3520b67..5059aab86236 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs @@ -17,7 +17,7 @@ public void AssemblyInitialization () const string msbuild_exe_path = "/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/msbuild/15.0/bin/MSBuild.dll"; if (is_in_vsmac) { var env = new Dictionary { - { "MD_APPLE_SDK_ROOT", Path.GetDirectoryName (Path.GetDirectoryName (Configuration.xcode_root)) ?? string.Empty }, + { "DEVELOPER_DIR", Path.GetDirectoryName (Path.GetDirectoryName (Configuration.xcode_root)) ?? string.Empty }, { "MSBUILD_EXE_PATH", msbuild_exe_path }, }; diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs index f0f790709c06..e41ff90d614e 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs @@ -22,9 +22,9 @@ ACTool CreateACToolTask (ApplePlatform platform, string projectDir, out string i intermediateOutputPath = Cache.CreateTemporaryDirectory (); - var sdk = Sdks.GetAppleSdk (platform); + var task = CreateTask (); + var version = AppleSdkVersion.UseDefault.ToString (); - var root = sdk.GetSdkPath (version, false); string sdkPlatform; var uiDeviceFamily = ""; @@ -47,7 +47,6 @@ ACTool CreateACToolTask (ApplePlatform platform, string projectDir, out string i throw new NotImplementedException (platform.ToString ()); } - var task = CreateTask (); task.ImageAssets = imageAssets .Select (v => { var spl = v.Split ('|'); diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs index 963fc4e4ad83..e07a1d80c94d 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs @@ -20,11 +20,11 @@ CompileAppManifest CreateTask (string? tmpdir = null, ApplePlatform platform = A var task = CreateTask (); task.AppBundleName = "AppBundleName"; task.CompiledAppManifest = new TaskItem (Path.Combine (tmpdir, "TemporaryAppManifest.plist")); - task.DefaultSdkVersion = Sdks.GetAppleSdk (platform).GetInstalledSdkVersions (false).First ().ToString ()!; task.MinSupportedOSPlatformVersion = "10.0"; task.SupportedOSPlatformVersion = "15.0"; task.SdkVersion = task.DefaultSdkVersion ?? string.Empty; task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform).ToString (); + task.DefaultSdkVersion = task.CurrentSdk.GetInstalledSdkVersions (false).First ().ToString ()!; return task; } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_Core.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_Core.cs index c98886bb4f40..f6b0181548aa 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_Core.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_Core.cs @@ -46,6 +46,7 @@ protected virtual void ConfigureTask () Task.MinSupportedOSPlatformVersion = "10.0"; Task.SupportedOSPlatformVersion = "15.0"; Task.SdkVersion = "10.0"; + Task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (Platform).ToString (); Plist = new PDictionary (); Plist ["CFBundleDisplayName"] = displayName; diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_iOS.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_iOS.cs index 144e00166d4f..eec87ffd32f5 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_iOS.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_iOS.cs @@ -15,7 +15,7 @@ protected override void ConfigureTask () Configuration.IgnoreIfIgnoredPlatform (ApplePlatform.iOS); base.ConfigureTask (); - Task.DefaultSdkVersion = Sdks.IOS.GetClosestInstalledSdk (AppleSdkVersion.V6_1, true).ToString (); + Task.DefaultSdkVersion = Task.CurrentSdk.GetClosestInstalledSdk (AppleSdkVersion.V6_1, true).ToString () ?? ""; Task.TargetFrameworkMoniker = TargetFramework.DotNet_iOS_String; Task.TargetArchitectures = "ARM64"; } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_tvOS.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_tvOS.cs index b77904722891..06ab59fadfbe 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_tvOS.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GeneratePlistTaskTests/GeneratePlistTaskTests_tvOS.cs @@ -14,7 +14,7 @@ protected override void ConfigureTask () Configuration.IgnoreIfIgnoredPlatform (ApplePlatform.TVOS); base.ConfigureTask (); - Task.DefaultSdkVersion = Sdks.TVOS.GetClosestInstalledSdk (AppleSdkVersion.V9_0, true).ToString (); + Task.DefaultSdkVersion = Task.CurrentSdk.GetClosestInstalledSdk (AppleSdkVersion.V9_0, true).ToString () ?? ""; Task.TargetFrameworkMoniker = TargetFramework.DotNet_tvOS_String; } } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs index 54819e3ef1cf..2b66ae4a5f93 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs @@ -18,10 +18,10 @@ namespace Xamarin.MacDev.Tasks { public class IBToolTaskTests : TestBase { IBTool CreateIBToolTask (ApplePlatform framework, string projectDir, string intermediateOutputPath) { + var task = CreateTask (); + var interfaceDefinitions = new List (); - var sdk = Sdks.GetSdk (framework); - var version = AppleSdkVersion.GetDefault (sdk, false); - var root = sdk.GetSdkPath (version, false); + var version = AppleSdkVersion.UseDefault.ToString (); string platform; switch (framework) { @@ -39,7 +39,6 @@ IBTool CreateIBToolTask (ApplePlatform framework, string projectDir, string inte foreach (var item in Directory.EnumerateFiles (projectDir, "*.xib", SearchOption.AllDirectories)) interfaceDefinitions.Add (new TaskItem (item)); - var task = CreateTask (); task.InterfaceDefinitions = interfaceDefinitions.ToArray (); task.IntermediateOutputPath = intermediateOutputPath; task.MinimumOSVersion = PDictionary.OpenFile (Path.Combine (projectDir, "Info.plist")).GetMinimumOSVersion (); @@ -48,8 +47,8 @@ IBTool CreateIBToolTask (ApplePlatform framework, string projectDir, string inte task.SdkDevPath = Configuration.xcode_root; task.SdkPlatform = platform; task.SdkVersion = version.ToString (); - task.SdkRoot = root; task.TargetFrameworkMoniker = TargetFramework.DotNet_iOS_String; + task.SdkRoot = task.CurrentSdk.GetSdkPath (version, false); return task; } @@ -62,7 +61,7 @@ public void TestBasicIBToolFunctionality () var ibtool = CreateIBToolTask (ApplePlatform.iOS, srcdir, tmp); var bundleResources = new HashSet (); - Assert.That (ibtool.Execute (), Is.True, "Execution of IBTool task failed."); + ExecuteTask (ibtool); foreach (var bundleResource in ibtool.BundleResources) { Assert.That (File.Exists (bundleResource.ItemSpec), Is.True, $"File does not exist: {bundleResource.ItemSpec}"); @@ -104,7 +103,7 @@ public void TestAdvancedIBToolFunctionality () ibtool.EnableOnDemandResources = true; - Assert.That (ibtool.Execute (), Is.True, "Execution of IBTool task failed."); + ExecuteTask (ibtool); foreach (var bundleResource in ibtool.BundleResources) { var bundleName = bundleResource.GetMetadata ("LogicalName"); @@ -179,7 +178,7 @@ void TestGenericAndDeviceSpecificXibsGeneric (params string [] fileNames) ibtool.EnableOnDemandResources = true; - Assert.That (ibtool.Execute (), Is.True, "Execution of IBTool task failed."); + ExecuteTask (ibtool); foreach (var bundleResource in ibtool.BundleResources) { var bundleName = bundleResource.GetMetadata ("LogicalName"); diff --git a/tests/package-mac-tests.sh b/tests/package-mac-tests.sh index ce02bb0d67eb..0b8dc92d6dab 100755 --- a/tests/package-mac-tests.sh +++ b/tests/package-mac-tests.sh @@ -19,7 +19,7 @@ cat test.config INCLUDE_MAC=$(grep ^INCLUDE_MAC= test.config | sed 's/.*=//') INCLUDE_MACCATALYST=$(grep ^INCLUDE_MACCATALYST= test.config | sed 's/.*=//') XCODE_DEVELOPER_ROOT=$(grep ^XCODE_DEVELOPER_ROOT= test.config | sed 's/.*=//') -export MD_APPLE_SDK_ROOT="$(dirname "$(dirname "$XCODE_DEVELOPER_ROOT")")" +export DEVELOPER_DIR="$(dirname "$(dirname "$XCODE_DEVELOPER_ROOT")")" export RootTestsDirectory="$(pwd)" make diff --git a/tests/xharness/Jenkins/TestTasks/AppleTestTask.cs b/tests/xharness/Jenkins/TestTasks/AppleTestTask.cs index 896e9a506d18..851fb2a9d42e 100644 --- a/tests/xharness/Jenkins/TestTasks/AppleTestTask.cs +++ b/tests/xharness/Jenkins/TestTasks/AppleTestTask.cs @@ -35,7 +35,7 @@ public override void SetEnvironmentVariables (Process process) var xcodeRoot = Jenkins.Harness.XcodeRoot; process.StartInfo.EnvironmentVariables ["RootTestsDirectory"] = HarnessConfiguration.RootDirectory; - process.StartInfo.EnvironmentVariables ["MD_APPLE_SDK_ROOT"] = xcodeRoot; + process.StartInfo.EnvironmentVariables ["DEVELOPER_DIR"] = xcodeRoot; foreach (var kvp in Environment) { if (kvp.Value is null) { From 58f44c5ff83b82040ee332ec25e11922ac17691b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 26 May 2026 05:06:36 +0200 Subject: [PATCH 11/97] Fix missing 'don't' in docs/os-onboarding.md that reverses sentence meaning (#25508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Line 23 of `docs/os-onboarding.md` was missing the word "don't", which reversed the intended meaning of the sentence. **Before:** "even if we bind any of the new APIs" **After:** "even if we **don't** bind any of the new APIs" The sentence explains that releasing support for a new OS version is technically optional because bindings aren't strictly required — the missing negation made it say the opposite. ## Changes - `docs/os-onboarding.md`: Inserted missing "don't" on line 23 Single-word typo fix, no functional code changes. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/os-onboarding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/os-onboarding.md b/docs/os-onboarding.md index db4f7364a34f..6bb151226afc 100644 --- a/docs/os-onboarding.md +++ b/docs/os-onboarding.md @@ -20,7 +20,7 @@ This involves: ### Bind new APIs -This is technically optional, because we can release support for a new OS version even if we bind any of the new APIs. +This is technically optional, because we can release support for a new OS version even if we don't bind any of the new APIs. Yet we try to bind most of new APIs, because it's hard to predict what users will need. From ff427e0963aa32f708129b995642d145c9ad1236 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 26 May 2026 05:07:14 +0200 Subject: [PATCH 12/97] [github] Update to gh aw v0.74.8 (#25514) --- .github/agents/agentic-workflows.agent.md | 66 +++- .github/aw/actions-lock.json | 6 +- .github/dependabot.yml | 16 +- .github/workflows/ci-postmortem.lock.yml | 338 +++++++++++++++------ .github/workflows/code-radiator.lock.yml | 196 +++++++----- .github/workflows/copilot-setup-steps.yml | 4 +- .github/workflows/macios-reviewer.lock.yml | 198 +++++++----- 7 files changed, 547 insertions(+), 277 deletions(-) diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md index cd376c5b1b30..f7e5eb4f1cd1 100644 --- a/.github/agents/agentic-workflows.agent.md +++ b/.github/agents/agentic-workflows.agent.md @@ -19,7 +19,13 @@ This is a **dispatcher agent** that routes your request to the appropriate speci - **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt - **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes - **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs +- **Rendering ASCII charts in markdown**: Routes to `asciicharts` guide — consult this whenever the workflow needs compact charts that render reliably in GitHub issues, comments, or discussions - **CLI commands and triggering workflows**: Routes to `cli-commands` guide — consult this whenever the user asks how to run, compile, debug, or manage workflows from the command line, or when they need the MCP tool equivalent of a `gh aw` command +- **Reducing token consumption / cost optimization**: Routes to `token-optimization` guide — consult this whenever the user asks how to reduce token usage, lower costs, speed up workflows, or measure the impact of prompt changes with experiments +- **Choosing workflow architectures and design patterns**: Routes to `patterns` guide — consult this whenever the user asks for strategy, architecture, operating models, or pattern selection for agentic workflows + +> [!IMPORTANT] +> For architecture/pattern-selection requests, load `https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/patterns.md` first. Workflows may optionally include: @@ -31,7 +37,7 @@ Workflows may optionally include: - Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` - Workflow lock files: `.github/workflows/*.lock.yml` - Shared components: `.github/workflows/shared/*.md` -- Configuration: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/github-agentic-workflows.md +- Configuration: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/github-agentic-workflows.md ## Problems This Solves @@ -53,7 +59,7 @@ When you interact with this agent, it will: ### Create New Workflow **Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/create-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/create-agentic-workflow.md **Use cases**: - "Create a workflow that triages issues" @@ -63,7 +69,7 @@ When you interact with this agent, it will: ### Update Existing Workflow **Load when**: User wants to modify, improve, or refactor an existing workflow -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/update-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/update-agentic-workflow.md **Use cases**: - "Add web-fetch tool to the issue-classifier workflow" @@ -73,7 +79,7 @@ When you interact with this agent, it will: ### Debug Workflow **Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/debug-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/debug-agentic-workflow.md **Use cases**: - "Why is this workflow failing?" @@ -83,7 +89,7 @@ When you interact with this agent, it will: ### Upgrade Agentic Workflows **Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/upgrade-agentic-workflows.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/upgrade-agentic-workflows.md **Use cases**: - "Upgrade all workflows to the latest version" @@ -93,7 +99,7 @@ When you interact with this agent, it will: ### Create a Report-Generating Workflow **Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/report.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/report.md **Use cases**: - "Create a weekly CI health report" @@ -103,7 +109,7 @@ When you interact with this agent, it will: ### Create Shared Agentic Workflow **Load when**: User wants to create a reusable workflow component or wrap an MCP server -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/create-shared-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/create-shared-agentic-workflow.md **Use cases**: - "Create a shared component for Notion integration" @@ -113,7 +119,7 @@ When you interact with this agent, it will: ### Fix Dependabot PRs **Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/dependabot.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/dependabot.md **Use cases**: - "Fix the open Dependabot PRs for npm dependencies" @@ -123,17 +129,27 @@ When you interact with this agent, it will: ### Analyze Test Coverage **Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/test-coverage.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/test-coverage.md **Use cases**: - "Create a workflow that comments coverage on PRs" - "Analyze coverage trends over time" - "Add a coverage gate that blocks PRs below a threshold" +### Render ASCII Charts in Markdown +**Load when**: The workflow needs in-markdown charts (sparklines, bars, table+trend views) that must align cleanly and render reliably across GitHub surfaces, including mobile. + +**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/asciicharts.md + +**Use cases**: +- "Show a compact trend chart in an issue comment" +- "Render a dashboard table with sparkline trends" +- "Generate aligned ASCII bars for service metrics" + ### CLI Commands Reference **Load when**: The user asks how to run, compile, debug, or manage workflows from the command line; needs the MCP tool equivalent of a `gh aw` command; or is in a restricted environment (e.g., Copilot Cloud) without direct CLI access. -**Reference file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/cli-commands.md +**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/cli-commands.md **Use cases**: - "How do I trigger workflow X on the main branch?" @@ -141,6 +157,30 @@ When you interact with this agent, it will: - "I'm in Copilot Cloud — how do I compile a workflow?" - "Show me all available gh aw commands" +### Token Consumption Optimization +**Load when**: The user asks how to reduce token usage, lower workflow costs, make a workflow faster or cheaper, or measure the impact of prompt or configuration changes. + +**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/token-optimization.md + +**Use cases**: +- "How do I reduce the token cost of this workflow?" +- "My workflow is too expensive — how do I optimize it?" +- "How do I compare token usage between two runs?" +- "Should I use gh-proxy or the MCP server?" +- "How do I use sub-agents to reduce costs?" +- "How do I measure the impact of a prompt change?" + +### Workflow Pattern Selection +**Load when**: The user asks for architecture, strategy, operating model selection, or pattern recommendations for building agentic workflows. + +**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/patterns.md + +**Use cases**: +- "Which pattern should I use for multi-repo rollout?" +- "How should I structure this workflow architecture?" +- "What pattern fits slash-command triage?" +- "Should this be DispatchOps or DailyOps?" + ## Instructions When a user interacts with you: @@ -185,12 +225,12 @@ gh aw compile --validate ## Important Notes -- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/github-agentic-workflows.md for complete documentation +- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/github-agentic-workflows.md for complete documentation - Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud - Workflows must be compiled to `.lock.yml` files before running in GitHub Actions - **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF - Follow security best practices: minimal permissions, explicit network access, no template injection -- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. +- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. - **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. - **Triggering runs**: Always use `gh aw run ` to trigger a workflow on demand — not `gh workflow run .lock.yml`. `gh aw run` handles workflow resolution by short name, input parsing and validation, and correct run-tracking for agentic workflows. Use `--ref ` to run on a specific branch. -- **CLI commands reference**: For a complete guide on all `gh aw` commands and their MCP tool equivalents (for restricted environments), see https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/cli-commands.md +- **CLI commands reference**: For a complete guide on all `gh aw` commands and their MCP tool equivalents (for restricted environments), see https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/cli-commands.md diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 4b30f2d3389b..06276c7f1e4c 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -25,10 +25,10 @@ "version": "v7.0.1", "sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" }, - "github/gh-aw-actions/setup@v0.72.1": { + "github/gh-aw-actions/setup@v0.74.8": { "repo": "github/gh-aw-actions/setup", - "version": "v0.72.1", - "sha": "bc56a0cad2f450c562810785ef38649c04db812a" + "version": "v0.74.8", + "sha": "efa55847f72aadb03490d955263ff911bf758700" } }, "containers": { diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6cc00712d1c7..13434fb1a2d8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,10 @@ -version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - cooldown: - default-days: 7 +- cooldown: + default-days: 7 + directory: / + ignore: + - dependency-name: "github/gh-aw-actions/**" # Managed by gh aw compile. Version-locked to the gh-aw compiler; do not bump. + package-ecosystem: github-actions + schedule: + interval: weekly +version: 2 diff --git a/.github/workflows/ci-postmortem.lock.yml b/.github/workflows/ci-postmortem.lock.yml index 9d6b4a9a0a6e..c8d473b80999 100644 --- a/.github/workflows/ci-postmortem.lock.yml +++ b/.github/workflows/ci-postmortem.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"15354af11629eb0049ecb70f03b13ed2df90af330f1d6a40d7ce9a202538bb0b","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0","digest":"sha256:9c2228324fb1f26f39dc9471612e530ae3efc3156dac05efb2e8d212878d454d","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.0@sha256:9c2228324fb1f26f39dc9471612e530ae3efc3156dac05efb2e8d212878d454d"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2","digest":"sha256:26db03408086a99cf1916348dcc4f9614206658f9082a8060dc7c81ad787f4ba","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.2@sha256:26db03408086a99cf1916348dcc4f9614206658f9082a8060dc7c81ad787f4ba"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"15354af11629eb0049ecb70f03b13ed2df90af330f1d6a40d7ce9a202538bb0b","compiler_version":"v0.74.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"efa55847f72aadb03490d955263ff911bf758700","version":"v0.74.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.49"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.74.8). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -32,29 +32,29 @@ # Custom actions used: # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 -# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 +# - github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb -# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 -# - ghcr.io/github/gh-aw-mcpg:v0.3.0@sha256:9c2228324fb1f26f39dc9471612e530ae3efc3156dac05efb2e8d212878d454d -# - ghcr.io/github/github-mcp-server:v1.0.2@sha256:26db03408086a99cf1916348dcc4f9614206658f9082a8060dc7c81ad787f4ba +# - ghcr.io/github/gh-aw-firewall/agent:0.25.49 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.49 +# - ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 +# - ghcr.io/github/github-mcp-server:v1.0.4 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "CI Post-Mortem Analysis" -"on": +on: schedule: - - cron: "45 11 * * 0" + - cron: "11 2 * * 0" # Friendly format: weekly on sunday (scattered) workflow_dispatch: inputs: aw_context: default: "" - description: Agent caller context (used internally by Agentic Workflows). + description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string @@ -78,35 +78,42 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "CI Post-Mortem Analysis" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/ci-postmortem.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: "claude-sonnet-4.5" - GH_AW_INFO_VERSION: "1.0.35" - GH_AW_INFO_AGENT_VERSION: "1.0.35" - GH_AW_INFO_CLI_VERSION: "v0.71.1" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_AGENT_VERSION: "1.0.48" + GH_AW_INFO_CLI_VERSION: "v0.74.8" GH_AW_INFO_WORKFLOW_NAME: "CI Post-Mortem Analysis" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","github","aka.ms","dev.azure.com","devdiv.visualstudio.com","microsoft.com","vsassets.io"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.28" + GH_AW_INFO_AWF_VERSION: "v0.25.49" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -130,17 +137,18 @@ jobs: .crush .gemini .opencode + .pi sparse-checkout-cone-mode: true fetch-depth: 1 - name: Save agent config folders for base branch restoration env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" # poutine:ignore untrusted_checkout_exec run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file id: check-lock-file - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_WORKFLOW_FILE: "ci-postmortem.lock.yml" GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" @@ -151,9 +159,9 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); await main(); - name: Check compile-agentic version - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.71.1" + GH_AW_COMPILED_VERSION: "v0.74.8" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -164,11 +172,11 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -187,30 +195,33 @@ jobs: Tools: add_comment(max:20), create_issue(max:20), update_issue(max:20), missing_tool, missing_data, noop + GH_AW_PROMPT_956c986f1e45d6a0_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_956c986f1e45d6a0_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} @@ -223,9 +234,10 @@ jobs: GH_AW_PROMPT_956c986f1e45d6a0_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -233,17 +245,18 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); await main(); - name: Substitute placeholders - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -255,14 +268,15 @@ jobs: return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST } }); - name: Validate prompt placeholders @@ -280,11 +294,15 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: activation + include-hidden-files: true path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base + /tmp/gh-aw/.github/agents if-no-files-found: ignore retention-days: 1 @@ -307,6 +325,7 @@ jobs: agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} @@ -314,15 +333,23 @@ jobs: model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "CI Post-Mortem Analysis" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/ci-postmortem.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -358,7 +385,7 @@ jobs: id: checkout-pr if: | github.event.pull_request || github.event.issue.pull_request - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: @@ -369,11 +396,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -389,12 +416,17 @@ jobs: - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0@sha256:9c2228324fb1f26f39dc9471612e530ae3efc3156dac05efb2e8d212878d454d ghcr.io/github/github-mcp-server:v1.0.2@sha256:26db03408086a99cf1916348dcc4f9614206658f9082a8060dc7c81ad787f4ba node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - - name: Write Safe Outputs Config + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 ghcr.io/github/github-mcp-server:v1.0.4 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs @@ -402,7 +434,7 @@ jobs: cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c2d474e65378a915_EOF' {"add_comment":{"max":20},"create_issue":{"max":20},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":20}} GH_AW_SAFE_OUTPUTS_CONFIG_c2d474e65378a915_EOF - - name: Write Safe Outputs Tools + - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { @@ -447,6 +479,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "fields": { + "type": "array" + }, "labels": { "type": "array", "itemType": "string", @@ -599,7 +634,7 @@ jobs: "customValidation": "requiresOneOf:status,title,body" } } - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -660,6 +695,7 @@ jobs: # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${MCP_GATEWAY_API_KEY}" export MCP_GATEWAY_API_KEY @@ -671,8 +707,13 @@ jobs: export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.9' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -681,7 +722,7 @@ jobs: "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.2", + "container": "ghcr.io/github/github-mcp-server:v1.0.4", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -721,32 +762,59 @@ jobs: } } GH_AW_MCP_CONFIG_7bd5cd6513b45b21_EOF - - name: Clean git credentials + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","aka.ms","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dev.azure.com","devdiv.visualstudio.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","microsoft.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","vsassets.io","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5","gemini-pro","haiku","any"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["large"],"claude":["agent","sonnet-6x","haiku","any"],"codex":["agent","gpt-5-codex","gpt-5","any"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"copilot":["agent","gpt-5.4","sonnet","gpt-5","any"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent","gemini-pro","gemini-flash","any"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite","copilot/raptor*mini*"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4-5*","anthropic/*sonnet-4.5*","anthropic/*sonnet-4-5*","copilot/*sonnet-3.7*","copilot/*sonnet-3-7*","anthropic/*sonnet-3.7*","anthropic/*sonnet-3-7*","copilot/*sonnet-3.5*","copilot/*sonnet-3-5*","anthropic/*sonnet-3.5*","anthropic/*sonnet-3-5*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,devdiv.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-sonnet-4.5 GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.74.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -794,7 +862,7 @@ jobs: bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -820,10 +888,10 @@ jobs: - name: Ingest agent output id: collect_output if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,devdiv.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,devdiv.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -834,7 +902,7 @@ jobs: await main(); - name: Parse agent logs for step summary if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ with: @@ -846,7 +914,7 @@ jobs: - name: Parse MCP Gateway logs for step summary if: always() id: parse-mcp-gateway - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -861,7 +929,7 @@ jobs: run: | # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts # AWF runs with sudo, creating files owned by root - sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall 2>/dev/null || true + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) if command -v awf &> /dev/null; then awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" @@ -871,13 +939,23 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -899,14 +977,17 @@ jobs: !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt /tmp/gh-aw/agent/ /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json /tmp/gh-aw/sandbox/firewall/logs/ /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json if-no-files-found: ignore conclusion: @@ -927,6 +1008,7 @@ jobs: concurrency: group: "gh-aw-conclusion-ci-postmortem" cancel-in-progress: false + queue: max outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -935,11 +1017,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "CI Post-Mortem Analysis" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/ci-postmortem.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -956,7 +1044,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Process no-op messages id: noop - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" @@ -973,7 +1061,7 @@ jobs: await main(); - name: Log detection run id: detection_runs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "CI Post-Mortem Analysis" @@ -989,7 +1077,7 @@ jobs: await main(); - name: Record missing tool id: missing_tool - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" @@ -1003,7 +1091,7 @@ jobs: await main(); - name: Record incomplete id: report_incomplete - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" @@ -1018,7 +1106,7 @@ jobs: - name: Handle agent failure id: handle_agent_failure if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "CI Post-Mortem Analysis" @@ -1029,15 +1117,21 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1062,11 +1156,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "CI Post-Mortem Analysis" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/ci-postmortem.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1092,7 +1192,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 - name: Check if detection needed id: detection_guard if: always() @@ -1107,7 +1207,7 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" @@ -1129,7 +1229,7 @@ jobs: ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - name: Setup threat detection if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "CI Post-Mortem Analysis" WORKFLOW_DESCRIPTION: "No description provided" @@ -1151,33 +1251,43 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-sonnet-4.5 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.71.1 + GH_AW_VERSION: v0.74.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1201,16 +1311,35 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } safe_outputs: needs: @@ -1232,7 +1361,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "claude-sonnet-4.5" - GH_AW_ENGINE_VERSION: "1.0.35" + GH_AW_ENGINE_VERSION: "1.0.48" GH_AW_WORKFLOW_ID: "ci-postmortem" GH_AW_WORKFLOW_NAME: "CI Post-Mortem Analysis" outputs: @@ -1249,11 +1378,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "CI Post-Mortem Analysis" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/ci-postmortem.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1279,10 +1414,11 @@ jobs: echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - name: Process Safe Outputs id: process_safe_outputs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,devdiv.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,devdiv.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":20},\"create_issue\":{\"max\":20},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":20}}" diff --git a/.github/workflows/code-radiator.lock.yml b/.github/workflows/code-radiator.lock.yml index 8c0f8d46a7c0..cd32f396e575 100644 --- a/.github/workflows/code-radiator.lock.yml +++ b/.github/workflows/code-radiator.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a665debda1a902047622496aa625b015b0b9f053d3892454a63ac7b73cc61808","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a665debda1a902047622496aa625b015b0b9f053d3892454a63ac7b73cc61808","compiler_version":"v0.74.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"efa55847f72aadb03490d955263ff911bf758700","version":"v0.74.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.49"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.72.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.74.8). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -36,18 +36,18 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 +# - github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 -# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 +# - ghcr.io/github/gh-aw-firewall/agent:0.25.49 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.49 +# - ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 +# - ghcr.io/github/github-mcp-server:v1.0.4 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "Code Radiator" -"on": +on: # roles: # Roles processed as role check in pre-activation job # - admin # Roles processed as role check in pre-activation job # - maintain # Roles processed as role check in pre-activation job @@ -59,7 +59,7 @@ name: "Code Radiator" inputs: aw_context: default: "" - description: Agent caller context (used internally by Agentic Workflows). + description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string @@ -84,35 +84,38 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: "claude-sonnet-4.5" - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.72.1" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_AGENT_VERSION: "1.0.48" + GH_AW_INFO_CLI_VERSION: "v0.74.8" GH_AW_INFO_WORKFLOW_NAME: "Code Radiator" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.41" + GH_AW_INFO_AWF_VERSION: "v0.25.49" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -164,7 +167,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.72.1" + GH_AW_COMPILED_VERSION: "v0.74.8" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -175,11 +178,11 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -207,28 +210,28 @@ jobs: cat << 'GH_AW_PROMPT_0170a78c8d56645a_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - **checkouts**: The following repositories have been checked out and are available in the workspace: @@ -258,11 +261,11 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -278,11 +281,11 @@ jobs: return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, @@ -335,6 +338,7 @@ jobs: agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} @@ -342,19 +346,23 @@ jobs: model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -408,11 +416,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -437,7 +445,7 @@ jobs: GH_AW_SUB_AGENT_EXT: ".agent.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 ghcr.io/github/github-mcp-server:v1.0.4 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -778,8 +786,13 @@ jobs: export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.9' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -788,7 +801,7 @@ jobs: "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "container": "ghcr.io/github/github-mcp-server:v1.0.4", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -855,25 +868,32 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","docs.github.com","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41,squid=sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4,agent=sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770,api-proxy=sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","docs.github.com","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5","gemini-pro","haiku","any"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["large"],"claude":["agent","sonnet-6x","haiku","any"],"codex":["agent","gpt-5-codex","gpt-5","any"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"copilot":["agent","gpt-5.4","sonnet","gpt-5","any"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent","gemini-pro","gemini-flash","any"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite","copilot/raptor*mini*"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4-5*","anthropic/*sonnet-4.5*","anthropic/*sonnet-4-5*","copilot/*sonnet-3.7*","copilot/*sonnet-3-7*","anthropic/*sonnet-3.7*","anthropic/*sonnet-3-7*","copilot/*sonnet-3.5*","copilot/*sonnet-3-5*","anthropic/*sonnet-3.5*","anthropic/*sonnet-3-5*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-sonnet-4.5 GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.72.1 + GH_AW_VERSION: v0.74.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -950,7 +970,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -1067,6 +1087,7 @@ jobs: concurrency: group: "gh-aw-conclusion-code-radiator" cancel-in-progress: false + queue: max outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1075,15 +1096,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1173,6 +1196,8 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1187,6 +1212,7 @@ jobs: GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1211,15 +1237,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1245,7 +1273,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 - name: Check if detection needed id: detection_guard if: always() @@ -1304,11 +1332,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1317,23 +1345,30 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-sonnet-4.5 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.72.1 + GH_AW_VERSION: v0.74.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1361,6 +1396,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | @@ -1371,10 +1407,11 @@ jobs: await main(); } catch (loadErr) { const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); core.error(msg); core.setOutput('reason', 'parse_error'); - if (continueOnError) { + if (continueOnError && !detectionExecutionFailed) { core.warning('\u26A0\uFE0F ' + msg); core.setOutput('conclusion', 'warning'); core.setOutput('success', 'false'); @@ -1405,7 +1442,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "claude-sonnet-4.5" - GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_ENGINE_VERSION: "1.0.48" GH_AW_WORKFLOW_ID: "code-radiator" GH_AW_WORKFLOW_NAME: "Code Radiator" outputs: @@ -1424,15 +1461,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1476,8 +1515,16 @@ jobs: echo "Extracted base branch from safe output: $BASE_BRANCH" fi fi + - name: Checkout repository (trusted default branch for comment events) + if: ((!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch')) && (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.repository.default_branch }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + persist-credentials: false + fetch-depth: 1 - name: Checkout repository - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') + if: ((!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch')) && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} @@ -1512,7 +1559,8 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net[0-9]*.0\",\"xcode[0-9]*\",\"xcode[0-9]*.[0-9]*\"],\"max\":10,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"]},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 0ae42f6f5c4b..f4841a250f76 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -21,6 +21,6 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - name: Install gh-aw extension - uses: github/gh-aw-actions/setup-cli@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: - version: v0.72.1 + version: v0.74.8 diff --git a/.github/workflows/macios-reviewer.lock.yml b/.github/workflows/macios-reviewer.lock.yml index 616d54825de5..28089a715a5d 100644 --- a/.github/workflows/macios-reviewer.lock.yml +++ b/.github/workflows/macios-reviewer.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a0f64139dbc6fe8697f9fd99f5489155b561c37ec0b13d7eaf30bda56d034e7f","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a0f64139dbc6fe8697f9fd99f5489155b561c37ec0b13d7eaf30bda56d034e7f","compiler_version":"v0.74.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"efa55847f72aadb03490d955263ff911bf758700","version":"v0.74.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.49"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.72.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.74.8). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -35,18 +35,18 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 +# - github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 -# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 +# - ghcr.io/github/gh-aw-firewall/agent:0.25.49 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.49 +# - ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 +# - ghcr.io/github/github-mcp-server:v1.0.4 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: ".NET for Apple Platforms PR Reviewer" -"on": +on: issue_comment: types: - created @@ -83,6 +83,8 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} slash_command: ${{ needs.pre_activation.outputs.matched_command }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} @@ -91,31 +93,33 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/macios-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: "claude-sonnet-4.5" - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.72.1" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_AGENT_VERSION: "1.0.48" + GH_AW_INFO_CLI_VERSION: "v0.74.8" GH_AW_INFO_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","github","aka.ms","dev.azure.com","microsoft.com","vsassets.io"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.41" + GH_AW_INFO_AWF_VERSION: "v0.25.49" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -128,7 +132,7 @@ jobs: await main(core, context); - name: Add eyes reaction for immediate feedback id: react - if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id || github.event_name == 'pull_request_review' && github.event.pull_request.head.repo.id == github.repository_id + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_REACTION: "eyes" @@ -180,7 +184,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.72.1" + GH_AW_COMPILED_VERSION: "v0.74.8" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -191,7 +195,7 @@ jobs: id: sanitized uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -200,7 +204,7 @@ jobs: await main(); - name: Add comment with workflow run link id: add-comment - if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id || github.event_name == 'pull_request_review' && github.event.pull_request.head.repo.id == github.repository_id + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" @@ -214,11 +218,11 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -243,28 +247,28 @@ jobs: cat << 'GH_AW_PROMPT_33adc77fba6f068a_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} @@ -294,11 +298,11 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -317,11 +321,11 @@ jobs: return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, @@ -375,6 +379,7 @@ jobs: agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} @@ -382,19 +387,23 @@ jobs: model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/macios-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -441,11 +450,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -470,7 +479,7 @@ jobs: GH_AW_SUB_AGENT_EXT: ".agent.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 ghcr.io/github/github-mcp-server:v1.0.4 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -694,8 +703,13 @@ jobs: export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.9' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -704,7 +718,7 @@ jobs: "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "container": "ghcr.io/github/github-mcp-server:v1.0.4", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -771,25 +785,32 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","aka.ms","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dev.azure.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","microsoft.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","vsassets.io","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41,squid=sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4,agent=sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770,api-proxy=sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","aka.ms","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dev.azure.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","microsoft.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","vsassets.io","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5","gemini-pro","haiku","any"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["large"],"claude":["agent","sonnet-6x","haiku","any"],"codex":["agent","gpt-5-codex","gpt-5","any"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"copilot":["agent","gpt-5.4","sonnet","gpt-5","any"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent","gemini-pro","gemini-flash","any"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite","copilot/raptor*mini*"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4-5*","anthropic/*sonnet-4.5*","anthropic/*sonnet-4-5*","copilot/*sonnet-3.7*","copilot/*sonnet-3-7*","anthropic/*sonnet-3.7*","anthropic/*sonnet-3-7*","copilot/*sonnet-3.5*","copilot/*sonnet-3-5*","anthropic/*sonnet-3.5*","anthropic/*sonnet-3-5*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-sonnet-4.5 GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.72.1 + GH_AW_VERSION: v0.74.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -866,7 +887,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMAND: review @@ -982,6 +1003,7 @@ jobs: concurrency: group: "gh-aw-conclusion-macios-reviewer" cancel-in-progress: false + queue: max outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -990,15 +1012,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/macios-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1088,6 +1112,8 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1100,6 +1126,7 @@ jobs: GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1144,15 +1171,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/macios-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1178,7 +1207,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 - name: Check if detection needed id: detection_guard if: always() @@ -1237,11 +1266,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1250,23 +1279,30 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-sonnet-4.5 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.72.1 + GH_AW_VERSION: v0.74.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1294,6 +1330,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | @@ -1304,10 +1341,11 @@ jobs: await main(); } catch (loadErr) { const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); core.error(msg); core.setOutput('reason', 'parse_error'); - if (continueOnError) { + if (continueOnError && !detectionExecutionFailed) { core.warning('\u26A0\uFE0F ' + msg); core.setOutput('conclusion', 'warning'); core.setOutput('success', 'false'); @@ -1324,18 +1362,21 @@ jobs: outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} matched_command: ${{ steps.check_command_position.outputs.matched_command }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/macios-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for command workflow id: check_membership uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1378,7 +1419,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "claude-sonnet-4.5" - GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_ENGINE_VERSION: "1.0.48" GH_AW_WORKFLOW_ID: "macios-reviewer" GH_AW_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" outputs: @@ -1391,15 +1432,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: ".NET for Apple Platforms PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/macios-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1428,7 +1471,8 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request_review_comment\":{\"max\":50,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"allowed_events\":[\"COMMENT\",\"REQUEST_CHANGES\"],\"max\":1,\"supersede_older_reviews\":true}}" From c7e31395d70bf2a281cbdf0b6039d2b25416c9d4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 26 May 2026 08:32:52 +0200 Subject: [PATCH 13/97] [github] Add autoformat-v2.yml: single zizmor-clean workflow (#25510) Create a new autoformat workflow that avoids the security concerns flagged by zizmor in the existing autoformat.yml + autoformat2.yml: - No workflow_run trigger (eliminates dangerous-triggers) - All actions pinned to SHA hashes (eliminates unpinned-uses) - persist-credentials: false on checkout (eliminates artipacked) - Job-level permissions (eliminates excessive-permissions) Two jobs handle same-repo and fork PRs differently: - Same-repo: format + commit + push directly - Fork: format + upload patch as artifact All logic is inlined (no rolfbjarne/autoformat references). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/autoformat-v2.yml | 97 +++++++++++++++++++++++++++++ .github/workflows/autoformat.yml | 30 --------- .github/workflows/autoformat2.yml | 27 -------- 3 files changed, 97 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/autoformat-v2.yml delete mode 100644 .github/workflows/autoformat.yml delete mode 100644 .github/workflows/autoformat2.yml diff --git a/.github/workflows/autoformat-v2.yml b/.github/workflows/autoformat-v2.yml new file mode 100644 index 000000000000..695354c78d1a --- /dev/null +++ b/.github/workflows/autoformat-v2.yml @@ -0,0 +1,97 @@ +name: Autoformat code v2 +on: pull_request + +permissions: {} + +jobs: + # Job for same-repo PRs: format and push directly + autoformat-push: + name: Autoformat and push + runs-on: ubuntu-latest + if: github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: write + + steps: + - name: 'Checkout' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.head_ref }} + fetch-depth: 1 + persist-credentials: false + + - name: 'Install .NET' + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 + with: + global-json-file: ./global.json + + - name: 'Autoformat' + run: ./tools/autoformat.sh + + - name: 'Check for changes' + id: check + run: | + if git diff --quiet; then + echo "changes=false" >> "$GITHUB_OUTPUT" + else + echo "changes=true" >> "$GITHUB_OUTPUT" + fi + + - name: 'Commit and push' + if: steps.check.outputs.changes == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + git config user.email 'github-actions-autoformatter@xamarin.com' + git config user.name 'GitHub Actions Autoformatter' + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}" + git add -A + git commit -m 'Auto-format source code' + git push + + # Job for fork PRs: format and upload patch as artifact + autoformat-artifact: + name: Autoformat and upload patch + runs-on: ubuntu-latest + if: github.event.pull_request.head.repo.full_name != github.repository + permissions: + contents: read + + steps: + - name: 'Checkout' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + fetch-depth: 1 + + - name: 'Install .NET' + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 + with: + global-json-file: ./global.json + + - name: 'Autoformat' + run: ./tools/autoformat.sh + + - name: 'Create patch' + id: patch + run: | + if git diff --quiet; then + echo "changes=false" >> "$GITHUB_OUTPUT" + else + echo "changes=true" >> "$GITHUB_OUTPUT" + git config user.email 'github-actions-autoformatter@xamarin.com' + git config user.name 'GitHub Actions Autoformatter' + git add -A + git commit -m 'Auto-format source code' + mkdir -p autoformat-output + git format-patch HEAD~1 --stdout > autoformat-output/autoformat.patch + fi + + - name: 'Upload patch' + if: steps.patch.outputs.changes == 'true' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: autoformat + path: autoformat-output/ diff --git a/.github/workflows/autoformat.yml b/.github/workflows/autoformat.yml deleted file mode 100644 index f20d04015aad..000000000000 --- a/.github/workflows/autoformat.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Autoformat code -on: pull_request - -# This action only need a single permission in order to autoformat the code. -permissions: - contents: read - -jobs: - autoformat-code: - name: Autoformat code - runs-on: ubuntu-latest - - steps: - - name: 'Checkout' - uses: actions/checkout@v6 - with: - fetch-depth: 0 - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.sha }} - - name: 'Install .NET' - uses: actions/setup-dotnet@v5 - with: - global-json-file: ./global.json - - name: 'Autoformat' - uses: rolfbjarne/autoformat@v0.6 - with: - script: ./tools/autoformat.sh - git_user_email: 'github-actions-autoformatter@xamarin.com' - git_user_name: 'GitHub Actions Autoformatter' - checkoutSource: false diff --git a/.github/workflows/autoformat2.yml b/.github/workflows/autoformat2.yml deleted file mode 100644 index bc382aaf8c2e..000000000000 --- a/.github/workflows/autoformat2.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Autoformat code - push results -on: - workflow_run: - workflows: ["Autoformat code"] - types: - - completed - -# This action needs the following permissions in order to push the results back to the original branch. -permissions: - pull-requests: write - contents: write - -jobs: - push-and-notify: - name: Push autoformatted code and notify user - runs-on: ubuntu-latest - if: > - github.event.workflow_run.event == 'pull_request' && - github.event.workflow_run.conclusion == 'success' - steps: - - name: 'Push autoformatted patch' - uses: rolfbjarne/autoformat-push@v0.3 - with: - githubToken: ${{ secrets.GITHUB_TOKEN }} - git_user_email: 'github-actions-autoformatter@xamarin.com' - git_user_name: 'GitHub Actions Autoformatter' - commentOnPullRequest: false From 181d1357463a54a75ed490676e39d430ef1744de Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 26 May 2026 13:53:42 +0200 Subject: [PATCH 14/97] [dotnet-linker] Add a trimmer step to inline calls to Class.GetHandle[Intrinsic]. (#25318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds a new trimmer step that inlines calls to `Class.GetHandle` (and `Class.GetHandleIntrinsic`) with direct native references to Objective-C classes. This is a companion feature to the existing `InlineDlfcnMethods` step, and together they enable the linker to replace managed dictionary lookups with direct native symbol references that are resolved at native link time. ## Motivation `Class.GetHandle ("ClassName")` calls go through a managed dictionary lookup at runtime. By inlining these calls, we: 1. **Improve startup performance** — eliminates dictionary lookups for class handles. 2. **Enable dead-code elimination** — the native linker can strip unreferenced class references, reducing binary size. 3. **Support NativeAOT** — ensures class references survive the NativeAOT compilation pipeline without relying on reflection-based discovery. ## Design The feature works in two modes controlled by the `InlineClassGetHandle` MSBuild property: - **`compatibility`** (default for IL trimming on .NET 11+): Rewrites `Class.GetHandle` calls to P/Invoke wrappers, then generates native code for all surviving wrappers after trimming. Falls back to `objc_getClass` at runtime for missing classes. - **`strict`** (default for NativeAOT on .NET 11+): Same rewriting, but does not generate runtime fallbacks — missing classes produce a build error. ### Pipeline 1. **ILTrim phase** (`InlineClassGetHandleStep`): Rewrites `Class.GetHandle`/`GetHandleIntrinsic` calls to generated P/Invoke methods. 2. **Type map generation** (`CoreTypeMapStep`): Emits class metadata (framework, introduced version, wrapper status) used by post-trim code generation. 3. **Post-trim processing** (`PostTrimmingProcessing` MSBuild task): Collects surviving class references from trimmed assemblies, generates native source files with `@interface` forward declarations and wrapper functions. 4. **NativeAOT path** (`CollectUnresolvedNativeSymbols` + `ComputeNativeAOTSurvivingNativeSymbols`): Extracts unresolved symbols from NativeAOT output and generates native code for surviving references only. ## Changes - **New linker steps**: `InlineClassGetHandleStep`, `GenerateInlinedClassGetHandleCodeStep`, updated `CoreTypeMapStep`. - **New MSBuild tasks**: `PostTrimmingProcessing` (generates native code), `CollectUnresolvedNativeSymbols`, `ComputeNativeAOTSurvivingNativeSymbols`, `CollectPostILTrimInformation`. - **Shared utilities**: `FileUtils.WriteIfDifferent`, `MachO` unresolved symbol extraction, framework lookup helpers. - **MSBuild integration**: New properties (`InlineClassGetHandle`), targets for post-trim native code generation and compilation. - **Tests**: New test variations (`inline-class-gethandle-compat`, `inline-class-gethandle-strict`) for both ILTrim and NativeAOT paths. - **Documentation**: `docs/code/class-handles.md`, `docs/building-apps/build-properties.md` updated. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 4 + docs/building-apps/build-properties.md | 36 +++ docs/code/class-handles.md | 57 ++++ dotnet/targets/Xamarin.Shared.Sdk.props | 6 + dotnet/targets/Xamarin.Shared.Sdk.targets | 47 +++- .../Tasks/CollectPostILTrimInformation.cs | 74 +++-- .../Tasks/CollectUnresolvedNativeSymbols.cs | 2 +- .../ComputeNativeAOTSurvivingNativeSymbols.cs | 42 +-- .../Tasks/PostTrimmingProcessing.cs | 185 ++++++++++-- src/ObjCRuntime/Registrar.cs | 22 +- src/coreml.cs | 12 +- tests/common/test-variations.csproj | 29 ++ tests/dotnet/UnitTests/RegistrarTest.cs | 1 + tests/linker/link all/dotnet/shared.csproj | 6 + .../linker/trimmode link/dotnet/shared.csproj | 6 + tests/monotouch-test/dotnet/shared.csproj | 4 + .../xharness/Jenkins/TestVariationsFactory.cs | 8 +- tools/common/DerivedLinkContext.cs | 14 +- tools/common/FileUtils.cs | 14 + tools/common/PathUtils.cs | 20 ++ tools/common/StaticRegistrar.cs | 7 +- tools/dotnet-linker/AppBundleRewriter.cs | 50 ++++ tools/dotnet-linker/LinkerConfiguration.cs | 24 +- .../Steps/InlineClassGetHandleStep.cs | 264 ++++++++++++++++++ .../Steps/InlineDlfcnMethodsStep.cs | 41 +-- .../MonoTouch.Tuner/ListExportedSymbols.cs | 59 ++-- tools/mtouch/Errors.designer.cs | 9 + tools/mtouch/Errors.resx | 6 + 28 files changed, 883 insertions(+), 166 deletions(-) create mode 100644 docs/code/class-handles.md create mode 100644 tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4a072d2b8a8d..618589332491 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -136,6 +136,10 @@ interface SomeClass { Located in `msbuild/` directory: - `Xamarin.MacDev.Tasks` - Shared Apple development tasks +### FileWrites + +If a target or task creates a file, that file must be added to the `FileWrites` item group. This ensures MSBuild's incremental clean can delete generated files. Additionally, if a target produces multiple output files, all of them should be listed in the target's `Outputs` attribute for correct incremental build behavior. + ### Project Templates Common project structure for Apple platform apps: diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 461d2986fdca..684cd816cb7d 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -559,6 +559,42 @@ See also: * The [AlternateAppIcon](build-items.md#alternateappicon) item group. * The [AppIcon](#appicon) property. +## InlineClassGetHandle + +Controls whether the build system replaces runtime calls to `Class.GetHandle` / +`Class.GetHandleIntrinsic` with direct native references to Objective-C classes +at build time. + +See [docs/code/class-handles.md](../code/class-handles.md) for an overview. + +The valid options are: + +* `compatibility`: Inlines `Class.GetHandle` calls only for types whose declaring + type matches the requested Objective-C class name. +* `strict`: Inlines all `Class.GetHandle` calls unconditionally. Requires using + the static registrar (not the dynamic registrar). +* (empty): Disables inlining of `Class.GetHandle` calls. + +Default value: +* .NET 11+: `strict` when using NativeAOT (`PublishAot=true`), `compatibility` otherwise. +* .NET 10 and earlier: not set (disabled). + +Example: + +```xml + + compatibility + +``` + +Custom behavior for specific Objective-C classes can be set using the [ReferenceNativeSymbol](build-items.md#referencenativesymbols) item group: + +```xml + + + +``` + ## InlineDlfcnMethods Controls whether the build system replaces runtime calls to `ObjCRuntime.Dlfcn` methods with direct native symbol lookups at build time, eliminating the overhead of `dlsym` at runtime. diff --git a/docs/code/class-handles.md b/docs/code/class-handles.md new file mode 100644 index 000000000000..48255fb5aa6a --- /dev/null +++ b/docs/code/class-handles.md @@ -0,0 +1,57 @@ +# Objective-C classes + +Objective-C classes can be referenced from managed code in several ways: + +* Calls to Class.GetHandle / GetHandleIntrinsic + +It's highly desirable to use a direct native reference to Objective-C classes when building a mobile app, for a few reasons: + +* It's faster at runtime, and the app is smaller. +* If the referenced Objective-C class comes from a third-party static library, the + native linker can remove it if it's configured to remove unused code + (because the native linker can't see that the class is in fact used + at runtime) unless there's a direct native reference to the class. + +On the other hand there's one scenario when a direct native reference is not desirable: when the native Objective-C class does not exist. + +In order to create a direct native reference to Objective-C classes, we need to know the names of those Objective-C classes. + +## The `InlineClassGetHandle` property + +This behavior is controlled by the `InlineClassGetHandle` MSBuild property, which +can either be enabled or disabled. + +See the [build properties documentation](../building-apps/build-properties.md) for default values. + +## How it works + +During the build we try to collect the following: + +* Any calls to `Class.GetHandle[Intrinsic]` APIs: we try to collect the class name (this might not always succeed, if the class name is not a constant). + +This is further complicated by the fact that we only want to create native +references for managed references that survive trimming. + +So we do the following: + +1. During trimming, two custom linker steps execute: + + * `InlineClassGetHandleStep`: for every call `Class.GetHandle` we've + collected, this step creates a P/Invoke to a native method that will + return the Objective-C class for that symbol (using a direct native + reference), and modifies the code that fetches that symbol to call said + P/Invoke. + +2. After trimming, we figure out which of those symbols survived: + + * For ILTrim: the `_CollectPostILTrimInformation` MSBuild target inspects + the trimmed assemblies and collects all the inlined P/Invokes that + survived. Per-assembly results are cached to speed up incremental builds. + * For NativeAOT: the `_CollectPostNativeAOTTrimInformation` MSBuild target + inspects the native object file (or static library) produced by NativeAOT, + collects all unresolved native references, and filters them against the + Objective-C classes to determine which survived. + +3. The `_PostTrimmingProcessing` MSBuild target takes the surviving symbols + from either path, generates the corresponding native Objective-C code, and + adds it to the list of files to compile and link into the final executable. diff --git a/dotnet/targets/Xamarin.Shared.Sdk.props b/dotnet/targets/Xamarin.Shared.Sdk.props index 40b953914bac..532e7e2a44cb 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.props +++ b/dotnet/targets/Xamarin.Shared.Sdk.props @@ -107,6 +107,12 @@ compatibility + + + strict + compatibility + + diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index d1f37e5aceba..02926aa4ed8c 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -642,6 +642,7 @@ @(_BundlerEnvironmentVariables -> 'EnvironmentVariable=Overwrite=%(Overwrite)|%(Identity)=%(Value)') @(_XamarinFrameworkAssemblies -> 'FrameworkAssembly=%(Filename)') Interpreter=$(MtouchInterpreter) + InlineClassGetHandle=$(InlineClassGetHandle) InlineDlfcnMethods=$(InlineDlfcnMethods) IntermediateLinkDir=$(IntermediateLinkDir) IntermediateOutputPath=$(DeviceSpecificIntermediateOutputPath) @@ -674,6 +675,7 @@ TargetArchitectures=$(TargetArchitectures) TargetFramework=$(_ComputedTargetFrameworkMoniker) TypeMapAssemblyName=$(_TypeMapAssemblyName) + TypeMapFilePath=$(_TypeMapFilePath) TypeMapOutputDirectory=$(_TypeMapOutputDirectory) UseLlvm=$(MtouchUseLlvm) Verbosity=$(_BundlerVerbosity) @@ -798,6 +800,7 @@ <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreMarkDispatcher" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.TrimmableRegistrarStep" Condition="'$(Registrar)' == 'trimmable-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineClassGetHandleStep" Condition="'$(InlineClassGetHandle)' != '' And '$(InlineClassGetHandle)' != 'disabled'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.Steps.ListExportedSymbols" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.Steps.PreOutputDispatcher" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.ClassHandleRewriterStep" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.ClassHandleRewriterStep" Condition="'$(InlineClassGetHandle)' == '' Or '$(InlineClassGetHandle)' == 'disabled'" /> + <_TypeMapFilePath Condition="'$(_TypeMapFilePath)' == ''">$(DeviceSpecificIntermediateOutputPath)type-map.txt @@ -1688,20 +1694,22 @@ <_ILTrimSurvivingNativeSymbolsFile>$(DeviceSpecificIntermediateOutputPath)inlined-dlfcn\iltrim-surviving-native-symbols.txt - <_NativeAOTUnresolvedSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-unresolved-symbols.txt <_NativeAOTSurvivingNativeSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-surviving-native-symbols.txt + <_ILTrimSurvivingClassesFile>$(DeviceSpecificIntermediateOutputPath)inlined-class-gethandle\iltrim-classes.txt + <_NativeAOTSurvivingClassesFile>$(DeviceSpecificIntermediateOutputPath)inlined-class-gethandle\nativeaot-classes.txt @@ -1710,6 +1718,7 @@ + @@ -1720,12 +1729,17 @@ <_SurvivingNativeSymbolsFile Include="$(_ILTrimSurvivingNativeSymbolsFile)" Condition="Exists('$(_ILTrimSurvivingNativeSymbolsFile)')" /> <_SurvivingNativeSymbolsFile Include="$(_NativeAOTSurvivingNativeSymbolsFile)" Condition="Exists('$(_NativeAOTSurvivingNativeSymbolsFile)')" /> + + <_SurvivingClassesFiles Include="$(_ILTrimSurvivingClassesFile)" Condition="Exists('$(_ILTrimSurvivingClassesFile)')" /> + <_SurvivingClassesFiles Include="$(_NativeAOTSurvivingClassesFile)" Condition="Exists('$(_NativeAOTSurvivingClassesFile)')" /> @@ -1735,8 +1749,15 @@ <_PostTrimmingSourceFiles> $(DeviceSpecificIntermediateOutputPath)posttrim-info-compiled/%(Arch)/%(Filename).o + <_NativeExecutableObjectFiles Include="@(_PostTrimmingSourceFiles -> '%(OutputFile)')" /> + + - <_CompiledPostTrimmingFiles Include="@(_PostTrimmingSourceFiles -> '%(OutputFile)')" /> - <_NativeExecutableObjectFiles Include="@(_CompiledPostTrimmingFiles)" /> - + @@ -1814,7 +1833,7 @@ _ReadAppManifest; _WriteAppManifest; _CompileNativeExecutable; - _PostTrimmingProcessing; + _CompilePostTrimmingFiles; _ReidentifyDynamicLibraries; _AddSwiftLinkerFlags; _ComputeLinkNativeExecutableInputs; @@ -1827,8 +1846,11 @@ Condition="'$(_UseNativeAot)' == 'true'" DependsOnTargets="_ComputePostTrimmingPaths;_CompileNativeExecutable" Inputs="$(NativeObject)" - Outputs="$(_NativeAOTSurvivingNativeSymbolsFile)" + Outputs="$(_NativeAOTSurvivingNativeSymbolsFile);$(_NativeAOTSurvivingClassesFile)" > + + <_NativeAOTUnresolvedSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-unresolved-symbols.txt + + + + + diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs index 116ce430cb49..7f2baaad79e3 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs @@ -9,12 +9,15 @@ using Mono.Cecil; +using Xamarin.Bundler; +using Xamarin.Utils; + #nullable enable namespace Xamarin.MacDev.Tasks { /// /// Scans trimmed assemblies to collect information that survived trimming. - /// See docs/code/native-symbols.md for an overview of native symbol handling. + /// See docs/code/native-symbols.md and docs/code/class-handles.md for an overview of native symbol handling. /// public class CollectPostILTrimInformation : XamarinTask { [Required] @@ -26,6 +29,12 @@ public class CollectPostILTrimInformation : XamarinTask { [Required] public string SurvivingNativeSymbolsFile { get; set; } = ""; + /// + /// Output file listing the Class.GetHandle calls that survived trimming. + /// + [Required] + public string SurvivingClassesFile { get; set; } = ""; + /// /// Directory for per-assembly cache files, to avoid re-scanning unchanged assemblies. /// @@ -51,17 +60,17 @@ void CollectSurvivingNativeSymbols () continue; var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath); - var cacheFile = Path.Combine (CacheDirectory, assemblyName + ".dlfcn-symbols.cache"); + var cacheFile = Path.Combine (CacheDirectory, assemblyName + ".internal-symbols.cache"); string []? cachedSymbols = null; if (File.Exists (cacheFile) && File.GetLastWriteTimeUtc (cacheFile) >= File.GetLastWriteTimeUtc (assemblyPath)) { cachedSymbols = File.ReadAllLines (cacheFile); - Log.LogMessage (MessageImportance.Low, "Using cached dlfcn symbols for {0}", assemblyName); + Log.LogMessage (MessageImportance.Low, "Using cached internal symbols for {0}", assemblyName); survivingSymbols.UnionWith (cachedSymbols); } else { var assemblySymbols = new HashSet (); - CollectDlfcnSymbolsFromAssembly (assemblyPath, assemblySymbols); + CollectInternalSymbolsFromAssembly (assemblyPath, assemblySymbols); // Write per-assembly cache (sorted for stability). var sortedAssemblySymbols = assemblySymbols.OrderBy (s => s).ToArray (); @@ -71,29 +80,53 @@ void CollectSurvivingNativeSymbols () } } + WriteSymbolsToFile (this, SurvivingNativeSymbolsFile, FilterToDlfcnSymbols (survivingSymbols)); + WriteSymbolsToFile (this, SurvivingClassesFile, FilterToClassSymbols (survivingSymbols)); + } + + public static void WriteSymbolsToFile (XamarinTask task, string file, IEnumerable unsortedSymbols) + { // Write the combined results only if contents changed (sorted for stability). - var sorted = survivingSymbols.OrderBy (s => s).ToArray (); + var sorted = unsortedSymbols.OrderBy (s => s).ToArray (); - if (File.Exists (SurvivingNativeSymbolsFile)) { - var existing = File.ReadAllLines (SurvivingNativeSymbolsFile); - if (existing.SequenceEqual (sorted)) + if (File.Exists (file)) { + var existing = File.ReadAllLines (file); + if (existing.SequenceEqual (sorted)) { + task.Log.LogMessage (MessageImportance.Low, "The file {0} is already up-to-date with {1} symbols", file, sorted.Length); return; + } } - var dir = Path.GetDirectoryName (SurvivingNativeSymbolsFile); - if (!string.IsNullOrEmpty (dir)) - Directory.CreateDirectory (dir); - File.WriteAllLines (SurvivingNativeSymbolsFile, sorted); - Log.LogMessage (MessageImportance.Low, "Found {0} surviving inlined dlfcn symbols", survivingSymbols.Count); + PathUtils.CreateDirectoryForFile (file); + File.WriteAllLines (file, sorted); + task.Log.LogMessage (MessageImportance.Low, "Wrote {0} symbols to {1}", sorted.Length, file); } - static void CollectDlfcnSymbolsFromAssembly (string assemblyPath, HashSet survivingSymbols) + public static IEnumerable FilterToDlfcnSymbols (IEnumerable symbols) { - const string prefix = "xamarin_Dlfcn_"; - const string suffix = "_Native"; + return FilterTo (symbols, "_xamarin_Dlfcn_", "_Native"); + } + public static IEnumerable FilterToClassSymbols (IEnumerable symbols) + { + return FilterTo (symbols, "_xamarin_Class_GetHandle_", "_Native"); + } + + static IEnumerable FilterTo (IEnumerable symbols, string prefix, string suffix) + { + return symbols + .Where (symbol => symbol.StartsWith (prefix, StringComparison.Ordinal) && symbol.EndsWith (suffix, StringComparison.Ordinal)) + .Select (symbol => symbol.Substring (prefix.Length, symbol.Length - prefix.Length - suffix.Length)); + } + + static void CollectInternalSymbolsFromAssembly (string assemblyPath, HashSet survivingSymbols) + { using var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, new ReaderParameters { ReadSymbols = false }); foreach (var module in assembly.Modules) { + if (!module.HasModuleReferences) + continue; + if (!module.ModuleReferences.Any (mr => mr.Name == "__Internal")) + continue; foreach (var type in module.Types) { if (!type.HasMethods) continue; @@ -102,14 +135,7 @@ static void CollectDlfcnSymbolsFromAssembly (string assemblyPath, HashSet public class ComputeNativeAOTSurvivingNativeSymbols : XamarinTask { /// @@ -29,39 +29,17 @@ public class ComputeNativeAOTSurvivingNativeSymbols : XamarinTask { [Required] public string SurvivingNativeSymbolsFile { get; set; } = ""; + /// + /// Output file listing the Class.GetHandle calls that survived trimming. + /// + [Required] + public string SurvivingClassesFile { get; set; } = ""; + public override bool Execute () { - if (!File.Exists (UnresolvedSymbolsFile)) - return !Log.HasLoggedErrors; - - const string prefix = "_xamarin_Dlfcn_"; - const string suffix = "_Native"; - var survivingSymbols = new HashSet (); - - foreach (var sym in File.ReadAllLines (UnresolvedSymbolsFile)) { - if (!sym.StartsWith (prefix) || !sym.EndsWith (suffix)) - continue; - var symbolLength = sym.Length - prefix.Length - suffix.Length; - if (symbolLength <= 0) - continue; - var symbolName = sym.Substring (prefix.Length, symbolLength); - survivingSymbols.Add (symbolName); - } - - var sorted = survivingSymbols.OrderBy (s => s).ToArray (); - - if (File.Exists (SurvivingNativeSymbolsFile)) { - var existing = File.ReadAllLines (SurvivingNativeSymbolsFile); - if (existing.SequenceEqual (sorted)) - return !Log.HasLoggedErrors; - } - - var dir = Path.GetDirectoryName (SurvivingNativeSymbolsFile); - if (!string.IsNullOrEmpty (dir)) - Directory.CreateDirectory (dir); - File.WriteAllLines (SurvivingNativeSymbolsFile, sorted); - Log.LogMessage (MessageImportance.Low, "Found {0} surviving native symbols from NativeAOT", survivingSymbols.Count); - + var unresolvedSymbols = File.Exists (UnresolvedSymbolsFile) ? File.ReadAllLines (UnresolvedSymbolsFile) : []; + CollectPostILTrimInformation.WriteSymbolsToFile (this, SurvivingNativeSymbolsFile, CollectPostILTrimInformation.FilterToDlfcnSymbols (unresolvedSymbols)); + CollectPostILTrimInformation.WriteSymbolsToFile (this, SurvivingClassesFile, CollectPostILTrimInformation.FilterToClassSymbols (unresolvedSymbols)); return !Log.HasLoggedErrors; } } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs index 77705a8dd1d7..bb65800df942 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs @@ -9,13 +9,14 @@ using Microsoft.Build.Framework; using Xamarin.Bundler; +using Xamarin.Utils; #nullable enable namespace Xamarin.MacDev.Tasks { /// /// Performs post-trimming processing, generating native code only for symbols that survived trimming. - /// See docs/code/native-symbols.md for an overview of native symbol handling. + /// See docs/code/native-symbols.md and docs/code/class-handles.md for an overview of native symbol handling. /// public class PostTrimmingProcessing : XamarinTask { [Required] @@ -26,6 +27,13 @@ public class PostTrimmingProcessing : XamarinTask { public ITaskItem [] ReferenceNativeSymbol { get; set; } = []; + /// + /// Files listing calls to Class.GetHandle that survived trimming. Each file contains one symbol name per line. + /// These can come from either ILTrim (CollectPostILTrimInformation) or NativeAOT + /// (ComputeNativeAOTSurvivingNativeSymbols). + /// + public ITaskItem [] SurvivingClassesFiles { get; set; } = []; + /// /// Files listing native symbols that survived trimming. Each file contains one symbol name per line. /// These can come from either ILTrim (CollectPostILTrimInformation) or NativeAOT @@ -33,6 +41,9 @@ public class PostTrimmingProcessing : XamarinTask { /// public ITaskItem [] SurvivingNativeSymbolsFiles { get; set; } = []; + // Type map + public string TypeMapFilePath { get; set; } = ""; + /// /// Output native source files to be compiled and linked. /// @@ -71,41 +82,166 @@ HashSet IgnoredSymbols { public override bool Execute () { - var items = new List (); + var nativeSourceFiles = new List (); + + Directory.CreateDirectory (OutputDirectory); + GenerateInlinedDlfcnNativeCode (nativeSourceFiles); + GenerateInlinedClassGetHandleNativeCode (nativeSourceFiles); - GenerateInlinedDlfcnNativeCode (items); + NativeSourceFiles = nativeSourceFiles.Select (path => { + var item = new Microsoft.Build.Utilities.TaskItem (path); + item.SetMetadata ("Arch", Architecture.ToLowerInvariant ()); + return item; + }).ToArray (); - NativeSourceFiles = items.ToArray (); return !Log.HasLoggedErrors; } - void GenerateInlinedDlfcnNativeCode (List items) + static HashSet ReadUniqueLinesFromFiles (IEnumerable files) { - // Collect all surviving symbols from all input files. - var survivingSymbols = new HashSet (); - foreach (var file in SurvivingNativeSymbolsFiles) { + var lines = new HashSet (); + foreach (var file in files) { var path = file.ItemSpec; if (!File.Exists (path)) continue; - survivingSymbols.UnionWith (File.ReadAllLines (path)); + lines.UnionWith (File.ReadAllLines (path)); + } + return lines; + } + + List FilterOutIgnoredSymbols (HashSet survivingSymbols, bool filterObjectiveCClasses) + { + var rv = new HashSet (survivingSymbols); + + foreach (var rns in ReferenceNativeSymbol) { + var nativeSymbol = rns.ItemSpec; + var symbolMode = rns.GetMetadata ("SymbolMode"); + if (!string.Equals (symbolMode, "Ignore", StringComparison.OrdinalIgnoreCase)) + continue; + + var symbolType = rns.GetMetadata ("SymbolType").ToLowerInvariant (); + switch (symbolType) { + case "objectivecclass": + if (filterObjectiveCClasses && rv.Remove (nativeSymbol)) { + Log.LogMessage (MessageImportance.Low, "Ignoring Objective-C class '{0}'", nativeSymbol); + } + break; + case "function": + case "field": + if (!filterObjectiveCClasses && rv.Remove (nativeSymbol)) { + Log.LogMessage (MessageImportance.Low, "Ignoring native symbol '{0}'", nativeSymbol); + } + break; + default: + Log.LogMessage (MessageImportance.Low, "Ignoring symbol '{0}' with unknown SymbolType '{1}'", nativeSymbol, symbolType); + continue; + } + } + + rv.Remove (""); // no empty symbols + + return rv.OrderBy (v => v).ToList (); + } + + void GenerateInlinedClassGetHandleNativeCode (List items) + { + // Collect all surviving symbols from all input files. + var classes = FilterOutIgnoredSymbols (ReadUniqueLinesFromFiles (SurvivingClassesFiles), filterObjectiveCClasses: true); + + if (classes.Count == 0) { + Log.LogMessage (MessageImportance.Low, "There were no surviving Objective-C classes that require inlined Class.GetHandle native code."); + return; + } + + if (string.IsNullOrEmpty (TypeMapFilePath) || !File.Exists (TypeMapFilePath)) { + Log.LogError ("The type map file '{0}' does not exist. This file is generated by the linker's CoreTypeMapStep. Ensure that trimming ran successfully.", TypeMapFilePath ?? ""); + return; + } + + var typeMapEntries = File.ReadAllLines (TypeMapFilePath) + .Select (line => { + var parts = line.Split ('|'); + string className = ""; + string framework = ""; + string introduced = ""; + bool iswrapper = false; + bool isstubclass = false; + foreach (var part in parts) { + var kvp = part.Split (new char [] { '=' }, 2); + if (kvp.Length != 2) + continue; + var key = kvp [0].Trim (); + var value = kvp [1].Trim (); + switch (key) { + case "Class": + className = value; + break; + case "Framework": + framework = value; + break; + case "Introduced": + introduced = value; + break; + case "IsWrapper": + iswrapper = string.Equals (value, "true", StringComparison.OrdinalIgnoreCase); + break; + case "IsStubClass": + isstubclass = string.Equals (value, "true", StringComparison.OrdinalIgnoreCase); + break; + } + } + return (Class: className, Framework: framework, Introduced: introduced, IsWrapper: iswrapper, IsStubClass: isstubclass); + }) + .ToArray (); + + var typeMap = new Dictionary (); + foreach (var entry in typeMapEntries) { + if (string.IsNullOrEmpty (entry.Class)) + continue; + if (typeMap.ContainsKey (entry.Class)) { + Log.LogError ("Duplicate class '{0}' found in the type map file '{1}'.", entry.Class, TypeMapFilePath); + return; + } + typeMap [entry.Class] = entry; } - var survivingButIgnoredSymbols = survivingSymbols.Intersect (IgnoredSymbols).ToList (); - if (survivingButIgnoredSymbols.Count > 0) { - Log.LogMessage (MessageImportance.Low, "The following symbols survived trimming but are marked as ignored:"); - foreach (var symbol in survivingButIgnoredSymbols) - Log.LogMessage (MessageImportance.Low, " {0}", symbol); - survivingSymbols.ExceptWith (survivingButIgnoredSymbols); + var sb = new StringBuilder (); + sb.AppendLine ($"#include "); + sb.AppendLine ($"#include "); + foreach (var objectiveCClassName in classes) { + // We don't want to import every header under the sun to find the @interface definitions for each class, so we generate + // a forward declaration for each class. To avoid potential issues with missing classes at runtime, we mark each declaration with __attribute__((weak_import)). + // The only exception is that we need to #include Foundation, which means we can't create declarations for Foundation classes. + if (!typeMap.TryGetValue (objectiveCClassName, out var info)) { + sb.AppendLine ($"__attribute__((weak_import)) @interface {objectiveCClassName} : NSObject @end // no objc type found"); + } else if (info.IsWrapper && info.Framework == "Foundation") { + // This is a special case for wrapper classes in the Foundation framework. Since we need to #include Foundation, we can't create a forward declaration for these classes. However, since they are wrappers, we know they won't be missing at runtime, so we don't need to mark them with __attribute__((weak_import)). + sb.AppendLine ($"// The class '{objectiveCClassName}' comes from the Foundation framework, so no generated @interface declaration."); + } else { + if (info.IsStubClass) + sb.AppendLine ("__attribute__((objc_class_stub)) __attribute__((objc_subclassing_restricted))"); + sb.AppendLine ($"__attribute__((weak_import)) @interface {objectiveCClassName} : NSObject @end // is stub: {info.IsStubClass}"); + } + sb.AppendLine ($"Class xamarin_Class_GetHandle_{objectiveCClassName}_Native ();"); + sb.AppendLine ($"Class xamarin_Class_GetHandle_{objectiveCClassName}_Native () {{ return [{objectiveCClassName} class]; }}"); + sb.AppendLine (); } + var outputPath = Path.Combine (OutputDirectory, "inlined-class-gethandle.m"); + FileUtils.WriteIfDifferent (outputPath, sb.ToString (), (msg) => Log.LogMessage (MessageImportance.Low, msg)); + + items.Add (outputPath); + } + + void GenerateInlinedDlfcnNativeCode (List items) + { + var survivingSymbols = FilterOutIgnoredSymbols (ReadUniqueLinesFromFiles (SurvivingNativeSymbolsFiles), filterObjectiveCClasses: false); + if (survivingSymbols.Count == 0) { Log.LogMessage (MessageImportance.Low, "There were no surviving symbols that require inlined dlfcn native code."); return; } - Directory.CreateDirectory (OutputDirectory); - var outputPath = Path.Combine (OutputDirectory, "inlined-dlfcn.c"); - var sb = new StringBuilder (); // The generated C code uses 'extern void*' declarations and returns the address of the symbol. // This is intentional: it allows the native linker to resolve the symbol at link time, which @@ -118,17 +254,10 @@ void GenerateInlinedDlfcnNativeCode (List items) sb.AppendLine (); } - var content = sb.ToString (); - if (File.Exists (outputPath) && File.ReadAllText (outputPath) == content) { - Log.LogMessage (MessageImportance.Low, "Inlined dlfcn native code is up to date"); - } else { - File.WriteAllText (outputPath, content); - Log.LogMessage (MessageImportance.Low, "Generated inlined dlfcn native code with {0} symbols", survivingSymbols.Count); - } + var outputPath = Path.Combine (OutputDirectory, "inlined-dlfcn.c"); + FileUtils.WriteIfDifferent (outputPath, sb.ToString (), (msg) => Log.LogMessage (MessageImportance.Low, msg)); - var item = new Microsoft.Build.Utilities.TaskItem (outputPath); - item.SetMetadata ("Arch", Architecture.ToLowerInvariant ()); - items.Add (item); + items.Add (outputPath); } } } diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index a90eed1e9ffe..3b7c402bd3cf 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -1119,7 +1119,7 @@ protected virtual void OnRegisterCategory (ObjCType type, [NotNullIfNotNull (nam protected abstract ConnectAttribute? GetConnectAttribute (TProperty property); // Return null if no attribute is found. Do not consider inherited properties. public abstract ProtocolAttribute? GetProtocolAttribute (TType type); // Return null if no attribute is found. Do not consider base types. protected abstract IEnumerable GetProtocolMemberAttributes (TType type); // Return null if no attributes found. Do not consider base types. - protected virtual Version? GetSdkIntroducedVersion (TType obj, out string? message) { message = null; return null; } // returns the sdk version when the type was introduced for the current platform (null if all supported versions) + public virtual Version? GetSdkIntroducedVersion (TType obj, out string? message) { message = null; return null; } // returns the sdk version when the type was introduced for the current platform (null if all supported versions) protected abstract Version GetSDKVersion (); protected abstract TType? GetProtocolAttributeWrapperType (TType type); // Return null if no attribute is found. Do not consider base types. public abstract BindAsAttribute? GetBindAsAttribute (TMethod method, int parameter_index); // If parameter_index = -1 then get the attribute for the return type. Return null if no attribute is found. Must consider base method. @@ -2639,6 +2639,26 @@ protected string ToSignature (TType type, ObjCMember member, bool forProperty = throw ErrorHelper.CreateError (4101, Errors.MT4101, GetTypeFullName (type)); } + // Gets the Objective-C name for the given type. + // Returns false if the type in question isn't exported to Objective-C, and thus doesn't have an Objective-C name. + public bool TryGetExportedTypeName (TType type, [NotNullWhen (true)] out string? name) + { + name = null; + + var registerAttribute = GetRegisterAttribute (type); + if (registerAttribute is null) + return false; + + if (!registerAttribute.IsWrapper) + return false; + + if (HasProtocolAttribute (type)) + return false; + + name = GetExportedTypeName (type, registerAttribute); + return true; + } + public string GetExportedTypeName (TType type, RegisterAttribute? register_attribute) { string? name = null; diff --git a/src/coreml.cs b/src/coreml.cs index b723bf9afee6..c4d6c7038e82 100644 --- a/src/coreml.cs +++ b/src/coreml.cs @@ -1581,9 +1581,9 @@ interface MLWritable { } #if !XAMCORE_5_0 - [Deprecated (PlatformName.MacOSX, 13, 3, message: "Use Background Assets or 'NSUrlSession' instead.")] - [Deprecated (PlatformName.MacCatalyst, 16, 4, message: "Use Background Assets or 'NSUrlSession' instead.")] - [Deprecated (PlatformName.iOS, 16, 4, message: "Use Background Assets or 'NSUrlSession' instead.")] + [Obsoleted (PlatformName.MacOSX, 13, 3, message: "Use Background Assets or 'NSUrlSession' instead.")] + [Obsoleted (PlatformName.MacCatalyst, 16, 4, message: "Use Background Assets or 'NSUrlSession' instead.")] + [Obsoleted (PlatformName.iOS, 16, 4, message: "Use Background Assets or 'NSUrlSession' instead.")] [iOS (14, 0)] [MacCatalyst (14, 0)] [NoTV] @@ -1613,9 +1613,9 @@ interface MLModelCollection { #endif // !XAMCORE_5_0 #if !XAMCORE_5_0 - [Deprecated (PlatformName.MacOSX, 13, 3, message: "Use Background Assets or 'NSUrlSession' instead.")] - [Deprecated (PlatformName.MacCatalyst, 16, 4, message: "Use Background Assets or 'NSUrlSession' instead.")] - [Deprecated (PlatformName.iOS, 16, 4, message: "Use Background Assets or 'NSUrlSession' instead.")] + [Obsoleted (PlatformName.MacOSX, 13, 3, message: "Use Background Assets or 'NSUrlSession' instead.")] + [Obsoleted (PlatformName.MacCatalyst, 16, 4, message: "Use Background Assets or 'NSUrlSession' instead.")] + [Obsoleted (PlatformName.iOS, 16, 4, message: "Use Background Assets or 'NSUrlSession' instead.")] [iOS (14, 0)] [MacCatalyst (14, 0)] [NoTV] diff --git a/tests/common/test-variations.csproj b/tests/common/test-variations.csproj index ea1da526a288..35762fdeb2dd 100644 --- a/tests/common/test-variations.csproj +++ b/tests/common/test-variations.csproj @@ -10,6 +10,7 @@ + @@ -25,6 +26,8 @@ + + @@ -68,6 +71,21 @@ <_TestVariationApplied>true + + + true + <_IsPublishing>true + full + + strict + $(DefineConstants);STATIC_NATIVE_SYMBOL_LOOKUP + + strict + managed-static + + <_TestVariationApplied>true + + partial <_TestVariationApplied>true @@ -161,6 +179,17 @@ $(DefineConstants);STATIC_NATIVE_SYMBOL_LOOKUP + + compatibility + <_TestVariationApplied>true + + + + strict + managed-static + <_TestVariationApplied>true + + <_InvalidTestVariations Include="$(TestVariation.Split('|'))" Exclude="@(TestVariations)" /> diff --git a/tests/dotnet/UnitTests/RegistrarTest.cs b/tests/dotnet/UnitTests/RegistrarTest.cs index 003efbdc3281..f7aad07cb565 100644 --- a/tests/dotnet/UnitTests/RegistrarTest.cs +++ b/tests/dotnet/UnitTests/RegistrarTest.cs @@ -76,6 +76,7 @@ public void ClassRewriterTest (ApplePlatform platform, bool rewriteHandles) // enable the linker (so that the main assembly is modified) properties ["LinkMode"] = "full"; properties ["MtouchLink"] = "full"; + properties ["InlineClassGetHandle"] = "disabled"; if (rewriteHandles) properties ["MtouchExtraArgs"] = "--optimize=redirect-class-handles"; diff --git a/tests/linker/link all/dotnet/shared.csproj b/tests/linker/link all/dotnet/shared.csproj index f11e80546aac..6b75ec2a2d4e 100644 --- a/tests/linker/link all/dotnet/shared.csproj +++ b/tests/linker/link all/dotnet/shared.csproj @@ -86,4 +86,10 @@ + + + + + + diff --git a/tests/linker/trimmode link/dotnet/shared.csproj b/tests/linker/trimmode link/dotnet/shared.csproj index 525978b17599..48401810a36d 100644 --- a/tests/linker/trimmode link/dotnet/shared.csproj +++ b/tests/linker/trimmode link/dotnet/shared.csproj @@ -88,4 +88,10 @@ + + + + + + diff --git a/tests/monotouch-test/dotnet/shared.csproj b/tests/monotouch-test/dotnet/shared.csproj index 52b2396eff16..989a89db0bef 100644 --- a/tests/monotouch-test/dotnet/shared.csproj +++ b/tests/monotouch-test/dotnet/shared.csproj @@ -54,6 +54,10 @@ + + + + diff --git a/tests/xharness/Jenkins/TestVariationsFactory.cs b/tests/xharness/Jenkins/TestVariationsFactory.cs index 812e53156a69..00c05d4882c6 100644 --- a/tests/xharness/Jenkins/TestVariationsFactory.cs +++ b/tests/xharness/Jenkins/TestVariationsFactory.cs @@ -117,10 +117,12 @@ IEnumerable GetTestData (RunTestTask test) yield return new TestData { Variation = "Debug (interpreter)", TestVariation = "interpreter", Ignored = ignore }; yield return new TestData { Variation = "Release (interpreter)", TestVariation = "release|interpreter", Ignored = ignore }; } + yield return new TestData { Variation = $"Release (compat inline Class.GetHandle)", TestVariation = "inline-class-gethandle-compat|release", Ignored = ignore }; + yield return new TestData { Variation = $"Release (strict inline Class.GetHandle)", TestVariation = "inline-class-gethandle-strict|release", Ignored = ignore }; yield return new TestData { Variation = $"Release (compat inline dlfcn)", TestVariation = "inline-dlfcn-methods-compat|release", Ignored = ignore }; yield return new TestData { Variation = $"Release (strict inline dlfcn, link sdk)", TestVariation = "inline-dlfcn-methods-strict|linksdk|release", Ignored = ignore }; if (mac_supports_arm64) - yield return new TestData { Variation = $"Release (NativeAOT, .NET 11 defaults)", TestVariation = "inline-dlfcn-methods-strict|nativeaot|release", Ignored = ignore, RuntimeIdentifier = arm64_sim_runtime_identifier }; // it's necessary to specify RID, because NativeAOT defaults to building for device + yield return new TestData { Variation = $"Release (NativeAOT, .NET 11 defaults)", TestVariation = "nativeaot-net11-defaults|release", Ignored = ignore, RuntimeIdentifier = arm64_sim_runtime_identifier }; // it's necessary to specify RID, because NativeAOT defaults to building for device break; case "introspection": if (mac_supports_arm64) @@ -156,6 +158,8 @@ IEnumerable GetTestData (RunTestTask test) yield return new TestData { Variation = "Release (NativeAOT)", TestVariation = "release|nativeaot", Ignored = ignore }; yield return new TestData { Variation = "Release (NativeAOT, ARM64)", TestVariation = "release|nativeaot", Ignored = !mac_supports_arm64 ? true : ignore, RuntimeIdentifier = arm64_runtime_identifier }; yield return new TestData { Variation = "Release (NativeAOT, x64)", TestVariation = "release|nativeaot", Ignored = ignore, RuntimeIdentifier = x64_runtime_identifier }; + if (mac_supports_arm64) + yield return new TestData { Variation = $"Release (NativeAOT, .NET 11 defaults)", TestVariation = "release|nativeaot-net11-defaults", Ignored = ignore }; yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT)", TestVariation = "trimmable-static-registrar|nativeaot|release", Ignored = ignore }; yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT, ARM64)", TestVariation = "trimmable-static-registrar|nativeaot|release", Ignored = !mac_supports_arm64 ? true : ignore, RuntimeIdentifier = arm64_runtime_identifier }; yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT, x64)", TestVariation = "trimmable-static-registrar|nativeaot|release", Ignored = ignore, RuntimeIdentifier = x64_runtime_identifier }; @@ -168,6 +172,8 @@ IEnumerable GetTestData (RunTestTask test) yield return new TestData { Variation = "Debug (interpreter)", TestVariation = "interpreter", Ignored = ignore }; yield return new TestData { Variation = "Release (interpreter)", TestVariation = "release|interpreter", Ignored = ignore }; } + yield return new TestData { Variation = $"Release (compat inline dlfcn)", TestVariation = "inline-dlfcn-methods-compat|release", Ignored = ignore }; + yield return new TestData { Variation = $"Release (strict inline dlfcn, link sdk)", TestVariation = "inline-dlfcn-methods-strict|linksdk|release", Ignored = ignore }; break; } break; diff --git a/tools/common/DerivedLinkContext.cs b/tools/common/DerivedLinkContext.cs index 743b1b768461..0f639be2e7b5 100644 --- a/tools/common/DerivedLinkContext.cs +++ b/tools/common/DerivedLinkContext.cs @@ -41,6 +41,9 @@ public class DerivedLinkContext : LinkContext { // true/false = corresponding constant value Dictionary? isdirectbinding_value; + // A map from Objective-C class name to C# type + Dictionary? objectiveCTypeInfo; + // Store interfaces the linker has linked away so that the static registrar can access them. public Dictionary> ProtocolImplementations { get; private set; } = new Dictionary> (); // Store types the linker has linked away so that the static registrar can access them. @@ -73,6 +76,7 @@ public AssemblyDefinition Corlib { return corlib; } } + public HashSet? CachedIsNSObject { get { return cached_isnsobject; } set { cached_isnsobject = value; } @@ -83,6 +87,11 @@ public HashSet? CachedIsNSObject { set { isdirectbinding_value = value; } } + public Dictionary? ObjectiveCTypeInfo { + get { return objectiveCTypeInfo; } + set { objectiveCTypeInfo = value; } + } + public IList DataContract { get { return srs_data_contract; @@ -237,8 +246,11 @@ public void AddLinkedAwayType (TypeDefinition td) } #if !LEGACY_TOOLS - public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttributeProvider provider, MethodDefinition? methodForErrorReporting = null) + public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttributeProvider? provider, MethodDefinition? methodForErrorReporting = null) { + if (provider is null) + return false; + if (!App.IsSimulatorBuild) { LinkerConfiguration.Report (LinkerConfiguration.Context, ErrorHelper.CreateError (99, "HasAvailabilityAttributesShowingUnavailableInSimulator should not be called when not building for the simulator. Please file an issue at https://github.com/dotnet/macios/issues.")); return false; diff --git a/tools/common/FileUtils.cs b/tools/common/FileUtils.cs index f8b3c617674f..7b406fd24ed6 100644 --- a/tools/common/FileUtils.cs +++ b/tools/common/FileUtils.cs @@ -75,5 +75,19 @@ public static bool UpdateFile (string targetFile, Action createOutput) } } + + public static void WriteIfDifferent (string path, string contents, Action log) + { + if (!File.Exists (path)) { + log ($"File '{path}' contents are not up-to-date, because the file doesn't exist."); + File.WriteAllText (path, contents); + } else if (string.Equals (contents, File.ReadAllText (path), StringComparison.Ordinal)) { + log ($"File '{path}' contents are up-to-date."); + return; + } else { + log ($"File '{path}' contents are not up-to-date."); + File.WriteAllText (path, contents); + } + } } } diff --git a/tools/common/PathUtils.cs b/tools/common/PathUtils.cs index c1675692caef..355253f5c2a7 100644 --- a/tools/common/PathUtils.cs +++ b/tools/common/PathUtils.cs @@ -422,5 +422,25 @@ static bool IsLongPathsEnabledRegistry { } } } + + public static void CreateDirectoryForFile (string? file) + { +#if NET + if (string.IsNullOrEmpty (file)) +#else + if (string.IsNullOrEmpty (file) || file is null) +#endif + return; + + var dir = Path.GetDirectoryName (file); +#if NET + if (string.IsNullOrEmpty (dir)) +#else + if (string.IsNullOrEmpty (dir) || dir is null) +#endif + return; + + Directory.CreateDirectory (dir); + } } } diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 2170362d0c79..9891a0c2ec6e 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -1591,7 +1591,7 @@ protected override IEnumerable GetProtocolMemberAttribu } #if !LEGACY_TOOLS - bool GetDotNetAvailabilityAttribute (ICustomAttribute ca, ApplePlatform currentPlatform, out Version? sdkVersion, out string? message) + public bool GetDotNetAvailabilityAttribute (ICustomAttribute ca, ApplePlatform currentPlatform, out Version? sdkVersion, out string? message) { var caType = ca.AttributeType; @@ -1601,6 +1601,7 @@ bool GetDotNetAvailabilityAttribute (ICustomAttribute ca, ApplePlatform currentP string supportedPlatformAndVersion; switch (ca.ConstructorArguments.Count) { case 1: + case 2: // second argument can be a message supportedPlatformAndVersion = (string) ca.ConstructorArguments [0].Value; break; default: @@ -1679,7 +1680,7 @@ bool CollectAvailabilityAttributes (IEnumerable attributes, ou return false; } - protected override Version? GetSdkIntroducedVersion (TypeReference obj, out string? message) + public override Version? GetSdkIntroducedVersion (TypeReference obj, out string? message) { TypeDefinition td = obj.Resolve (); @@ -2025,7 +2026,7 @@ public bool IsCustomType (ObjCType type) return false; } - bool IsPlatformType (TypeReference type) + public bool IsPlatformType (TypeReference type) { if (type.IsNested) return false; diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index 5013997108d1..1b3908f009ea 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -70,6 +70,7 @@ public AssemblyDefinition SystemConsoleAssembly { Dictionary> type_map = new (); Dictionary method_map = new (); Dictionary field_map = new (); + Dictionary created_types = new (); public AppBundleRewriter (LinkerConfiguration configuration) { @@ -1450,6 +1451,7 @@ public void ClearCurrentAssembly () type_map.Clear (); method_map.Clear (); field_map.Clear (); + created_types.Clear (); } public CustomAttribute CreateAttribute (MethodReference constructor) @@ -1676,5 +1678,53 @@ static bool DebugAttributes { return debug_attributes.Value; } } + + public TypeDefinition GetOrCreateType (ModuleDefinition module, string @namespace, string @typename, out bool created) + { + created = false; + + var fullName = @namespace + "." + typename; + if (!created_types.TryGetValue (fullName, out var cachedTypeDefinition)) { + cachedTypeDefinition = module.Types.FirstOrDefault (t => t.Namespace == @namespace && t.Name == typename); + if (cachedTypeDefinition is null) { + cachedTypeDefinition = new TypeDefinition (@namespace, typename, TypeAttributes.Public | TypeAttributes.Sealed, module.TypeSystem.Object); + module.Types.Add (cachedTypeDefinition); + created = true; + } + created_types [fullName] = cachedTypeDefinition; + } + + return cachedTypeDefinition; + } + + public MethodDefinition CreateInternalPInvoke (ModuleDefinition module, string @namespace, string @typename, string methodName, out bool created) + { + var cachedTypeDefinition = GetOrCreateType (module, @namespace, @typename, out _); + var nativeMethod = methodName; + var rv = cachedTypeDefinition.Methods.FirstOrDefault (m => m.Name == methodName); + if (rv is not null) { + created = false; + return rv; // already exists, no need to create it again + } + + // [DllImport ("__Internal")] + // static extern IntPtr {methodName} (); + + rv = new MethodDefinition (methodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PInvokeImpl, System_IntPtr); + rv.IsPreserveSig = true; + + var mod = module.ModuleReferences.FirstOrDefault (mr => mr.Name == "__Internal"); + if (mod is null) { + mod = new ModuleReference ("__Internal"); + module.ModuleReferences.Add (mod); + } + rv.PInvokeInfo = new PInvokeInfo (PInvokeAttributes.CharSetNotSpec | PInvokeAttributes.CallConvCdecl, nativeMethod, mod); + + cachedTypeDefinition.Methods.Add (rv); + + created = true; + + return rv; + } } } diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 8458b9030239..b42db901cbad 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -32,6 +32,7 @@ public class LinkerConfiguration { public bool HybridGlobalization { get; private set; } public InlineDlfcnMethodsMode InlineDlfcnMethods { get; set; } public bool InlineDlfcnMethodsEnabled => InlineDlfcnMethods != InlineDlfcnMethodsMode.Disabled; + public InlineClassGetHandleMode InlineClassGetHandle { get; set; } // Per-assembly field symbols collected by InlineDlfcnMethodsStep, keyed by assembly name. public Dictionary> InlinedDlfcnFields { get; } = new Dictionary> (); // All [Field] symbol names collected by ProcessExportedFields, used in compatibility mode. @@ -45,6 +46,7 @@ public class LinkerConfiguration { public string RelativeAppBundlePath { get; private set; } = string.Empty; public Version? SdkVersion { get; private set; } public string SdkRootDirectory { get; private set; } = string.Empty; + public string TypeMapFilePath { get; set; } = string.Empty; public int Verbosity => Driver.Verbosity; public string XamarinNativeLibraryDirectory { get; private set; } = string.Empty; @@ -210,11 +212,17 @@ public static LinkerConfiguration GetInstance (LinkContext context) case "FrameworkAssembly": FrameworkAssemblies.Add (value); break; + case "InlineClassGetHandle": + if (Enum.TryParse (value, true, out var inlineClassGetHandleMode)) + InlineClassGetHandle = inlineClassGetHandleMode; + else if (string.IsNullOrEmpty (value)) + InlineClassGetHandle = InlineClassGetHandleMode.Disabled; + else + throw new InvalidOperationException ($"Unknown InlineClassGetHandle value: {value}"); + break; case "InlineDlfcnMethods": if (Enum.TryParse (value, true, out var inlineDlfcnMode)) InlineDlfcnMethods = inlineDlfcnMode; - else if (string.Equals (value, "compatibility", StringComparison.OrdinalIgnoreCase)) - InlineDlfcnMethods = InlineDlfcnMethodsMode.Compat; else if (string.IsNullOrEmpty (value)) InlineDlfcnMethods = InlineDlfcnMethodsMode.Disabled; else @@ -377,6 +385,9 @@ public static LinkerConfiguration GetInstance (LinkContext context) case "TypeMapAssemblyName": Application.TypeMapAssemblyName = value; break; + case "TypeMapFilePath": + TypeMapFilePath = value; + break; case "TypeMapOutputDirectory": Application.TypeMapOutputDirectory = value; break; @@ -567,6 +578,7 @@ public void Write () Console.WriteLine ($" SdkRootDirectory: {SdkRootDirectory}"); Console.WriteLine ($" SdkVersion: {SdkVersion}"); Console.WriteLine ($" TypeMapAssemblyName: {Application.TypeMapAssemblyName}"); + Console.WriteLine ($" TypeMapFilePath: {TypeMapFilePath}"); Console.WriteLine ($" TypeMapOutputDirectory: {Application.TypeMapOutputDirectory}"); Console.WriteLine ($" UseInterpreter: {Application.UseInterpreter}"); Console.WriteLine ($" UseLlvm: {Application.IsLLVM}"); @@ -665,4 +677,12 @@ public enum InlineDlfcnMethodsMode { Disabled, Strict, Compat, + Compatibility = Compat, +} + +public enum InlineClassGetHandleMode { + Disabled, + Strict, + Compat, + Compatibility = Compat, } diff --git a/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs b/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs new file mode 100644 index 000000000000..1a48cba7b3b0 --- /dev/null +++ b/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs @@ -0,0 +1,264 @@ +using System.Linq; +using System.Text; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; + +using Xamarin.Bundler; +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.Linker.Steps; + +// See docs/code/class-handles.md for an overview of class handle handling. + +// Find all the references to Objective-C classes for each assembly. +// * If an assembly is trimmed, we replace calls to (inline) Class.GetHandle with a P/Invoke that fetches the class in question. +// * If an assembly is not trimmed, we do not inline Class.GetHandle, but we keep a list of all the Objective-C classes that are used, so that we can tell the native linker about them so they're not linked away by the native linker. +// +// The sticky problem is that we can't inspect the managed output from NativeAOT, which means +// we can't list the Objective-C classes NativeAOT-compiled assemblies using. To get around this +// problem, we convert calls to Class.GetHandle to a P/Invoke which fetches the native Objective-C +// class handle directly - because we _can_ list the P/Invoke calls from NativeAOT-compiled code. +// +// For non-trimmed assemblies, we don't need to do this, because we know nothing from the assembly +// will be trimmed away. This has the added advantage of being Hot Reload compatible, because we +// can't modify assemblies when we're doing Hot Reload. + +public class InlineClassGetHandleStep : AssemblyModifierStep { + + protected override string Name { get; } = "Inline Class GetHandle"; + protected override int ErrorCode { get; } = 2262; + + bool strictMode; + bool? inlining_enabled; + + Dictionary objectiveCTypeMap = new (); + + public const string PInvokePrefix = "xamarin_Class_GetHandle_"; + public const string PInvokeSuffix = "_Native"; + + protected override void TryProcess () + { + strictMode = Configuration.InlineClassGetHandle == InlineClassGetHandleMode.Strict; + + if (strictMode && Configuration.Application.Registrar == Bundler.RegistrarMode.Dynamic) { + Report (ErrorHelper.CreateError (Configuration.Application, 2262, null, Errors.MX2262)); + } + + objectiveCTypeMap = DerivedLinkContext.StaticRegistrar.Types.ToDictionary (v => v.Value.ExportedName, v => v.Value); + + if (!string.IsNullOrEmpty (Configuration.TypeMapFilePath)) { + var sb = new StringBuilder (); + foreach (var info in objectiveCTypeMap.Values.OrderBy (v => v.ExportedName)) { + var td = info.Type.Resolve (); + if (td is null) + continue; + var introduced = DerivedLinkContext.StaticRegistrar.GetSdkIntroducedVersion (td, out _); + Frameworks.TryGetFramework (App, td, out string? framework); + sb.AppendLine ($"Class={info.ExportedName}|Framework={framework}|Introduced={introduced}|IsWrapper={info.IsWrapper}|IsStubClass={info.IsStubClass}"); + } + Driver.WriteIfDifferent (Configuration.TypeMapFilePath, sb.ToString ()); + } + + base.TryProcess (); + } + + protected override bool IsActiveFor (AssemblyDefinition assembly) + { + if (!Configuration.Profile.IsOrReferencesProductAssembly (assembly)) + return false; + + // we have to process both trimmed and non-trimmed assemblies. + + return true; + } + + protected override bool ModifyAssembly (AssemblyDefinition assembly) + { + inlining_enabled = Annotations.GetAction (assembly) == AssemblyAction.Link; + var modified = base.ModifyAssembly (assembly); + inlining_enabled = null; + return modified; + } + + protected override bool ProcessType (TypeDefinition type) + { + var modified = false; + + if (inlining_enabled == true) { + modified |= ProcessMethods (type); + } else { + if (ListExportedSymbols.TryGetRequiredObjectiveCType (DerivedLinkContext, type, out var exportedName)) { + DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (exportedName).AddMember (type); + } + } + return modified; + } + + MethodDefinition GetOrCreatePInvokeMethod (MethodDefinition callingMethod, string objectiveCClassName) + { + // [DllImport ("__Internal")] + // static extern IntPtr xamarin_Class_GetClassHandle_{objectiveCClassName}_Native (); + + return abr.CreateInternalPInvoke (callingMethod.Module, "ObjCRuntime", "Class", $"{PInvokePrefix}{objectiveCClassName}{PInvokeSuffix}", out _); + } + + protected override bool ProcessMethod (MethodDefinition method) + { + var modified = false; + + if (!method.HasBody) + return modified; + + if (method.DeclaringType.Name == "Class" && method.DeclaringType.Namespace == "ObjCRuntime") + return modified; // don't process the Class methods themselves + + bool isOurOwnCode () + { + // Don't show warnings for a few places in our own code where we call Class.GetHandle in un-inlinable ways. + switch (method.DeclaringType.Namespace) { + case "Registrar": + switch (method.DeclaringType.Name) { + case "DynamicRegistrar": + switch (method.Name) { + case "OnReloadType": + case "OnRegisterType": + return true; + } + break; + } + break; + } + return false; + } + + foreach (var instr in method.Body.Instructions) { + if (instr.Operand is not MethodReference mr) + continue; + if (mr.DeclaringType.Name != "Class" || mr.DeclaringType.Namespace != "ObjCRuntime") + continue; + if (mr.Name != "GetHandle" && mr.Name != "GetHandleIntrinsic") + continue; + if (mr.Parameters.Count != 1) + continue; + if (!mr.Parameters [0].ParameterType.Is ("System", "String")) + continue; + if (!mr.ReturnType.Is ("ObjCRuntime", "NativeHandle")) + continue; + + var ldstr = instr.Previous; + if (ldstr.OpCode != OpCodes.Ldstr) { + if (!isOurOwnCode ()) + Driver.Log (3, "Unknown or unsupported pattern in call to Class.GetHandle in '{0}': {1}. The call will not be inlined.", FormatMethod (method), ldstr); + continue; + } + if (ldstr.Operand is not string objectiveCClassName) { + if (!isOurOwnCode ()) + Driver.Log (3, "Unknown or unsupported pattern in call to Class.GetHandle in '{0}': {1}. The call will not be inlined.", FormatMethod (method), ldstr.Operand); + continue; + } + + if (!objectiveCTypeMap.TryGetValue (objectiveCClassName, out var objCType)) { + Driver.Log (3, "Could not find a managed type for the Objective-C type '{1}' in the call to Class.GetHandle in '{0}', assuming the Objective-C type is available in the simulator.", FormatMethod (method), objectiveCClassName); + } + + if (!strictMode) { + if (objCType is not null && objCType.Type == method.DeclaringType) { + // This is a call to Class.GetHandle for the same class that it's being called from, this is OK. + } else if (ListExportedSymbols.TryGetRequiredObjectiveCType (DerivedLinkContext, method.DeclaringType, out var exportedName)) { + if (exportedName != objectiveCClassName) { + Driver.Log (3, "The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but the declaring type's exported name is '{2}', not '{1}'. Since we're in compat mode, we're assuming the class should not be preserved.", FormatMethod (method), objectiveCClassName, exportedName); + continue; + } + } else { + if (App.StaticRegistrar.GetCategoryAttribute (method.DeclaringType) is not null) + Driver.Log (3, "The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but we couldn't determine whether this class should be statically preserved or not. Since we're in compat mode, we're assuming the class should not be statically preserved.", FormatMethod (method), objectiveCClassName); + continue; + } + } + + // Check if the Objective-C class is listed as a ReferenceNativeSymbol with Ignore mode, and if so, don't inline the call to Class.GetHandle (because the native symbol won't be available at link time) + var existingSymbol = DerivedLinkContext.RequiredSymbols.Find (Symbol.ObjectiveCPrefix + objectiveCClassName); + if (existingSymbol is not null && existingSymbol.Type == SymbolType.ObjectiveCClass && existingSymbol.Mode == SymbolMode.Ignore) { + Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the class is listed as a ReferenceNativeSymbol with Ignore mode.", objectiveCClassName, FormatMethod (method)); + continue; + } + + if (objCType is not null) { + if (DerivedLinkContext.App.IsSimulatorBuild) { + if (DerivedLinkContext.HasAvailabilityAttributesShowingUnavailableInSimulator (objCType.Type.Resolve (), method)) { + Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the type is marked with an attribute indicating it's not available in the simulator.", objectiveCClassName, FormatMethod (method)); + continue; + } + } + + if (IsUnsupported (objCType.Type.Resolve ())) { + Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the type is marked with an [UnsupportedOSPlatform] attribute.", objectiveCClassName, FormatMethod (method)); + continue; + } + + if (objCType.Methods is null && DerivedLinkContext.StaticRegistrar.IsPlatformType (objCType.Type) && !objCType.IsProtocol && !objCType.IsCategory && objCType.IsModel) { + // The static registrar skips generating code for this type, so we shouldn't inline calls to Class.GetHandle for it, because the P/Invoke we generate won't be able to find the native symbol for it. + continue; + } + + if (Frameworks.TryGetFramework (App, objCType.Type.Resolve (), out Framework? framework) && framework.IsFrameworkUnavailable (App)) { + Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the framework {2} is unavailable.", objectiveCClassName, FormatMethod (method), framework.Name); + continue; + } + + if (objCType.Type.Is ("UIKit", "UITitlebar")) { + // UITitlebar is a weird special case, the class exists in the headers, and it's documented online, but it's not possible to link with it (not even in an Xcode project, it's not in any .tbd files). + continue; + } + } + + ldstr.OpCode = OpCodes.Call; + ldstr.Operand = GetOrCreatePInvokeMethod (method, objectiveCClassName); + + instr.OpCode = OpCodes.Call; + instr.Operand = abr.NativeObject_op_Implicit_NativeHandle; + + modified = true; + } + + return modified; + } + + bool IsUnsupported (TypeDefinition? type) + { + if (type is null) + return false; + + if (!type.HasCustomAttributes) + return false; + + foreach (var ca in type.CustomAttributes) { + if (!ca.AttributeType.Is ("System.Runtime.Versioning", "UnsupportedOSPlatformAttribute")) + continue; + + if (!DerivedLinkContext.StaticRegistrar.GetDotNetAvailabilityAttribute (ca, App.Platform, out var sdkVersion, out _)) + continue; + + if (sdkVersion is null) + return true; // if there's no version, then it's always unavailable + + return sdkVersion <= App.SdkVersion; + } + + return false; + } + + static string FormatMethod (MethodReference method) + { + var rv = method.FullName; + var idx = rv.IndexOf (' '); + if (idx > 0) + rv = rv.Substring (idx + 1); + return rv; + } +} diff --git a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs index d6b214986d02..99b67d4a9984 100644 --- a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs +++ b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs @@ -28,6 +28,9 @@ public class InlineDlfcnMethodsStep : AssemblyModifierStep { bool strictMode; + public const string PInvokePrefix = "xamarin_Dlfcn_"; + public const string PInvokeSuffix = "_Native"; + protected override void TryProcess () { strictMode = Configuration.InlineDlfcnMethods == InlineDlfcnMethodsMode.Strict; @@ -99,18 +102,15 @@ TypeDefinition GetDlfcnType (ModuleDefinition module, string @namespace, string? { var frameworkOverride = !string.IsNullOrEmpty (fieldLibraryName) ? fieldLibraryName : current_framework; var ns = string.IsNullOrEmpty (frameworkOverride) ? @namespace : frameworkOverride; - var dlfcn = module.Types.FirstOrDefault (t => t.Namespace == ns && t.Name == "Dlfcn"); - if (dlfcn is null) { - dlfcn = new TypeDefinition (ns, "Dlfcn", TypeAttributes.NotPublic | TypeAttributes.Sealed, module.TypeSystem.Object); - module.Types.Add (dlfcn); - + var rv = abr.GetOrCreateType (module, ns, "Dlfcn", out var created); + if (created) { if (!string.IsNullOrEmpty (frameworkOverride)) { - var attrib = new CustomAttribute (abr.ObjectiveCFrameworkAttribute_ctor_String); + var attrib = abr.CreateAttribute (abr.ObjectiveCFrameworkAttribute_ctor_String); attrib.ConstructorArguments.Add (new CustomAttributeArgument (abr.System_String, frameworkOverride)); - dlfcn.CustomAttributes.Add (attrib); + rv.CustomAttributes.Add (attrib); } } - return dlfcn; + return rv; } void AddField (string assemblyName, string symbolName) @@ -124,30 +124,13 @@ void AddField (string assemblyName, string symbolName) MethodDefinition GetOrCreatePInvokeMethod (MethodDefinition callingMethod, string symbolName) { - var dlfcn = GetDlfcnType (callingMethod); - var methodName = $"xamarin_Dlfcn_{symbolName}_Native"; - var nativeMethod = methodName; - var rv = dlfcn.Methods.FirstOrDefault (m => m.Name == methodName); - if (rv is not null) - return rv; // already exists, no need to create it again - // [DllImport ("__Internal")] // static extern IntPtr xamarin_Dlfcn_{symbolName}_Native (); - rv = new MethodDefinition (methodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PInvokeImpl, abr.System_IntPtr); - rv.IsPreserveSig = true; - - var mod = callingMethod.Module.ModuleReferences.FirstOrDefault (mr => mr.Name == "__Internal"); - if (mod is null) { - mod = new ModuleReference ("__Internal"); - callingMethod.Module.ModuleReferences.Add (mod); - } - rv.PInvokeInfo = new PInvokeInfo (PInvokeAttributes.CharSetNotSpec | PInvokeAttributes.CallConvCdecl, nativeMethod, mod); - - dlfcn.Methods.Add (rv); - - AddField (callingMethod.Module.Assembly.Name.Name, symbolName); - + var methodName = $"{PInvokePrefix}{symbolName}{PInvokeSuffix}"; + var rv = abr.CreateInternalPInvoke (callingMethod.Module, callingMethod.DeclaringType.Namespace, "Dlfcn", methodName, out var created); + if (created) + AddField (callingMethod.Module.Assembly.Name.Name, symbolName); return rv; } diff --git a/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs b/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs index eda79a994f73..b74d18cddca7 100644 --- a/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs +++ b/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs @@ -18,7 +18,6 @@ namespace Xamarin.Linker.Steps { public class ListExportedSymbols : BaseStep { PInvokeWrapperGenerator? state; - bool is_product_assembly; PInvokeWrapperGenerator? State { get { @@ -80,8 +79,6 @@ protected override void ProcessAssembly (AssemblyDefinition assembly) if (!hasSymbols) return; - is_product_assembly = Configuration.Profile.IsProductAssembly (assembly); - var modified = false; foreach (var type in assembly.MainModule.Types) modified |= ProcessType (type); @@ -114,40 +111,41 @@ bool ProcessType (TypeDefinition type) void AddRequiredObjectiveCType (TypeDefinition type) { + if (TryGetRequiredObjectiveCType (DerivedLinkContext, type, out var exportedName)) + DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (exportedName).AddMember (type); + } + + // Returns true if the specified type represents an Objective-C class that should be referenced as a required symbol, so that the native linker doesn't link it away. + public static bool TryGetRequiredObjectiveCType (DerivedLinkContext derivedLinkContext, TypeDefinition type, [NotNullWhen (true)] out string? exportedName) + { + exportedName = null; + // The product assembly only has one type we may need to keep: XamarinSwiftFunctions - if (is_product_assembly) { + if (derivedLinkContext.LinkerConfiguration.Profile.IsProductAssembly (type.Module.Assembly)) { switch (type.Name) { case "XamarinSwiftFunctions": break; default: - return; + return false; } } - var staticRegistrar = DerivedLinkContext.StaticRegistrar; + var staticRegistrar = derivedLinkContext.StaticRegistrar; if (staticRegistrar is null) - return; + return false; - var registerAttribute = staticRegistrar.GetRegisterAttribute (type); - if (registerAttribute is null) - return; + if (!staticRegistrar.TryGetExportedTypeName (type, out exportedName)) + return false; - if (!registerAttribute.IsWrapper) - return; - - if (staticRegistrar.HasProtocolAttribute (type)) - return; - - if (DerivedLinkContext.App.RequireLinkWithAttributeForObjectiveCClassSearch) { + if (derivedLinkContext.App.RequireLinkWithAttributeForObjectiveCClassSearch) { var has_linkwith_attributes = false; - if (DerivedLinkContext.App.Assemblies.TryGetValue (type.Module.Assembly, out var asm)) + if (derivedLinkContext.App.Assemblies.TryGetValue (type.Module.Assembly, out var asm)) has_linkwith_attributes = asm.HasLinkWithAttributes; if (!has_linkwith_attributes) - return; + return false; } - var exportedName = staticRegistrar.GetExportedTypeName (type, registerAttribute); - DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (exportedName).AddMember (type); + return true; } bool ProcessMethod (MethodDefinition method) @@ -191,13 +189,18 @@ bool ProcessMethod (MethodDefinition method) switch (pinfo.Module.Name) { case "__Internal": - // For NativeAOT builds, don't add inlined dlfcn P/Invoke wrappers as - // required symbols: only the surviving ones will have native code generated, - // so force-referencing all of them causes linker errors for symbols that - // NativeAOT trimmed away. For non-NativeAOT builds, the wrappers are resolved - // via dlsym and need the -u flags to be exported from the binary. - if (Configuration.InlineDlfcnMethodsEnabled && Configuration.Application.XamarinRuntime == XamarinRuntime.NativeAOT && pinfo.EntryPoint.StartsWith ("xamarin_Dlfcn_", StringComparison.Ordinal)) - break; + if (Configuration.Application.XamarinRuntime == XamarinRuntime.NativeAOT) { + // For NativeAOT builds, don't add inlined dlfcn P/Invoke wrappers as + // required symbols: only the surviving ones will have native code generated, + // so force-referencing all of them causes linker errors for symbols that + // NativeAOT trimmed away. For non-NativeAOT builds, the wrappers are resolved + // via dlsym and need the -u flags to be exported from the binary. + if (Configuration.InlineDlfcnMethodsEnabled && pinfo.EntryPoint.StartsWith (InlineDlfcnMethodsStep.PInvokePrefix, StringComparison.Ordinal)) + break; + // Same goes for inlined Class.GetHandle calls. + if (Configuration.InlineClassGetHandle != InlineClassGetHandleMode.Disabled && pinfo.EntryPoint.StartsWith (InlineClassGetHandleStep.PInvokePrefix, StringComparison.Ordinal)) + break; + } Driver.Log (4, "Adding native reference to {0} in {1} because it's referenced by {2} in {3}.", pinfo.EntryPoint, pinfo.Module.Name, method.FullName, method.Module.Name); DerivedLinkContext.RequiredSymbols.AddFunction (pinfo.EntryPoint).AddMember (method); break; diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index ee92d9580d81..7c3f1861b4ab 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -3596,6 +3596,15 @@ public static string MX2261 { } } + /// + /// Looks up a localized string similar to The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar.. + /// + public static string MX2262 { + get { + return ResourceManager.GetString("MX2262", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not {0} the assembly '{1}'. /// diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index 987d7c75e479..cc89125d1ff1 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -1131,6 +1131,12 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + + + + From 9dc9c389760c3f001bbc086611b562593206a40d Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Wed, 27 May 2026 02:06:01 -0400 Subject: [PATCH 15/97] Make NSUrlSessionHandler handle redirects like the .NET handlers do (#25529) This change updates NSUrlSessionHandler redirect auth handling so it matches the .NET handlers more closely, this is just for .NET compatibility and for correctness; there's no security enforcement here. After an automatic redirect credentials are now resolved against the current redirect target Url instead of the orginal request Url. Also, non CredentialCache credentials are not reused across redirects by default, which is consistent with `SocketsHttpHandler` and `WinHttpHandler`. This avoids sending credentials that were scoped, or only meant for the original origin, to a redirected destination. CredentialCache entries still work when they are explicitly scoped for the redirect target. --- src/Foundation/NSUrlSessionHandler.cs | 98 +++++- .../System.Net.Http/MessageHandlers.cs | 284 ++++++++++++++++++ 2 files changed, 379 insertions(+), 3 deletions(-) diff --git a/src/Foundation/NSUrlSessionHandler.cs b/src/Foundation/NSUrlSessionHandler.cs index fc35793204e2..e34081682faa 100644 --- a/src/Foundation/NSUrlSessionHandler.cs +++ b/src/Foundation/NSUrlSessionHandler.cs @@ -134,6 +134,15 @@ static NSUrlSessionConfiguration CreateConfig () // Double.MaxValue does not work, so default to 24 hours config.TimeoutIntervalForRequest = 24 * 60 * 60; config.TimeoutIntervalForResource = 24 * 60 * 60; + + // Disable shared credential storage so credentials we pass with UseCredential in DidReceiveChallenge dont get saved in + // the SharedCredentialStorage so (native) NSUrlSession can't try to authenticate later requests by itself using old credentials + // incluiding redirects, and then our managed DidReceiveChallenge delegate may not get called at all. We already manage + // the credential flow in DidReceiveChallenge and the Credentials property. The switch is just a compat in case we + // someone needs to go back to the old behaviour. + var useSharedCredentialStorage = AppContext.TryGetSwitch ("Foundation.NSUrlSessionHandler.UseSharedCredentialStorage", out var useSharedStorage) && useSharedStorage; + if (!useSharedCredentialStorage) + config.URLCredentialStorage = null; return config; } @@ -1029,7 +1038,30 @@ void WillCacheResponseImpl (NSUrlSession session, NSUrlSessionDataTask dataTask, [Preserve (Conditional = true)] public override void WillPerformHttpRedirection (NSUrlSession session, NSUrlSessionTask task, NSHttpUrlResponse response, NSUrlRequest newRequest, Action completionHandler) { - completionHandler (sessionHandler.AllowAutoRedirect ? newRequest : null!); + if (!sessionHandler.AllowAutoRedirect) { + completionHandler (null!); + return; + } + + var inflight = GetInflightData (task); + + if (inflight is null) { + completionHandler (null!); + return; + } + + inflight.HasRedirected = true; + + if (newRequest.Url?.AbsoluteString is string redirectUrl) { + inflight.CurrentRequestUrl = redirectUrl; + + if (Uri.TryCreate (redirectUrl, UriKind.Absolute, out var redirectUri)) + inflight.HasCrossOriginRedirect |= IsCrossOriginRedirect (inflight.RequestUrl, redirectUri); + else + inflight.HasCrossOriginRedirect = true; + } + + completionHandler (newRequest); } [Preserve (Conditional = true)] @@ -1127,6 +1159,22 @@ void DidReceiveChallengeImpl (NSUrlSession session, NSUrlSessionTask task, NSUrl } } + // Detect redirect from the task as a fallback in case + // WillPerformHttpRedirection has not updated infligth state yet + if (!inflight.HasRedirected) { + var originalUrl = task.OriginalRequest?.Url?.AbsoluteString; + var currentUrl = task.CurrentRequest?.Url?.AbsoluteString; + if (originalUrl is not null && currentUrl is not null + && !string.Equals (originalUrl, currentUrl, StringComparison.Ordinal)) { + inflight.HasRedirected = true; + inflight.CurrentRequestUrl = currentUrl; + if (Uri.TryCreate (currentUrl, UriKind.Absolute, out var redirectUri)) + inflight.HasCrossOriginRedirect |= IsCrossOriginRedirect (inflight.RequestUrl, redirectUri); + else + inflight.HasCrossOriginRedirect = true; + } + } + if (sessionHandler.Credentials is not null && TryGetAuthenticationType (challenge.ProtectionSpace, out var authType)) { NetworkCredential? credentialsToUse = null; if (authType != RejectProtectionSpaceAuthType) { @@ -1147,8 +1195,9 @@ void DidReceiveChallengeImpl (NSUrlSession session, NSUrlSessionTask task, NSUrl var nsurlRespose = challenge.FailureResponse as NSHttpUrlResponse; var responseIsUnauthorized = (nsurlRespose is null) ? false : nsurlRespose.StatusCode == (int) HttpStatusCode.Unauthorized && challenge.PreviousFailureCount > 0; if (!responseIsUnauthorized) { - var uri = inflight.Request.RequestUri!; - credentialsToUse = sessionHandler.Credentials.GetCredential (uri, authType); + var uri = GetCredentialLookupUri (task, inflight); + if (ShouldLookupCredentials (sessionHandler.Credentials, inflight)) + credentialsToUse = sessionHandler.Credentials.GetCredential (uri, authType); } } @@ -1165,6 +1214,45 @@ void DidReceiveChallengeImpl (NSUrlSession session, NSUrlSessionTask task, NSUrl } } + static Uri GetCredentialLookupUri (NSUrlSessionTask task, InflightData inflight) + { + var currentRequestUrl = task.CurrentRequest?.Url?.AbsoluteString; + if (currentRequestUrl is not null && Uri.TryCreate (currentRequestUrl, UriKind.Absolute, out var currentRequestUri)) + return currentRequestUri; + + if (Uri.TryCreate (inflight.CurrentRequestUrl, UriKind.Absolute, out var inflightCurrentRequestUri)) + return inflightCurrentRequestUri; + + return inflight.Request.RequestUri!; + } + + static bool ShouldLookupCredentials (ICredentials credentials, InflightData inflight) + { + if (credentials is CredentialCache) + return true; + + if (!inflight.HasRedirected) + return true; + + // We are now matching .NET handlers (SocketsHttpHandler and WinHttpHandler) redirect behavior by dropping non CredentialCache credentials after a redirect + // Ref: + // https://github.com/dotnet/runtime/blob/eb5503a1f0dc40ee7b73eb79a039eb143ee25038/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs#L541-L547 + // https://github.com/dotnet/runtime/blob/eb5503a1f0dc40ee7b73eb79a039eb143ee25038/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs#L52-L87 + // Provide a way for customers to opt into the old behavior for same origin redirects. + var allowSameOriginRedirectCredentials = AppContext.TryGetSwitch ("Foundation.NSUrlSessionHandler.AllowSameOriginRedirectCredentials", out var allowRedirectCred) && allowRedirectCred; + return allowSameOriginRedirectCredentials && !inflight.HasCrossOriginRedirect; + } + + static bool IsCrossOriginRedirect (string originalRequestUrl, Uri currentRequestUri) + { + if (!Uri.TryCreate (originalRequestUrl, UriKind.Absolute, out var originalRequestUri)) + return true; + + return !string.Equals (originalRequestUri.Scheme, currentRequestUri.Scheme, StringComparison.OrdinalIgnoreCase) + || !string.Equals (originalRequestUri.IdnHost, currentRequestUri.IdnHost, StringComparison.OrdinalIgnoreCase) + || originalRequestUri.Port != currentRequestUri.Port; + } + static readonly string RejectProtectionSpaceAuthType = "reject"; static bool TryGetAuthenticationType (NSUrlProtectionSpace protectionSpace, [NotNullWhen (true)] out string? authenticationType) @@ -1196,6 +1284,9 @@ static bool TryGetAuthenticationType (NSUrlProtectionSpace protectionSpace, [Not class InflightData { public readonly object Lock = new object (); public string RequestUrl { get; set; } + public string CurrentRequestUrl { get; set; } + public bool HasRedirected { get; set; } + public bool HasCrossOriginRedirect { get; set; } public TaskCompletionSource CompletionSource { get; } = new TaskCompletionSource (TaskCreationOptions.RunContinuationsAsynchronously); public CancellationToken CancellationToken { get; set; } @@ -1214,6 +1305,7 @@ class InflightData { public InflightData (string requestUrl, CancellationToken cancellationToken, HttpRequestMessage request) { RequestUrl = requestUrl; + CurrentRequestUrl = requestUrl; CancellationToken = cancellationToken; Request = request; } diff --git a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs index 2accb1397400..b159fab8c8c5 100644 --- a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs +++ b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs @@ -3,6 +3,7 @@ // using System.Collections; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Net; @@ -25,6 +26,9 @@ namespace MonoTests.System.Net.Http { [TestFixture] [Preserve (AllMembers = true)] public class MessageHandlerTest { + const string AllowSameOriginRedirectCredentialsSwitch = "Foundation.NSUrlSessionHandler.AllowSameOriginRedirectCredentials"; + const string UseSharedCredentialStorageSwitch = "Foundation.NSUrlSessionHandler.UseSharedCredentialStorage"; + public MessageHandlerTest () { // Https seems broken on our macOS 10.9 bot, so skip this test. @@ -468,6 +472,138 @@ public void RedirectionWithAuthorizationHeaders (Type handlerType) } } + [TestCase (true, false, HttpStatusCode.Unauthorized, false, TestName = "NSUrlSessionHandlerOriginCredentialCacheNotSentToCrossOriginRedirectTarget")] + [TestCase (true, true, HttpStatusCode.OK, true, TestName = "NSUrlSessionHandlerTargetCredentialCacheSentToCrossOriginRedirectTarget")] + [TestCase (false, false, HttpStatusCode.Unauthorized, false, TestName = "NSUrlSessionHandlerNetworkCredentialNotSentToCrossOriginRedirectTarget")] + public void NSUrlSessionHandlerCredentialsCrossOriginRedirectTarget (bool useCredentialCache, bool cacheRedirectTarget, HttpStatusCode expectedStatusCode, bool expectAuthorizationHeader) + { + if (!HttpListener.IsSupported) { + Assert.Inconclusive ("HttpListener is not supported"); + } + + using var server = new RedirectBasicAuthServer (crossOrigin: true); + using var handler = new NSUrlSessionHandler (); + var username = "origin-user"; + var password = "origin-password"; + + if (useCredentialCache) { + var cache = new CredentialCache (); + var credentialUri = cacheRedirectTarget ? new Uri (server.TargetUri, "protected") : server.OriginUri; + cache.Add (credentialUri, "basic", new NetworkCredential (username, password)); + handler.Credentials = cache; + } else { + handler.Credentials = new NetworkCredential (username, password); + } + + HttpStatusCode statusCode = HttpStatusCode.NotFound; + var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { + using var client = new HttpClient (handler); + using var response = await client.GetAsync (new Uri (server.OriginUri, "start")); + statusCode = response.StatusCode; + }, out var ex); + + Assert.That (done, Is.True, "Request timed out."); + Assert.That (ex, Is.Null, "Exception"); + Assert.That (statusCode, Is.EqualTo (expectedStatusCode), "StatusCode"); + Assert.That (server.TargetRequestCount, Is.GreaterThanOrEqualTo (1), "Target request count"); + Assert.That (server.TargetAuthorizationHeaders.Length > 0, Is.EqualTo (expectAuthorizationHeader), "Authorization header presence."); + } + + [TestCase (false, HttpStatusCode.Unauthorized, TestName = "NSUrlSessionHandlerNetworkCredentialNotSentAfterSameOriginRedirectByDefault")] + [TestCase (true, HttpStatusCode.OK, TestName = "NSUrlSessionHandlerNetworkCredentialSentAfterSameOriginRedirectWithAppContextSwitch")] + public void NSUrlSessionHandlerNetworkCredentialSameOriginRedirectCredentials (bool allowSameOriginRedirectCredentials, HttpStatusCode expectedStatusCode) + { + if (!HttpListener.IsSupported) { + Assert.Inconclusive ("HttpListener is not supported"); + } + + AppContext.TryGetSwitch (AllowSameOriginRedirectCredentialsSwitch, out var originalValue); + try { + AppContext.SetSwitch (AllowSameOriginRedirectCredentialsSwitch, allowSameOriginRedirectCredentials); + + using var server = new RedirectBasicAuthServer (crossOrigin: false); + using var handler = new NSUrlSessionHandler { + Credentials = new NetworkCredential ("origin-user", "origin-password"), + }; + + HttpStatusCode statusCode = HttpStatusCode.NotFound; + var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { + using var client = new HttpClient (handler); + using var response = await client.GetAsync (new Uri (server.OriginUri, "start")); + statusCode = response.StatusCode; + }, out var ex); + + Assert.That (done, Is.True, "Request timed out."); + Assert.That (ex, Is.Null, "Exception"); + Assert.That (statusCode, Is.EqualTo (expectedStatusCode), "StatusCode"); + Assert.That (server.TargetRequestCount, Is.GreaterThanOrEqualTo (1), "Target request count"); + Assert.That (server.TargetAuthorizationHeaders.Length > 0, Is.EqualTo (allowSameOriginRedirectCredentials), "Authorization header presence."); + } finally { + AppContext.SetSwitch (AllowSameOriginRedirectCredentialsSwitch, originalValue); + } + } + + [TestCase (false, HttpStatusCode.Unauthorized, false, TestName = "NSUrlSessionHandlerNetworkCredentialNotSentToCrossOriginRedirectWithDefaultCredentialStorage")] + [TestCase (true, HttpStatusCode.OK, true, TestName = "NSUrlSessionHandlerNetworkCredentialSentToCrossOriginRedirectWithSharedCredentialStorage")] + public void NSUrlSessionHandlerUseSharedCredentialStorage (bool useSharedCredentialStorage, HttpStatusCode expectedStatusCode, bool expectSecondRequestAuthorizationHeader) + { + if (!HttpListener.IsSupported) { + Assert.Inconclusive ("HttpListener is not supported"); + } + + AppContext.TryGetSwitch (UseSharedCredentialStorageSwitch, out var originalValue); + try { + AppContext.SetSwitch (UseSharedCredentialStorageSwitch, useSharedCredentialStorage); + + using var server = new RedirectBasicAuthServer (crossOrigin: true); + + // First, prime the shared credential storage by making a successful auth request + // using a CredentialCache with the target URI. When shared storage is enabled, + // NSUrlSession stores the credential with ForSession persistence in SharedCredentialStorage, + // making it available to subsequent handlers targeting the same host:port. + var cache = new CredentialCache (); + cache.Add (new Uri (server.TargetUri, "protected"), "basic", new NetworkCredential ("origin-user", "origin-password")); + + using (var primingHandler = new NSUrlSessionHandler { Credentials = cache }) { + var primingDone = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { + using var client = new HttpClient (primingHandler); + using var response = await client.GetAsync (new Uri (server.OriginUri, "start")); + }, out var primingEx); + + Assert.That (primingDone, Is.True, "Priming request timed out."); + Assert.That (primingEx, Is.Null, "Priming exception"); + } + + // Record how many auth headers the server received from the priming request + var authHeadersAfterPriming = server.TargetAuthorizationHeaders.Length; + + // Now make a second request with a NetworkCredential (not CredentialCache) to the same server. + // With shared storage enabled, NSUrlSession finds cached credentials in SharedCredentialStorage + // and pre-emptively authenticates the redirect target without calling DidReceiveChallenge. + // With shared storage disabled (default), no stored credentials exist, and the + // DidReceiveChallenge delegate blocks the NetworkCredential after the cross-origin redirect. + using var handler = new NSUrlSessionHandler { + Credentials = new NetworkCredential ("origin-user", "origin-password"), + }; + + HttpStatusCode statusCode = HttpStatusCode.NotFound; + var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { + using var client = new HttpClient (handler); + using var response = await client.GetAsync (new Uri (server.OriginUri, "start")); + statusCode = response.StatusCode; + }, out var ex); + + Assert.That (done, Is.True, "Request timed out."); + Assert.That (ex, Is.Null, "Exception"); + Assert.That (statusCode, Is.EqualTo (expectedStatusCode), "StatusCode"); + + var authHeadersFromSecondRequest = server.TargetAuthorizationHeaders.Length - authHeadersAfterPriming; + Assert.That (authHeadersFromSecondRequest > 0, Is.EqualTo (expectSecondRequestAuthorizationHeader), "Authorization header on second request."); + } finally { + AppContext.SetSwitch (UseSharedCredentialStorageSwitch, originalValue); + } + } + [TestCase (typeof (SocketsHttpHandler))] [TestCase (typeof (NSUrlSessionHandler))] public void RejectSslCertificatesServicePointManager (Type handlerType) @@ -867,6 +1003,154 @@ static NWListener CreateNWTlsListener (bool requireClientCert) return (cert.Export (X509ContentType.Pfx, password), password); } + sealed class RedirectBasicAuthServer : IDisposable { + readonly bool crossOrigin; + readonly HttpListener originListener; + readonly HttpListener? targetListener; + readonly Task originTask; + readonly Task? targetTask; + readonly object targetAuthorizationHeadersLock = new object (); + readonly List targetAuthorizationHeaders = new List (); + readonly string expectedBasicAuth; + int targetRequestCount; + + public Uri OriginUri { get; } + public Uri TargetUri { get; } + public int TargetRequestCount => Volatile.Read (ref targetRequestCount); + + public string [] TargetAuthorizationHeaders { + get { + lock (targetAuthorizationHeadersLock) + return targetAuthorizationHeaders.ToArray (); + } + } + + public RedirectBasicAuthServer (bool crossOrigin, string username = "origin-user", string password = "origin-password") + { + this.crossOrigin = crossOrigin; + expectedBasicAuth = "Basic " + Convert.ToBase64String (global::System.Text.Encoding.UTF8.GetBytes ($"{username}:{password}")); + originListener = CreateStartedHttpListener (out var originUri); + OriginUri = originUri; + + if (crossOrigin) { + targetListener = CreateStartedHttpListener (out var targetUri); + TargetUri = targetUri; + } else { + TargetUri = OriginUri; + } + + originTask = Task.Run (RunOrigin); + if (targetListener is not null) + targetTask = Task.Run (RunTarget); + } + + async Task RunOrigin () + { + while (true) { + var context = await GetContextAsync (originListener); + if (context is null) + return; + + if (!crossOrigin && string.Equals (context.Request.Url?.AbsolutePath, "/protected", StringComparison.Ordinal)) { + RespondToProtectedResource (context); + } else { + RespondWithRedirect (context); + } + } + } + + async Task RunTarget () + { + if (targetListener is null) + return; + + while (true) { + var context = await GetContextAsync (targetListener); + if (context is null) + return; + + RespondToProtectedResource (context); + } + } + + void RespondWithRedirect (HttpListenerContext context) + { + var response = context.Response; + response.StatusCode = (int) HttpStatusCode.Redirect; + response.RedirectLocation = new Uri (TargetUri, "protected").AbsoluteUri; + response.Close (); + } + + void RespondToProtectedResource (HttpListenerContext context) + { + Interlocked.Increment (ref targetRequestCount); + + var authorization = context.Request.Headers ["Authorization"]; + if (!string.IsNullOrEmpty (authorization)) { + lock (targetAuthorizationHeadersLock) + targetAuthorizationHeaders.Add (authorization); + } + + var response = context.Response; + if (string.Equals (authorization, expectedBasicAuth, StringComparison.Ordinal)) { + response.StatusCode = (int) HttpStatusCode.OK; + } else { + response.StatusCode = (int) HttpStatusCode.Unauthorized; + response.AddHeader ("WWW-Authenticate", "Basic realm=\"redirect-target\""); + } + response.Close (); + } + + static async Task GetContextAsync (HttpListener listener) + { + try { + return await listener.GetContextAsync (); + } catch (HttpListenerException) { + return null; + } catch (ObjectDisposedException) { + return null; + } catch (InvalidOperationException) { + return null; + } + } + + static HttpListener CreateStartedHttpListener (out Uri uri) + { + const int MinPort = 49215; + const int MaxPort = 65535; + + for (var port = MinPort; port < MaxPort; port++) { + var listener = new HttpListener (); + var url = $"http://127.0.0.1:{port}/"; + listener.Prefixes.Add (url); + try { + listener.Start (); + uri = new Uri (url); + return listener; + } catch { + listener.Close (); + } + } + + throw new InvalidOperationException ("Could not start a local HTTP listener."); + } + + public void Dispose () + { + originListener.Close (); + targetListener?.Close (); + + try { + if (targetTask is null) + Task.WaitAll (new [] { originTask }, TimeSpan.FromSeconds (1)); + else + Task.WaitAll (new [] { originTask, targetTask }, TimeSpan.FromSeconds (1)); + } catch { + // Listener disposal wakes the request loops. + } + } + } + [Test] public void AssertDefaultValuesNSUrlSessionHandler () { From 7a656b332e82e218fe8e8c1c0a327d6250024d22 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 08:33:03 +0200 Subject: [PATCH 16/97] [bgen] Remove support for ZeroCopyStrings (#25515) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the dead zero-copy string marshaling code from the binding generator. This feature was never fully working, because it never really worked well (it ran into a number of bugs in other places, causing crashes because APIs would retain or copy NSStrings when they shouldn't and this optimization would run head-first into those). Note that this option never did anything in .NET, it was always forcefully disabled if someone tried to enable it. ## Changes - Remove `ZeroCopyStrings` field, `type_wants_zero_copy`, `ZeroCopyStringMarshal`, `CollectFastStringMarshalParameters`, and all zero-copy code paths from the generator - Simplify `GenerateMarshalString`/`GenerateDisposeString` (always use the copy path) - Keep `ZeroCopyStringsAttribute` and `DisableZeroCopyAttribute` as no-op stubs wrapped in `#if !XAMCORE_5_0` with `[Obsolete]` for source compatibility - Guard `--use-zero-copy` CLI option with `#if !XAMCORE_5_0` (emits warning BI1027) - Mark `ObjCBindings.Property.DisableZeroCopy` enum value as `[Obsolete]` - Remove `[DisableZeroCopy]` usages from binding sources (glkit, corebluetooth) - Update documentation to mark both attributes as obsolete 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/website/binding_types_reference_guide.md | 70 ++------------ docs/website/generator-errors.md | 2 +- src/ObjCBindings/ExportTag.cs | 15 +-- src/Resources.Designer.cs | 2 +- src/Resources.resx | 2 +- src/bgen/Attributes.cs | 40 ++------ src/bgen/BindingTouch.cs | 5 +- src/bgen/Generator.cs | 91 ++----------------- src/bgen/Models/BindingTouchConfig.cs | 1 - src/bgen/Models/MarshalInfo.cs | 7 -- src/corebluetooth.cs | 1 - src/glkit.cs | 2 - .../Validators/FieldValidator.cs | 2 + .../DataModel/Property.Generator.cs | 4 +- .../AttributesNames.cs | 3 +- .../Validators/ArrayValidatorTests.cs | 1 + .../Validators/FieldValidatorTests.cs | 1 + .../PropertyOrFieldValidatorTests.cs | 1 + 18 files changed, 44 insertions(+), 206 deletions(-) diff --git a/docs/website/binding_types_reference_guide.md b/docs/website/binding_types_reference_guide.md index 78b90f26cbca..ace56aab5bc3 100644 --- a/docs/website/binding_types_reference_guide.md +++ b/docs/website/binding_types_reference_guide.md @@ -1306,28 +1306,9 @@ This should map to Objective-C/clang use of `__attribute__((objc_designated_init ### DisableZeroCopyAttribute -This attribute is applied to string parameters or string properties and -instructs the code generator to not use the zero-copy string marshaling for -this parameter, and instead create a new NSString instance from the C# string. -This attribute is only required on strings if you instruct the generator to use -zero-copy string marshaling using either the `--zero-copy` command -line option or setting the assembly-level attribute `ZeroCopyStringsAttribute`. - -This is necessary in cases where the property is declared in Objective-C to -be a `retain` or `assign` property instead of a `copy` property. These typically -happen in third-party libraries that have been wrongly "optimized" by -developers. In general, `retain` or `assign` `NSString` properties are incorrect -since `NSMutableString` or user-derived classes of `NSString` might alter the -contents of the strings without the knowledge of the library code, subtly -breaking the application. Typically this happens due to premature -optimization. - -The following shows two such properties in Objective-C: - -```csharp -@property(nonatomic,retain) NSString *name; -@property(nonatomic,assign) NSString *name2; -``` +> **Note:** This attribute is obsolete and has no effect. Zero-copy string +> marshaling is no longer supported. The attribute is preserved for source +> compatibility but can be safely removed from binding definitions. @@ -2435,47 +2416,10 @@ This corresponds to `clang` [`__attribute__((objc_requires_super))`](https://cla ### ZeroCopyStringsAttribute -Only available in Xamarin.iOS 5.4 and newer. - -This attribute instructs the generator that the binding for this specific -library (if applied with `[assembly:]`) or type should use the fast -zero-copy string marshaling. This attribute is equivalent to passing the -command line option `--zero-copy` to the generator. - -When using zero-copy for strings, the generator effectively uses the same C# -string as the string that Objective-C consumes without incurring the creation of -a new `NSString` object and avoiding copying the data from the C# strings to the -Objective-C string. The only drawback of using Zero Copy strings is that you -must ensure that any string property that you wrap that happens to be flagged as -`retain` or `copy` has the `[DisableZeroCopy]` attribute set. This is -require because the handle for zero-copy strings is allocated on the stack and -is invalid upon the function return. - -Example: - -```csharp -[ZeroCopyStrings] -[BaseType (typeof (NSObject))] -interface MyBinding { - [Export ("name")] - string Name { get; set; } - - [Export ("domain"), NullAllowed] - string Domain { get; set; } - - [DisablZeroCopy] - [Export ("someRetainedNSString")] - string RetainedProperty { get; set; } -} - -``` - -You can also apply the attribute at the assembly level, and it will apply to -all the types of the assembly: - -```csharp -[assembly:ZeroCopyStrings] -``` +> **Note:** This attribute is obsolete and has no effect. Zero-copy string +> marshaling is no longer supported. The `--use-zero-copy` command line option +> is also no longer supported. The attribute is preserved for source +> compatibility but can be safely removed from binding definitions. ## Strongly-typed dictionaries diff --git a/docs/website/generator-errors.md b/docs/website/generator-errors.md index 1d135ecb476f..365a8964309d 100644 --- a/docs/website/generator-errors.md +++ b/docs/website/generator-errors.md @@ -128,7 +128,7 @@ Please go to [[FieldAttribute]](https://developer.xamarin.com/guides/cross-platf ### BI1026: `*`: Enums attributed with [\*] must have an underlying type of `long` or `ulong` -### BI1027: Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. +### BI1027: Support for ZeroCopy strings is not implemented. The --use-zero-copy option is not supported and will be ignored. ### BI1028: Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/src/ObjCBindings/ExportTag.cs b/src/ObjCBindings/ExportTag.cs index 7172cc573a55..ca3d9b7d0b13 100644 --- a/src/ObjCBindings/ExportTag.cs +++ b/src/ObjCBindings/ExportTag.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; #nullable enable @@ -169,18 +170,10 @@ public enum Property : Int64 { CustomMarshalDirective = 1 << 5, /// - /// Apply to strings parameters that are merely retained or assigned, - /// not copied this is an exception as it is advised in the coding - /// standard for Objective-C to avoid this, but a few properties do use - /// this. Use this falg for properties flagged with `retain' or - /// `assign', which look like this: - /// - /// @property (retain) NSString foo; - /// @property (assign) NSString assigned; - /// - /// This forced the generator to create an NSString before calling the - /// API instead of using the fast string marshalling code. + /// This flag is obsolete and has no effect. Zero-copy string marshaling is no longer supported. /// + [EditorBrowsable (EditorBrowsableState.Never)] + [Obsolete ("Zero-copy string marshaling is no longer supported. This flag has no effect.")] DisableZeroCopy = 1 << 6, /// diff --git a/src/Resources.Designer.cs b/src/Resources.Designer.cs index 5cd19312d3ee..db627089d6d1 100644 --- a/src/Resources.Designer.cs +++ b/src/Resources.Designer.cs @@ -440,7 +440,7 @@ internal static string BI1026 { } /// - /// Looks up a localized string similar to Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings.. + /// Looks up a localized string similar to The --use-zero-copy option is not supported and will be ignored.. /// internal static string BI1027 { get { diff --git a/src/Resources.resx b/src/Resources.resx index acd9d535c624..3e6aace23529 100644 --- a/src/Resources.resx +++ b/src/Resources.resx @@ -294,7 +294,7 @@ - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. diff --git a/src/bgen/Attributes.cs b/src/bgen/Attributes.cs index a55108690d3c..8e47763419eb 100644 --- a/src/bgen/Attributes.cs +++ b/src/bgen/Attributes.cs @@ -517,20 +517,14 @@ public class NoDefaultValueAttribute : Attribute { public class IgnoredInDelegateAttribute : Attribute { } -// Apply to strings parameters that are merely retained or assigned, -// not copied this is an exception as it is advised in the coding -// standard for Objective-C to avoid this, but a few properties do use -// this. Use this attribtue for properties flagged with `retain' or -// `assign', which look like this: -// -// @property (retain) NSString foo; -// @property (assign) NSString assigned; -// -// This forced the generator to create an NSString before calling the -// API instead of using the fast string marshalling code. +#if !XAMCORE_5_0 +// This attribute is obsolete and has no effect. Zero-copy string marshaling is no longer supported. +[Obsolete ("Zero-copy string marshaling is no longer supported. This attribute has no effect.")] +[AttributeUsage (AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)] public class DisableZeroCopyAttribute : Attribute { public DisableZeroCopyAttribute () { } } +#endif // Apply this attribute to methods that need a custom binding method. // @@ -561,29 +555,13 @@ public class MarshalDirectiveAttribute : Attribute { public string? Library { get; set; } } -// -// By default, the generator will not do Zero Copying of strings, as most -// third party libraries do not follow Apple's design guidelines of making -// string properties and parameters copy parameters, instead many libraries -// "retain" as a broken optimization [1]. -// -// The consumer of the generator can force this by passing -// --use-zero-copy or setting the [assembly:ZeroCopyStrings] attribute. -// When these are set, the generator assumes the library perform -// copies over any NSStrings it keeps instead of retains/assigns and -// that any property that happens to be a retain/assign has the -// [DisableZeroCopyAttribute] attribute applied. -// -// [1] It is broken because consumer code can pass an NSMutableString, the -// library retains the value, but does not have a way of noticing changes -// that might happen to the mutable string behind its back. -// -// In the ZeroCopy case it is a problem because we pass handles to stack-allocated -// strings that stop existing after the invocation is over. -// +#if !XAMCORE_5_0 +// This attribute is obsolete and has no effect. Zero-copy string marshaling is no longer supported. +[Obsolete ("Zero-copy string marshaling is no longer supported. This attribute has no effect.")] [AttributeUsage (AttributeTargets.Assembly | AttributeTargets.Method | AttributeTargets.Interface, AllowMultiple = true)] public class ZeroCopyStringsAttribute : Attribute { } +#endif [AttributeUsage (AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class SnippetAttribute : Attribute { diff --git a/src/bgen/BindingTouch.cs b/src/bgen/BindingTouch.cs index 2766217756fa..5afd87e41b01 100644 --- a/src/bgen/BindingTouch.cs +++ b/src/bgen/BindingTouch.cs @@ -163,7 +163,9 @@ public bool TryCreateOptionSet (BindingTouchConfig config, string [] args) { "p", "Sets private mode", v => config.IsPublicMode = false }, { "baselib=", "Sets the base library", v => config.Baselibdll = v }, { "attributelib=", "Sets the attribute library", v => config.Attributedll = v }, - { "use-zero-copy", v=> config.UseZeroCopy = true }, +#if !XAMCORE_5_0 + { "use-zero-copy", v=> ErrorHelper.Warning (1027) }, +#endif { "nostdlib", "Does not reference mscorlib.dll library", l => config.OmitStandardLibrary = true }, #if !XAMCORE_5_0 { "no-mono-path", "Launches compiler with empty MONO_PATH", l => { }, true }, @@ -364,7 +366,6 @@ bool TryGenerate (BindingTouchConfig config, Api api) try { var g = new Generator (this, api, config.IsPublicMode, config.IsExternal, config.IsDebug) { BaseDir = config.BindingFilesOutputDirectory ?? config.TemporaryFileDirectory!, - ZeroCopyStrings = config.UseZeroCopy, InlineSelectors = config.InlineSelectors ?? (CurrentPlatform != PlatformName.MacOSX), }; diff --git a/src/bgen/Generator.cs b/src/bgen/Generator.cs index cf581d75be9b..f5559c942d26 100644 --- a/src/bgen/Generator.cs +++ b/src/bgen/Generator.cs @@ -109,9 +109,6 @@ Nomenclator Nomenclator { string? is_direct_binding_value; // An expression that calculates the IsDirectBinding value. Might not be a constant expression. This will be added to every constructor for a type. bool? is_direct_binding; // If a constant value for IsDirectBinding is known, it's stored here. Will be null if no constant value is known. - // Whether to use ZeroCopy for strings, defaults to false - public bool ZeroCopyStrings; - public bool BindThirdPartyLibrary { get { return BindingTouch.BindThirdPartyLibrary; } } public bool InlineSelectors; public string BaseDir { get { return basedir; } set { basedir = value; } } @@ -124,11 +121,6 @@ Nomenclator Nomenclator { // bool type_needs_thread_checks; - // - // If set, the members of this type will get zero copy - // - internal bool type_wants_zero_copy; - // // Used by the public binding generator to populate the // class with types that do not exist @@ -843,16 +835,7 @@ public TrampolineInfo MakeTrampoline (Type t) if (mai.PlainString) return safe_name; else { - bool allow_null = null_allowed_override || AttributeManager.IsNullable (pi); - - if (mai.ZeroCopyStringMarshal) { - if (allow_null) - return String.Format ("{0} is null ? IntPtr.Zero : (IntPtr)(&_s{0})", pi.Name); - else - return String.Format ("(IntPtr)(&_s{0})", pi.Name); - } else { - return "ns" + pi.Name; - } + return "ns" + pi.Name; } } @@ -3097,13 +3080,8 @@ void GenerateInvoke (bool stret, bool supercall, MethodInfo mi, MemberInformatio if (mai.PlainString) ErrorHelper.Warning (1101); - if (mai.ZeroCopyStringMarshal) { - target_name = "(IntPtr)(&_s" + pi.Name + ")"; - handle = ""; - } else { - target_name = "ns" + pi.Name; - handle = ""; - } + target_name = "ns" + pi.Name; + handle = ""; } else target_name = pi.Name.GetSafeParamName (); break; @@ -3248,48 +3226,16 @@ bool IsOptimizable (MemberInfo method) // @probe_null: determines whether null is allowed, and // whether we need to generate code to handle this // - // @must_copy: determines whether to create a new NSString, necessary - // for NSString properties that are flagged with "retain" instead of "copy" - // - // @prefix: prefix to prepend on each line - // // @property: the name of the property // - public string GenerateMarshalString (bool probe_null, bool must_copy) - { - if (must_copy) { - return "var ns{0} = CFString.CreateNative ({1});\n"; - } - return - "ObjCRuntime.NSStringStruct _s{0}; Console.WriteLine (\"" + CurrentMethod + ": Marshalling: {{1}}\", {1}); \n" + - "_s{0}.ClassPtr = ObjCRuntime.NSStringStruct.ReferencePtr;\n" + - "_s{0}.Flags = 0x010007d1; // RefCount=1, Unicode, InlineContents = 0, DontFreeContents\n" + - "_s{0}.UnicodePtr = _p{0};\n" + - "_s{0}.Length = " + (probe_null ? "{1} is null ? 0 : {1}.Length;" : "{1}.Length;\n"); - } - - public string GenerateDisposeString (bool probe_null, bool must_copy) + public string GenerateMarshalString (bool probe_null) { - if (must_copy) { - return "CFString.ReleaseNative (ns{0});\n"; - } else - return "if (_s{0}.Flags != 0x010007d1) throw new Exception (\"String was retained, not copied\");"; + return "var ns{0} = CFString.CreateNative ({1});\n"; } - List? CollectFastStringMarshalParameters (MethodInfo mi) + public string GenerateDisposeString (bool probe_null) { - List? stringParameters = null; - - foreach (var pi in mi.GetParameters ()) { - var mai = new MarshalInfo (this, mi, pi); - - if (mai.ZeroCopyStringMarshal) { - if (stringParameters is null) - stringParameters = new List (); - stringParameters.Add (pi.Name.GetSafeParamName () ?? ""); - } - } - return stringParameters; + return "CFString.ReleaseNative (ns{0});\n"; } AvailabilityBaseAttribute? GetIntroduced (Type? type, string methodName) @@ -3371,8 +3317,8 @@ void GenerateTypeLowering (MethodInfo mi, bool null_allowed_override, out String if (mai.Type == TypeCache.System_String && !mai.PlainString) { bool probe_null = null_allowed_override || AttributeManager.IsNullable (pi); - convs.AppendFormat (GenerateMarshalString (probe_null, !mai.ZeroCopyStringMarshal), pi.Name, pi.Name.GetSafeParamName ()); - disposes.AppendFormat (GenerateDisposeString (probe_null, !mai.ZeroCopyStringMarshal), pi.Name); + convs.AppendFormat (GenerateMarshalString (probe_null), pi.Name, pi.Name.GetSafeParamName ()); + disposes.AppendFormat (GenerateDisposeString (probe_null), pi.Name); } else if (mai.Type.TryIsArray (out var etype)) { if (HasBindAsAttribute (pi)) { convs.AppendFormat ("using var nsb_{0} = {1}\n", pi.Name, GetToBindAsWrapper (mi, null, pi)); @@ -3615,9 +3561,6 @@ public void GenerateMethodBody (MemberInformation minfo, MethodInfo mi, string? GenerateArgumentChecks (mi, false, propInfo, out bool needsGCKeepAlives); - // Collect all strings that can be fast-marshalled - var stringParameters = CollectFastStringMarshalParameters (mi); - GenerateTypeLowering (mi, null_allowed_override, out var args, out var convs, out var disposes, out var by_ref_processing, out var by_ref_init, out var post_return, propInfo); if (minfo.is_protocol_member && minfo.is_static) { @@ -3629,12 +3572,6 @@ public void GenerateMethodBody (MemberInformation minfo, MethodInfo mi, string? if (by_ref_init.Length > 0) print (by_ref_init.ToString ()); - if (stringParameters is not null) { - print ("fixed (char * {0}){{", - stringParameters.Select (name => "_p" + name + " = " + name).Aggregate ((first, second) => first + ", " + second)); - indent++; - } - if (propInfo is not null && IsSetter (mi) && HasBindAsAttribute (propInfo)) { convs.AppendFormat ("using var nsb_{0} = {1}\n", propInfo.Name, GetToBindAsWrapper (mi, minfo, null)); } @@ -3834,10 +3771,6 @@ public void GenerateMethodBody (MemberInformation minfo, MethodInfo mi, string? WriteMarkDirtyIfDerived (sw, mi.DeclaringType!); if (post_return?.Length > 0) print (post_return.ToString ()); - if (stringParameters is not null) { - indent--; - print ("}"); - } indent--; } @@ -5769,12 +5702,6 @@ public void Generate (Type type) if (is_rgen_type) return; - if (ZeroCopyStrings) { - ErrorHelper.Warning (1027); - ZeroCopyStrings = false; - } - - type_wants_zero_copy = AttributeManager.HasAttribute (type) || ZeroCopyStrings; var tsa = AttributeManager.GetCustomAttribute (type); // if we're inside a special namespace then default is non-thread safe, otherwise default is thread safe if (NamespaceCache.UINamespaces.Contains (type.Namespace!)) { diff --git a/src/bgen/Models/BindingTouchConfig.cs b/src/bgen/Models/BindingTouchConfig.cs index 0aa0d50b614f..4cad9cbbba2f 100644 --- a/src/bgen/Models/BindingTouchConfig.cs +++ b/src/bgen/Models/BindingTouchConfig.cs @@ -5,7 +5,6 @@ public class BindingTouchConfig { public bool ShowHelp = false; - public bool UseZeroCopy = false; public string? BindingFilesOutputDirectory = null; public string? TemporaryFileDirectory = null; public string? HelperClassNamespace = null; diff --git a/src/bgen/Models/MarshalInfo.cs b/src/bgen/Models/MarshalInfo.cs index 27c4a52c6fea..091b1dc32ffd 100644 --- a/src/bgen/Models/MarshalInfo.cs +++ b/src/bgen/Models/MarshalInfo.cs @@ -13,10 +13,6 @@ public class MarshalInfo { public Type Type { get; } public bool IsOut { get; } - // This is set on a string parameter if the argument parameters are set to - // Copy. This means that we can do fast string passing. - public bool ZeroCopyStringMarshal { get; set; } - public bool IsAligned; // Used for parameters @@ -25,9 +21,6 @@ public MarshalInfo (Generator generator, MethodInfo mi, ParameterInfo pi) this.Generator = generator; PlainString = Generator.AttributeManager.HasAttribute (pi); Type = pi.ParameterType; - ZeroCopyStringMarshal = (Type == Generator.TypeCache.System_String) && PlainString == false && !Generator.AttributeManager.HasAttribute (pi) && generator.type_wants_zero_copy; - if (ZeroCopyStringMarshal && Generator.AttributeManager.HasAttribute (mi)) - ZeroCopyStringMarshal = false; IsOut = pi.IsOut; } diff --git a/src/corebluetooth.cs b/src/corebluetooth.cs index b5d8f22930bb..3b1fc5133c5c 100644 --- a/src/corebluetooth.cs +++ b/src/corebluetooth.cs @@ -791,7 +791,6 @@ interface CBPeripheral : NSCopying { /// To be added. /// To be added. [Export ("name", ArgumentSemantic.Retain)] - [DisableZeroCopy] [NullAllowed] string Name { get; } diff --git a/src/glkit.cs b/src/glkit.cs index 9d70fa9313a6..609394d61ab9 100644 --- a/src/glkit.cs +++ b/src/glkit.cs @@ -171,7 +171,6 @@ interface GLKBaseEffect : GLKNamedEffect { /// /// To be added. [Export ("label", ArgumentSemantic.Copy)] - [DisableZeroCopy] [NullAllowed] // default is null on iOS 5.1.1 string Label { get; set; } @@ -583,7 +582,6 @@ interface GLKSkyboxEffect : GLKNamedEffect { /// To be added. [NullAllowed] // by default this property is null [Export ("label", ArgumentSemantic.Copy)] - [DisableZeroCopy] string Label { get; set; } /// To be added. diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/FieldValidator.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/FieldValidator.cs index 6bc47067a431..3bffa00f9f39 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/FieldValidator.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/FieldValidator.cs @@ -76,6 +76,7 @@ internal static bool FlagsAreValid (Property property, RootContext context, // there are a number of flags that have no effect in a field property, we are not raise a warning for each // of them since they are ignored by rgen. The user that has warnings as errors will have to deal with them. +#pragma warning disable CS0618 // DisableZeroCopy is obsolete var ignoredFlags = new [] { PropertyFlag.IsThreadStatic, PropertyFlag.MarshalNativeExceptions, @@ -93,6 +94,7 @@ internal static bool FlagsAreValid (Property property, RootContext context, PropertyFlag.Optional, PropertyFlag.CreateEvents, }; +#pragma warning restore CS0618 var builder = ImmutableArray.CreateBuilder (); foreach (var flag in ignoredFlags) { diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs index 5c1dc20fae17..623bca12c849 100644 --- a/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs +++ b/src/rgen/Microsoft.Macios.Generator/DataModel/Property.Generator.cs @@ -99,10 +99,12 @@ public bool MarshalNativeExceptions public bool IsTransient => IsProperty && ExportPropertyData.Flags.HasFlag (ObjCBindings.Property.Transient); /// - /// True if the property was marked to DisableZeroCopy. + /// True if the property was marked to DisableZeroCopy. This flag is obsolete and has no effect. /// +#pragma warning disable CS0618 // Type or member is obsolete public bool DisableZeroCopy => IsProperty && ExportPropertyData.Flags.HasFlag (ObjCBindings.Property.DisableZeroCopy); +#pragma warning restore CS0618 /// /// True if the generator should not use a NSString for marshalling. diff --git a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs index e09a98b9115f..5d35b03547f6 100644 --- a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs +++ b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs @@ -90,8 +90,7 @@ static class AttributesNames { public const string DisableDefaultCtorAttribute = "DisableDefaultCtorAttribute"; /// - /// This attribute is applied to string parameters or string properties and instructs the code generator to not - /// use the zero-copy string marshaling for this parameter, and instead create a new NSString instance from the C# string + /// This attribute is obsolete and has no effect. Zero-copy string marshaling is no longer supported. /// [BindingFlag (AttributeTargets.Parameter | AttributeTargets.Property)] public const string DisableZeroCopyAttribute = "DisableZeroCopyAttribute"; diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/ArrayValidatorTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/ArrayValidatorTests.cs index 5d265504094a..74b3a943027b 100644 --- a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/ArrayValidatorTests.cs +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/ArrayValidatorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma warning disable APL0003 +#pragma warning disable CS0618 // DisableZeroCopy is obsolete using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/FieldValidatorTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/FieldValidatorTests.cs index 7d9c84e75903..9571b1eb590f 100644 --- a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/FieldValidatorTests.cs +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/FieldValidatorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma warning disable APL0003 +#pragma warning disable CS0618 // DisableZeroCopy is obsolete using Microsoft.Macios.Bindings.Analyzer.Validators; using Xunit; diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/PropertyOrFieldValidatorTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/PropertyOrFieldValidatorTests.cs index 738eded7d1a6..ca019c36bc7e 100644 --- a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/PropertyOrFieldValidatorTests.cs +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/Validators/PropertyOrFieldValidatorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma warning disable APL0003 +#pragma warning disable CS0618 // DisableZeroCopy is obsolete using Microsoft.Macios.Bindings.Analyzer.Validators; using Xunit; From fc4f229bf04c8bf44065792994b7532031b08236 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 07:02:49 +0000 Subject: [PATCH 17/97] [main] Update dependencies from dotnet/dotnet (#25533) 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**: [20260526.20](https://dev.azure.com/dnceng/internal/_build/results?buildId=2984799) ([315942](https://maestro.dot.net/channel/10307/github:dotnet:dotnet/build/315942)) - **Date Produced**: May 27, 2026 1:23:24 AM UTC - **Commit**: [c9b6c9515ff95716e797906a3eff7179baa272b2](https://github.com/dotnet/dotnet/commit/c9b6c9515ff95716e797906a3eff7179baa272b2) - **Branch**: [release/10.0.4xx](https://github.com/dotnet/dotnet/tree/release/10.0.4xx) - **Dependency Updates**: - From [10.0.0-beta.26270.102 to 10.0.0-beta.26276.120][1] - Microsoft.DotNet.Arcade.Sdk - Microsoft.DotNet.Build.Tasks.Feed - Microsoft.DotNet.SharedFramework.Sdk - From [10.0.301-servicing.26270.102 to 10.0.400-preview.0.26276.120][1] - Microsoft.NET.Sdk - From [10.0.301 to 10.0.400-preview.26276.120][1] - Microsoft.TemplateEngine.Authoring.Tasks [1]: https://github.com/dotnet/dotnet/compare/2970f74d53...c9b6c9515f --- NuGet.config | 1 - eng/Version.Details.props | 10 +++++----- eng/Version.Details.xml | 20 ++++++++++---------- global.json | 6 +++--- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/NuGet.config b/NuGet.config index 31b55049d0f8..1d436b8dfad1 100644 --- a/NuGet.config +++ b/NuGet.config @@ -10,7 +10,6 @@ - diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 2580fca25dba..f05bc8e722c7 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.26270.102 - 10.0.0-beta.26270.102 + 10.0.0-beta.26276.120 + 10.0.0-beta.26276.120 0.11.5-alpha.26070.104 - 10.0.0-beta.26270.102 + 10.0.0-beta.26276.120 10.0.3-servicing.26070.104 10.0.3 10.0.3 - 10.0.301-servicing.26270.102 + 10.0.400-preview.0.26276.120 10.0.3 - 10.0.301 + 10.0.400-preview.26276.120 26.0.11017 18.5.9227 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 21e29be673c0..8532af929144 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,8 +1,8 @@ - + https://github.com/dotnet/dotnet - 2970f74d535c31d8ad587c8127796c72f29bfcee + c9b6c9515ff95716e797906a3eff7179baa272b2 https://github.com/dotnet/dotnet @@ -95,25 +95,25 @@ - + https://github.com/dotnet/dotnet - 2970f74d535c31d8ad587c8127796c72f29bfcee + c9b6c9515ff95716e797906a3eff7179baa272b2 - + https://github.com/dotnet/dotnet - 2970f74d535c31d8ad587c8127796c72f29bfcee + c9b6c9515ff95716e797906a3eff7179baa272b2 - + https://github.com/dotnet/dotnet - 2970f74d535c31d8ad587c8127796c72f29bfcee + c9b6c9515ff95716e797906a3eff7179baa272b2 https://github.com/dotnet/xharness 51ca379106cfd749a498cb0822210ef1aa926e41 - + https://github.com/dotnet/dotnet - 2970f74d535c31d8ad587c8127796c72f29bfcee + c9b6c9515ff95716e797906a3eff7179baa272b2 diff --git a/global.json b/global.json index 40583aca3c5a..e96f69ff8e58 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.301-servicing.26270.102", + "version": "10.0.400-preview.0.26276.120", "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.301-servicing.26270.102" + "dotnet": "10.0.400-preview.0.26276.120" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26270.102" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26276.120" } } From b8e66aaf8c0670c81f7535e4477569500c3d7918 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 09:16:18 +0200 Subject: [PATCH 18/97] [tests] Improve new BasicAuthWorksWhenBearerIsAdvertisedFirst test. (#25503) - Remove unused 'using System.Net.Http.Headers' directive. - Track request counts server-side and assert that at least one unauthenticated request was received before the authenticated retry, ensuring the test validates the actual challenge/fallback flow. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System.Net.Http/NSUrlSessionHandlerTest.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs index f8bf44a1035c..8b1fe66f384a 100644 --- a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs +++ b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs @@ -5,7 +5,6 @@ using System; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -234,6 +233,9 @@ public void BasicAuthWorksWhenBearerIsAdvertisedFirst () var expectedBasicValue = Convert.ToBase64String (Encoding.UTF8.GetBytes ($"{username}:{password}")); var serverReady = new SemaphoreSlim (0, 1); + int requestIndex = 0; + int firstUnauthenticatedIndex = -1; + int firstAuthenticatedIndex = -1; var httpListener = StartListenerOnAvailablePort (out var listeningPort); if (httpListener is null) { @@ -250,14 +252,17 @@ public void BasicAuthWorksWhenBearerIsAdvertisedFirst () var response = context.Response; var authHeader = request.Headers ["Authorization"]; + var currentIndex = Interlocked.Increment (ref requestIndex); if (authHeader is not null && authHeader == $"Basic {expectedBasicValue}") { // Authenticated - return success + Interlocked.CompareExchange (ref firstAuthenticatedIndex, currentIndex, -1); response.StatusCode = 200; var body = Encoding.UTF8.GetBytes ("authenticated"); response.ContentLength64 = body.Length; response.OutputStream.Write (body, 0, body.Length); } else { // Return 401 with Bearer first, then Basic + Interlocked.CompareExchange (ref firstUnauthenticatedIndex, currentIndex, -1); response.StatusCode = 401; response.AddHeader ("WWW-Authenticate", "Bearer realm=\"test\", charset=\"UTF-8\""); response.AppendHeader ("WWW-Authenticate", "Basic realm=\"test\", charset=\"UTF-8\""); @@ -291,6 +296,9 @@ public void BasicAuthWorksWhenBearerIsAdvertisedFirst () Assert.That (ex, Is.Null, $"Exception: {ex}"); Assert.That (statusCode, Is.EqualTo (HttpStatusCode.OK), "Expected 200 OK after Basic auth negotiation"); Assert.That (responseBody, Is.EqualTo ("authenticated"), "Response body"); + Assert.That (firstUnauthenticatedIndex, Is.GreaterThan (0), "Server should have received an unauthenticated request"); + Assert.That (firstAuthenticatedIndex, Is.GreaterThan (0), "Server should have received an authenticated request"); + Assert.That (firstUnauthenticatedIndex, Is.LessThan (firstAuthenticatedIndex), "Unauthenticated request should have arrived before the authenticated retry"); if (serverTask.IsFaulted) Assert.Fail ($"Server task failed: {serverTask.Exception}"); From 90f96d56ea4cfdfd3390367f7a65fd9475bd5f21 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 07:29:59 +0000 Subject: [PATCH 19/97] [main] Update dependencies from dotnet/macios (#25520) This pull request updates the following dependencies ## From https://github.com/dotnet/macios - **Subscription**: [c0371266-dd6f-4959-822b-decc72d2d668](https://maestro.dot.net/subscriptions?search=c0371266-dd6f-4959-822b-decc72d2d668) - **Build**: [20260525.2](https://dev.azure.com/devdiv/DevDiv/_build/results?buildId=14185729) ([315724](https://maestro.dot.net/channel/3884/github:dotnet:macios/build/315724)) - **Date Produced**: May 25, 2026 9:48:07 AM UTC - **Commit**: [336ee8588b15a460a3ff34c3129d5d359e9d27aa](https://github.com/dotnet/macios/commit/336ee8588b15a460a3ff34c3129d5d359e9d27aa) - **Branch**: [release/9.0.1xx](https://github.com/dotnet/macios/tree/release/9.0.1xx) - **Dependency Updates**: - From [26.5.9003 to 26.5.9004][1] - Microsoft.iOS.Sdk.net9.0_26.5 - Microsoft.MacCatalyst.Sdk.net9.0_26.5 - Microsoft.macOS.Sdk.net9.0_26.5 - Microsoft.tvOS.Sdk.net9.0_26.5 [1]: https://github.com/dotnet/macios/compare/5a29bbfbaa...336ee8588b --- NuGet.config | 2 +- eng/Version.Details.props | 8 ++++---- eng/Version.Details.xml | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/NuGet.config b/NuGet.config index 1d436b8dfad1..128c4a3f5b21 100644 --- a/NuGet.config +++ b/NuGet.config @@ -12,7 +12,7 @@ - + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index f05bc8e722c7..7cda8864708c 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -19,16 +19,16 @@ This file should be imported by eng/Versions.props 26.0.11017 18.5.9227 - 26.5.9003 + 26.5.9004 26.0.11017 18.5.9227 - 26.5.9003 + 26.5.9004 26.0.11017 15.5.9227 - 26.5.9003 + 26.5.9004 26.0.11017 18.5.9227 - 26.5.9003 + 26.5.9004 11.0.0-prerelease.26264.1 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8532af929144..f875d42c06e2 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -60,21 +60,21 @@ 797d30720e5e629d23eb146935da94cb1b61047e - + https://github.com/dotnet/macios - 5a29bbfbaac5941a8b229c91f1b606081b49ea10 + 336ee8588b15a460a3ff34c3129d5d359e9d27aa - + https://github.com/dotnet/macios - 5a29bbfbaac5941a8b229c91f1b606081b49ea10 + 336ee8588b15a460a3ff34c3129d5d359e9d27aa - + https://github.com/dotnet/macios - 5a29bbfbaac5941a8b229c91f1b606081b49ea10 + 336ee8588b15a460a3ff34c3129d5d359e9d27aa - + https://github.com/dotnet/macios - 5a29bbfbaac5941a8b229c91f1b606081b49ea10 + 336ee8588b15a460a3ff34c3129d5d359e9d27aa From 04c0a3143e271aafd806a3eb3e91e0c83bddd748 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 09:47:27 +0200 Subject: [PATCH 20/97] [devops] If SYSTEM_PULLREQUEST_PULLREQUESTNUMBER is set, always use that as the PR number. (#25517) There's no need to try to figure out which PR we're working on, if we're told by using the SYSTEM_PULLREQUEST_PULLREQUESTNUMBER environment variable. --- tools/devops/automation/scripts/GitHub.psm1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/devops/automation/scripts/GitHub.psm1 b/tools/devops/automation/scripts/GitHub.psm1 index 82436b73540d..c4b001ee52d1 100644 --- a/tools/devops/automation/scripts/GitHub.psm1 +++ b/tools/devops/automation/scripts/GitHub.psm1 @@ -306,7 +306,7 @@ class GitHubComments { return $true } else { # we might have gotten here because of the trigger type. This means that we are in a PR BUT - # we did not get the PR ids, but those can be found in the diff evirtoment vars + # we did not get the PR ids, but those can be found in the diff environment vars if ($Env:BUILD_REASON -eq "PullRequest") { # set the PR ids to the PR we have in the VSTS env vars $this.PRIds = @($Env:SYSTEM_PULLREQUEST_PULLREQUESTNUMBER) @@ -1118,6 +1118,13 @@ function Get-GitHubPRsForHash { Write-Host "Getting related PR ids for commit $Hash" $prs = [System.Collections.ArrayList]@() + + if ($Env:SYSTEM_PULLREQUEST_PULLREQUESTNUMBER) { + Write-Host "Found PR in environment: $Env:SYSTEM_PULLREQUEST_PULLREQUESTNUMBER" + $prs.Add($Env:SYSTEM_PULLREQUEST_PULLREQUESTNUMBER) > $null + return $prs + } + if ($Env:IS_PR -eq "false") { Write-Host "This isn't a PR, IS_PR=false" return $prs From 70bac4ad23286a94ec5ca3a12971b6f388f24acd Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 07:19:42 -0400 Subject: [PATCH 21/97] [aw] Align Code Radiator base-branch policy with safe-output glob matching (#25440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code Radiator was failing in `safe_outputs` when processing `create_pull_request` for `xcode26.5`, causing code-push output cancellation and masking subsequent `missing_tool` reporting. The root issue was branch-allowlist pattern semantics not matching runtime glob behavior. - **Safe-output base branch policy update** - Updated `safe-outputs.create-pull-request.allowed-base-branches` in `.github/workflows/code-radiator.md` to runtime-compatible globs: - `net*.0` - `xcode*` - `xcode*.*` - This preserves support for both major-only and dotted Xcode branches (for example `xcode26` and `xcode26.5`). - **Workflow lock regeneration** - Recompiled `.github/workflows/code-radiator.lock.yml` so generated `create_pull_request.allowed_base_branches` matches source frontmatter. - **Prompt/body pattern alignment** - Updated the workflow’s documented “Target Branch Patterns” in the markdown body to match the same glob set and avoid config/prompt drift. ```yaml safe-outputs: create-pull-request: allowed-base-branches: - "net*.0" - "xcode*" - "xcode*.*" ``` --------- Co-authored-by: rolfbjarne <249268+rolfbjarne@users.noreply.github.com> --- .github/workflows/code-radiator.lock.yml | 34 ++++++++++++------------ .github/workflows/code-radiator.md | 12 ++++----- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/code-radiator.lock.yml b/.github/workflows/code-radiator.lock.yml index cd32f396e575..3db0cc9188d5 100644 --- a/.github/workflows/code-radiator.lock.yml +++ b/.github/workflows/code-radiator.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a665debda1a902047622496aa625b015b0b9f053d3892454a63ac7b73cc61808","compiler_version":"v0.74.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c4be7d8ac2692259a94c4639bb109ba34737aa41d51fcd6cc2948df40f5a1bb4","compiler_version":"v0.74.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"efa55847f72aadb03490d955263ff911bf758700","version":"v0.74.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.49"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -190,24 +190,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_0170a78c8d56645a_EOF' + cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' - GH_AW_PROMPT_0170a78c8d56645a_EOF + GH_AW_PROMPT_f2376eda3a4bbca0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_0170a78c8d56645a_EOF' + cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' Tools: add_comment(max:10), create_pull_request(max:10), update_pull_request(max:10), add_labels(max:10), push_to_pull_request_branch(max:10), missing_tool, missing_data, noop - GH_AW_PROMPT_0170a78c8d56645a_EOF + GH_AW_PROMPT_f2376eda3a4bbca0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_0170a78c8d56645a_EOF' + cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' - GH_AW_PROMPT_0170a78c8d56645a_EOF + GH_AW_PROMPT_f2376eda3a4bbca0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_0170a78c8d56645a_EOF' + cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -239,12 +239,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_0170a78c8d56645a_EOF + GH_AW_PROMPT_f2376eda3a4bbca0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_0170a78c8d56645a_EOF' + cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' {{#runtime-import .github/workflows/code-radiator.md}} - GH_AW_PROMPT_0170a78c8d56645a_EOF + GH_AW_PROMPT_f2376eda3a4bbca0_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -451,9 +451,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_1344cecc882c8d36_EOF' - {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net[0-9]*.0","xcode[0-9]*","xcode[0-9]*.[0-9]*"],"max":10,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} - GH_AW_SAFE_OUTPUTS_CONFIG_1344cecc882c8d36_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c85768df61d14c26_EOF' + {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} + GH_AW_SAFE_OUTPUTS_CONFIG_c85768df61d14c26_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -796,7 +796,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_2bd87db1366a659d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_be302d958a95e8cb_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -840,7 +840,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_2bd87db1366a659d_EOF + GH_AW_MCP_CONFIG_be302d958a95e8cb_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1563,7 +1563,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net[0-9]*.0\",\"xcode[0-9]*\",\"xcode[0-9]*.[0-9]*\"],\"max\":10,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"]},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"]},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/code-radiator.md b/.github/workflows/code-radiator.md index 9fec3bff181d..496e72c3da55 100644 --- a/.github/workflows/code-radiator.md +++ b/.github/workflows/code-radiator.md @@ -28,9 +28,9 @@ safe-outputs: create-pull-request: max: 10 allowed-base-branches: - - "net[0-9]*.0" - - "xcode[0-9]*" - - "xcode[0-9]*.[0-9]*" + - "net*.0" + - "xcode*" + - "xcode*.*" add-comment: max: 10 target: "*" @@ -54,9 +54,9 @@ Merge code from `main` into active target branches, creating pull requests for e ## Target Branch Patterns Only consider remote branches matching these patterns: -- `net[0-9]*.0` (e.g., `net11.0`, `net10.0`) -- `xcode[0-9]*` (e.g., `xcode26`) -- `xcode[0-9]*.[0-9]*` (e.g., `xcode26.4`) +- `net*.0` (e.g., `net11.0`, `net10.0`) +- `xcode*` (e.g., `xcode26`) +- `xcode*.*` (e.g., `xcode26.4`) Only process branches that have had commits in the last 30 days. From 3a676cc64dc5e56980e943ca745502df90eeaad9 Mon Sep 17 00:00:00 2001 From: VS MobileTools Engineering Service 2 Date: Wed, 27 May 2026 08:53:47 -0700 Subject: [PATCH 22/97] Localized file check-in by OneLocBuild Task: Build definition ID 14411: Build ID 14187915 (#25518) This is the pull request automatically created by the OneLocBuild task in the build process to check-in localized files generated based upon translation source files (.lcl files) handed-back from the downstream localization pipeline. If there are issues in translations, visit https://aka.ms/icxLocBug and log bugs for fixes. The OneLocBuild wiki is https://aka.ms/onelocbuild and the localization process in general is documented at https://aka.ms/AllAboutLoc. --- .../TranslatedAssemblies/MSBStrings.cs.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.de.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.es.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.fr.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.it.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.ja.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.ko.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.pl.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.pt-BR.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.ru.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.tr.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.zh-Hans.resx | 12 ++++++++++++ .../TranslatedAssemblies/MSBStrings.zh-Hant.resx | 12 ++++++++++++ 13 files changed, 156 insertions(+) diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx index 0714444b8e01..f3125770202e 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx @@ -1284,4 +1284,16 @@ The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose. + + The environment variable '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The environment variable '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored in .NET 11+. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + \ No newline at end of file From 0ab164c66ef86aef81c72e2cf0485610fdc6df5c Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 18:16:33 +0200 Subject: [PATCH 23/97] [src] NSBindingSelectionMarker is in AppKit. (#25523) --- src/foundation.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/foundation.cs b/src/foundation.cs index 915fe5d0c9f7..8ceba04a14c1 100644 --- a/src/foundation.cs +++ b/src/foundation.cs @@ -13062,6 +13062,7 @@ interface NSObject2 : NSObjectProtocol { [NoTV] [NoiOS] [NoMacCatalyst] + [ObjectiveCFramework ("AppKit")] interface NSBindingSelectionMarker : NSCopying { /// To be added. /// To be added. From ca43e4a6252db60596a8287d892e9acaed230769 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 18:57:29 +0200 Subject: [PATCH 24/97] [bgen] Replace XPath lookups with Dictionary in DocumentationManager (#25521) DocumentationManager.TryGetDocumentation was calling `doc.SelectSingleNode("/doc/members/member[@name='...']")` for every member (~30K calls for tvOS). Each XPath query traverses the entire XML DOM, making this O(n*m) where n=members queried and m=total members. Replace with a `Dictionary` built once at construction time, giving O(1) lookups thereafter. Performance (tvOS, wall clock time): - Before: 270s, peak RSS 363MB - After: 28s, peak RSS 358MB - Speedup: 9.6x (242s saved) Generated code: verified identical (git diff = empty). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/bgen/DocumentationManager.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/bgen/DocumentationManager.cs b/src/bgen/DocumentationManager.cs index 8b8a29851b37..bd9f3e8c18e2 100644 --- a/src/bgen/DocumentationManager.cs +++ b/src/bgen/DocumentationManager.cs @@ -10,14 +10,25 @@ public class DocumentationManager { string xml; - XmlDocument? doc; + Dictionary? memberLookup; public DocumentationManager (string assembly) { this.xml = Path.ChangeExtension (assembly, ".xml"); if (File.Exists (xml)) { - doc = new XmlDocument (); + var doc = new XmlDocument (); doc.LoadWithoutNetworkAccess (xml); + // Pre-build a dictionary for O(1) member lookups instead of + // O(n) XPath queries on every TryGetDocumentation call. + var members = doc.SelectNodes ("/doc/members/member[@name]"); + if (members is not null) { + memberLookup = new Dictionary (members.Count); + foreach (XmlNode member in members) { + var name = member.Attributes? ["name"]?.Value; + if (name is not null) + memberLookup [name] = member; + } + } } } @@ -38,14 +49,13 @@ public bool TryGetDocumentation (MemberInfo member, [NotNullWhen (true)] out str { documentation = null; - if (doc is null) + if (memberLookup is null) return false; if (!TryGetId (member, out var id)) return false; - var node = doc.SelectSingleNode ($"/doc/members/member[@name='{id}']"); - if (node is null) + if (!memberLookup.TryGetValue (id, out var node)) return false; if (transformNode is not null) From 5092c587b33f4b2a6d043d7b4dd6b00087091ce9 Mon Sep 17 00:00:00 2001 From: VS MobileTools Engineering Service 2 Date: Wed, 27 May 2026 09:57:31 -0700 Subject: [PATCH 25/97] Localized file check-in by OneLocBuild Task: Build definition ID 14411: Build ID 14204921 (#25538) This is the pull request automatically created by the OneLocBuild task in the build process to check-in localized files generated based upon translation source files (.lcl files) handed-back from the downstream localization pipeline. If there are issues in translations, visit https://aka.ms/icxLocBug and log bugs for fixes. The OneLocBuild wiki is https://aka.ms/onelocbuild and the localization process in general is documented at https://aka.ms/AllAboutLoc. --- macios/src/TranslatedAssemblies/Resources.cs.resx | 2 +- macios/src/TranslatedAssemblies/Resources.de.resx | 2 +- macios/src/TranslatedAssemblies/Resources.es.resx | 2 +- macios/src/TranslatedAssemblies/Resources.fr.resx | 2 +- macios/src/TranslatedAssemblies/Resources.it.resx | 2 +- macios/src/TranslatedAssemblies/Resources.ja.resx | 2 +- macios/src/TranslatedAssemblies/Resources.ko.resx | 2 +- macios/src/TranslatedAssemblies/Resources.pl.resx | 2 +- macios/src/TranslatedAssemblies/Resources.pt-BR.resx | 2 +- macios/src/TranslatedAssemblies/Resources.ru.resx | 2 +- macios/src/TranslatedAssemblies/Resources.tr.resx | 2 +- macios/src/TranslatedAssemblies/Resources.zh-Hans.resx | 2 +- macios/src/TranslatedAssemblies/Resources.zh-Hant.resx | 2 +- macios/tools/mtouch/TranslatedAssemblies/Errors.cs.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.de.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.es.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.fr.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.it.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.ja.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.ko.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.pl.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.pt-BR.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.ru.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.tr.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hans.resx | 3 +++ macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hant.resx | 3 +++ 26 files changed, 52 insertions(+), 13 deletions(-) diff --git a/macios/src/TranslatedAssemblies/Resources.cs.resx b/macios/src/TranslatedAssemblies/Resources.cs.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.cs.resx +++ b/macios/src/TranslatedAssemblies/Resources.cs.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.de.resx b/macios/src/TranslatedAssemblies/Resources.de.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.de.resx +++ b/macios/src/TranslatedAssemblies/Resources.de.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.es.resx b/macios/src/TranslatedAssemblies/Resources.es.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.es.resx +++ b/macios/src/TranslatedAssemblies/Resources.es.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.fr.resx b/macios/src/TranslatedAssemblies/Resources.fr.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.fr.resx +++ b/macios/src/TranslatedAssemblies/Resources.fr.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.it.resx b/macios/src/TranslatedAssemblies/Resources.it.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.it.resx +++ b/macios/src/TranslatedAssemblies/Resources.it.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.ja.resx b/macios/src/TranslatedAssemblies/Resources.ja.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.ja.resx +++ b/macios/src/TranslatedAssemblies/Resources.ja.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.ko.resx b/macios/src/TranslatedAssemblies/Resources.ko.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.ko.resx +++ b/macios/src/TranslatedAssemblies/Resources.ko.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.pl.resx b/macios/src/TranslatedAssemblies/Resources.pl.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.pl.resx +++ b/macios/src/TranslatedAssemblies/Resources.pl.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.pt-BR.resx b/macios/src/TranslatedAssemblies/Resources.pt-BR.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.pt-BR.resx +++ b/macios/src/TranslatedAssemblies/Resources.pt-BR.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.ru.resx b/macios/src/TranslatedAssemblies/Resources.ru.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.ru.resx +++ b/macios/src/TranslatedAssemblies/Resources.ru.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.tr.resx b/macios/src/TranslatedAssemblies/Resources.tr.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.tr.resx +++ b/macios/src/TranslatedAssemblies/Resources.tr.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.zh-Hans.resx b/macios/src/TranslatedAssemblies/Resources.zh-Hans.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.zh-Hans.resx +++ b/macios/src/TranslatedAssemblies/Resources.zh-Hans.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/src/TranslatedAssemblies/Resources.zh-Hant.resx b/macios/src/TranslatedAssemblies/Resources.zh-Hant.resx index f4c1d83d2521..7b439f46e642 100644 --- a/macios/src/TranslatedAssemblies/Resources.zh-Hant.resx +++ b/macios/src/TranslatedAssemblies/Resources.zh-Hant.resx @@ -250,7 +250,7 @@ `{0}`: Enums attributed with [{1}] must have an underlying type of `long` or `ulong` - Support for ZeroCopy strings is not implemented. Strings will be marshalled as NSStrings. + The --use-zero-copy option is not supported and will be ignored. Internal sanity check failed, please file a bug report (https://github.com/dotnet/macios/issues/new) with a test case. diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.cs.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.cs.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.cs.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.cs.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.de.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.de.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.de.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.de.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.es.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.es.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.es.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.es.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.fr.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.fr.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.fr.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.fr.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.it.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.it.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.it.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.it.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.ja.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.ja.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.ja.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.ja.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.ko.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.ko.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.ko.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.ko.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.pl.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.pl.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.pl.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.pl.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.pt-BR.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.pt-BR.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.pt-BR.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.pt-BR.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.ru.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.ru.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.ru.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.ru.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.tr.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.tr.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.tr.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.tr.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hans.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hans.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hans.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hans.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} diff --git a/macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hant.resx b/macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hant.resx index 3cbbaf109f2a..ce4b85b8b369 100644 --- a/macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hant.resx +++ b/macios/tools/mtouch/TranslatedAssemblies/Errors.zh-Hant.resx @@ -857,6 +857,9 @@ '{0}' has multiple SupportedSimulator attributes for the '{1}' platform. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + The linker step '{0}' failed during processing: {1} From 9be4da423f3930bd33a004de6c2abca8fa8e0498 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 18:57:39 +0200 Subject: [PATCH 26/97] [tests] Misc improvements to the app size tests (#25522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Make assertion failure messages context-aware: in CI, mention the build artifact; locally, just suggest WRITE_KNOWN_FAILURES=1. - Gate the PublishPipelineArtifact step on the directory actually existing to avoid noisy 'succeeded with issues' when tests pass. - Fix misleading assertion messages for preserved-API checks: 'No added APIs.' → 'Unexpected APIs were added to the preserved set.' - Fix SKILL.md documentation to show actual artifact naming pattern (including uploadPrefix) and realistic file name examples. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/update-expected-app-size/SKILL.md | 14 +++++++------- tests/dotnet/UnitTests/AppSizeTest.cs | 18 +++++++++++++----- .../automation/templates/tests/run-tests.yml | 9 ++++++++- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/.github/skills/update-expected-app-size/SKILL.md b/.github/skills/update-expected-app-size/SKILL.md index 88d888c17a0e..1537c6cc59b2 100644 --- a/.github/skills/update-expected-app-size/SKILL.md +++ b/.github/skills/update-expected-app-size/SKILL.md @@ -21,13 +21,13 @@ Download updated expected app size files from Azure DevOps artifacts for the cur The app size tests (`tests/dotnet/UnitTests/AppSizeTest.cs`) compare the built app's size and preserved APIs against expected files stored in `tests/dotnet/UnitTests/expected/`. When the test detects a difference and `WRITE_KNOWN_FAILURES` is not set, it writes the updated expected file to `$(Build.ArtifactStagingDirectory)/updated-expected-sizes/`, and a pipeline step publishes this directory as a build artifact. -The artifact name follows the pattern `updated-expected-sizes-{testPrefix}-{attempt}` (e.g., `updated-expected-sizes-dotnettests_ios-1`). Inside the artifact, files are named: -- `{Platform}-{Runtime}-size.txt` — e.g., `iOS-MonoVM-size.txt` -- `{Platform}-{Runtime}-preservedapis.txt` — e.g., `iOS-MonoVM-preservedapis.txt` +The artifact name follows the pattern `{uploadPrefix}updated-expected-sizes-{testPrefix}-{attempt}` (e.g., `updated-expected-sizes-dotnettests_ios-1`). Inside the artifact, files are named after the test variant: +- `{Platform}-{Variant}-size.txt` — e.g., `iOS-MonoVM-size.txt`, `iOS-MonoVM-interpreter-size.txt`, `iOS-NativeAOT-TrimmableStatic-size.txt`, `MacOSX-CoreCLR-Interpreter-size.txt` +- `{Platform}-{Variant}-preservedapis.txt` — e.g., `iOS-MonoVM-preservedapis.txt`, `MacCatalyst-MonoVM-interpreter-preservedapis.txt` The expected files on disk are at: -- `tests/dotnet/UnitTests/expected/{Platform}-{Runtime}-size.txt` -- `tests/dotnet/UnitTests/expected/{Platform}-{Runtime}-preservedapis.txt` +- `tests/dotnet/UnitTests/expected/{Platform}-{Variant}-size.txt` +- `tests/dotnet/UnitTests/expected/{Platform}-{Variant}-preservedapis.txt` ## Workflow @@ -111,8 +111,8 @@ After placing the files: If automated download fails (auth issues, etc.), provide the user with: 1. The Azure DevOps build URL 2. Instructions to navigate to the build → Summary → Artifacts section -3. Look for individual artifacts whose names match the patterns above -4. Download each file and place it as `tests/dotnet/UnitTests/expected/{artifactName}.txt` +3. Look for individual artifacts whose names contain `updated-expected-sizes` +4. Download the artifact zip, extract it, and copy the `.txt` files (e.g., `iOS-MonoVM-interpreter-size.txt`) into `tests/dotnet/UnitTests/expected/` ## Fallback: Run Locally diff --git a/tests/dotnet/UnitTests/AppSizeTest.cs b/tests/dotnet/UnitTests/AppSizeTest.cs index db6d4d432dbf..701aed8bbf74 100644 --- a/tests/dotnet/UnitTests/AppSizeTest.cs +++ b/tests/dotnet/UnitTests/AppSizeTest.cs @@ -201,15 +201,16 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, Console.WriteLine ($" Updated expected file: {expectedSizeReportPath}"); } else if (hasDifferences) { UploadUpdatedExpectedFile (expectedSizeReportPath, report.ToString ()); + var updateHint = GetUpdateHint (); if (hasFileDifferences) { var details = new List (); foreach (var key in filesAdded) details.Add ($"added: '{key}'"); foreach (var key in filesRemoved) details.Add ($"removed: '{key}'"); - Assert.Fail ($"The app bundle's file list changed ({string.Join (", ", details)}). The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); + Assert.Fail ($"The app bundle's file list changed ({string.Join (", ", details)}). {updateHint}"); } - Assert.Fail ($"{msg} The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); + Assert.Fail ($"{msg} {updateHint}"); } } @@ -251,9 +252,9 @@ void AssertAssemblyReport (ApplePlatform platform, string name, string appPath, if (!update) { if (addedAPIs.Count > 0 || removedAPIs.Count > 0) { UploadUpdatedExpectedFile (expectedFile, string.Join ('\n', preservedAPIs) + "\n"); - var updateMsg = " The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."; - Assert.That (addedAPIs, Is.Empty, "No added APIs." + updateMsg); - Assert.That (removedAPIs, Is.Empty, "No removed APIs." + updateMsg); + var updateHint = " " + GetUpdateHint (); + Assert.That (addedAPIs, Is.Empty, "Unexpected APIs were added to the preserved set." + updateHint); + Assert.That (removedAPIs, Is.Empty, "APIs were unexpectedly removed from the preserved set." + updateHint); } } } @@ -274,6 +275,13 @@ static void UploadUpdatedExpectedFile (string expectedFilePath, string content) Console.WriteLine ($" Updated expected file written to: {outputFile}"); } + static string GetUpdateHint () + { + if (IsInCI) + return "The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."; + return "Set WRITE_KNOWN_FAILURES=1 to update the expected files in-place."; + } + static string FormatBytes (long bytes, bool alwaysShowSign = false) { return $"{(alwaysShowSign && bytes > 0 ? "+" : "")}{bytes:N0} bytes ({bytes / 1024.0:N1} KB = {bytes / (1024.0 * 1024.0):N1} MB)"; diff --git a/tools/devops/automation/templates/tests/run-tests.yml b/tools/devops/automation/templates/tests/run-tests.yml index 40c85db23a1a..483f3afbb30c 100644 --- a/tools/devops/automation/templates/tests/run-tests.yml +++ b/tools/devops/automation/templates/tests/run-tests.yml @@ -173,13 +173,20 @@ steps: condition: succeededOrFailed() # Upload updated expected app size files if the app size tests produced any. +- bash: | + if [ -d "$(Build.ArtifactStagingDirectory)/updated-expected-sizes" ]; then + echo "##vso[task.setvariable variable=HAS_UPDATED_EXPECTED_SIZES]true" + fi + displayName: 'Check for updated expected app size files' + condition: succeededOrFailed() + - task: PublishPipelineArtifact@1 displayName: 'Publish Artifact: Updated expected app size files' inputs: targetPath: '$(Build.ArtifactStagingDirectory)/updated-expected-sizes' artifactName: '${{ parameters.uploadPrefix }}updated-expected-sizes-${{ parameters.testPrefix }}-$(System.JobAttempt)' continueOnError: true - condition: succeededOrFailed() + condition: and(succeededOrFailed(), eq(variables['HAS_UPDATED_EXPECTED_SIZES'], 'true')) - pwsh: | $summaryName = "TestSummary-${{ parameters.testPrefix }}.md" From 963bc34671abab98f25aef3ef39922d65a70fc4c Mon Sep 17 00:00:00 2001 From: VS MobileTools Engineering Service 2 Date: Wed, 27 May 2026 09:59:03 -0700 Subject: [PATCH 27/97] Localized file check-in by OneLocBuild Task: Build definition ID 14411: Build ID 14204473 (#25535) This is the pull request automatically created by the OneLocBuild task in the build process to check-in localized files generated based upon translation source files (.lcl files) handed-back from the downstream localization pipeline. If there are issues in translations, visit https://aka.ms/icxLocBug and log bugs for fixes. The OneLocBuild wiki is https://aka.ms/onelocbuild and the localization process in general is documented at https://aka.ms/AllAboutLoc. From 90bc0339e695715f7e8f8b6314119f7039a04987 Mon Sep 17 00:00:00 2001 From: VS MobileTools Engineering Service 2 Date: Wed, 27 May 2026 09:59:59 -0700 Subject: [PATCH 28/97] Localized file check-in by OneLocBuild Task: Build definition ID 14411: Build ID 14204987 (#25539) This is the pull request automatically created by the OneLocBuild task in the build process to check-in localized files generated based upon translation source files (.lcl files) handed-back from the downstream localization pipeline. If there are issues in translations, visit https://aka.ms/icxLocBug and log bugs for fixes. The OneLocBuild wiki is https://aka.ms/onelocbuild and the localization process in general is documented at https://aka.ms/AllAboutLoc. From eeb0a42d3781a9f19035d5816e67a9a166c97695 Mon Sep 17 00:00:00 2001 From: VS MobileTools Engineering Service 2 Date: Wed, 27 May 2026 10:01:06 -0700 Subject: [PATCH 29/97] Localized file check-in by OneLocBuild Task: Build definition ID 14411: Build ID 14204841 (#25537) This is the pull request automatically created by the OneLocBuild task in the build process to check-in localized files generated based upon translation source files (.lcl files) handed-back from the downstream localization pipeline. If there are issues in translations, visit https://aka.ms/icxLocBug and log bugs for fixes. The OneLocBuild wiki is https://aka.ms/onelocbuild and the localization process in general is documented at https://aka.ms/AllAboutLoc. From 1becd48487877965deb60e9748cc06579702e103 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 19:07:25 +0200 Subject: [PATCH 30/97] [dotnet] Don't enable InlineClassGetHandle if we're using the static registrar as a custom trimmer step. (#25524) The InlineClassGetHandle step runs before marking, and the StaticRegistrar step runs after sweeping, which causes a problem when the InlineClassGetHandle step needs the registrar to run first. This won't be a problem once we've moved out of custom trimmer steps. --- dotnet/targets/Xamarin.Shared.Sdk.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.props b/dotnet/targets/Xamarin.Shared.Sdk.props index 532e7e2a44cb..ce84e27e899b 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.props +++ b/dotnet/targets/Xamarin.Shared.Sdk.props @@ -108,7 +108,7 @@ - + strict compatibility From 8eb8f4bead24fae55229f6e244c12175847a09cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 19:32:22 +0200 Subject: [PATCH 31/97] Bump actions/upload-artifact from 4.6.2 to 7.0.1 (#25550) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.1. --- .github/workflows/autoformat-v2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autoformat-v2.yml b/.github/workflows/autoformat-v2.yml index 695354c78d1a..4fb4d4a234df 100644 --- a/.github/workflows/autoformat-v2.yml +++ b/.github/workflows/autoformat-v2.yml @@ -91,7 +91,7 @@ jobs: - name: 'Upload patch' if: steps.patch.outputs.changes == 'true' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: autoformat path: autoformat-output/ From 16053556bda02e8bb8a8bd14e4f26055a0ce6e79 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 20:46:06 +0200 Subject: [PATCH 32/97] [runtime] Show the error code if calling coreclr_initialize fails. (#25544) --- runtime/coreclr-bridge.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runtime/coreclr-bridge.m b/runtime/coreclr-bridge.m index e55c125461c8..1368766a6040 100644 --- a/runtime/coreclr-bridge.m +++ b/runtime/coreclr-bridge.m @@ -507,6 +507,10 @@ LOG_CORECLR (stderr, "xamarin_vm_initialize (%i, %p, %p): rv: %i domainId: %i handle: %p\n", combinedPropertyCount, combinedPropertyKeys, combinedPropertyValues, rv, coreclr_domainId, coreclr_handle); + if (rv != 0) { + LOG (PRODUCT ": The call to 'coreclr_initialize' failed: %i (%p)\n", rv, rv); + } + return rv == 0; } #endif // !defined (NATIVEAOT) From cad4a4b8c378c10ae47a51c0dac4a3577a8d45c5 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 27 May 2026 20:48:01 +0200 Subject: [PATCH 33/97] [github] Fix workflows to pin actions & drop credentials when possible. (#25545) --- .github/workflows/copilot-setup-steps.yml | 4 +++- .github/workflows/inter-branch-merge-flow.yml | 2 +- .github/workflows/update-single-platform-branches.yml | 3 ++- .github/workflows/yamllint.yml | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index f4841a250f76..451d17b79bd5 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -19,7 +19,9 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Install gh-aw extension uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: diff --git a/.github/workflows/inter-branch-merge-flow.yml b/.github/workflows/inter-branch-merge-flow.yml index b6b979ddad46..7c95ddd17a32 100644 --- a/.github/workflows/inter-branch-merge-flow.yml +++ b/.github/workflows/inter-branch-merge-flow.yml @@ -30,4 +30,4 @@ permissions: jobs: Merge: - uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main + uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@b36a3594d11b8d56bf7d3dbce919e0688715d5ec # main diff --git a/.github/workflows/update-single-platform-branches.yml b/.github/workflows/update-single-platform-branches.yml index 0944fe689a68..bb0e62bcf29d 100644 --- a/.github/workflows/update-single-platform-branches.yml +++ b/.github/workflows/update-single-platform-branches.yml @@ -19,9 +19,10 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 + persist-credentials: true # need to push changes - name: 'Update branches' run: | diff --git a/.github/workflows/yamllint.yml b/.github/workflows/yamllint.yml index d2fa268c38a3..ccef5822206a 100644 --- a/.github/workflows/yamllint.yml +++ b/.github/workflows/yamllint.yml @@ -13,10 +13,11 @@ jobs: steps: - name: 'Checkout' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 + persist-credentials: false - name: Install yamllint run: pip install yamllint From b2159abc9e08563146d184abcbd23269359c1492 Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Thu, 28 May 2026 10:01:22 -0400 Subject: [PATCH 34/97] [CI] Add retry count on task failure for AzureCLI step in provision.yml (#25565) Tries to mitigate: https://devdiv.visualstudio.com/0bdbc590-a062-4c3f-b0f6-9383f67865ee/_apis/build/builds/14219070/logs/948 --- tools/devops/automation/templates/common/provision.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/devops/automation/templates/common/provision.yml b/tools/devops/automation/templates/common/provision.yml index 6424ffd2103d..b60e12e2fb84 100644 --- a/tools/devops/automation/templates/common/provision.yml +++ b/tools/devops/automation/templates/common/provision.yml @@ -35,6 +35,7 @@ steps: - task: AzureCLI@2 displayName: 'Generate BosStorageMirror SAS tokens' condition: and(succeeded(), ${{ parameters.enabled }}) + retryCountOnTaskFailure: 3 inputs: azureSubscription: 'Xamarin - RelEng (BosStorageMirror-Contributor-MI)' scriptType: 'bash' From 52b5249e851eb98a46a6c7dbbd6b7b0d1f33a3ef Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 28 May 2026 16:52:21 +0200 Subject: [PATCH 35/97] [github] Add zizmor GitHub Actions security linter workflow (#25560) Fixes https://github.com/dotnet/macios/issues/25496. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/zizmor.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 000000000000..a27c501d6710 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,34 @@ +name: zizmor 🌈 + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + +permissions: {} + +jobs: + zizmor: + name: zizmor 🌈 + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + actions: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Find workflow files + id: find-files + run: | + files=$(find .github/workflows -name '*.yml' ! -name '*.lock.yml' | sort | tr '\n' ' ') + echo "files=$files" >> "$GITHUB_OUTPUT" + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + with: + inputs: ${{ steps.find-files.outputs.files }} From a584d6e224930279d9a4c0bc98a3a7a019348853 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 12:01:43 -0400 Subject: [PATCH 36/97] [github] Fix Code Radiator: prevent upstream unsetting that breaks safeoutputs PR creation. Fixes #25555. (#25563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Code Radiator workflow consistently failed with `"No changes to commit - no commits found"` from `safeoutputs create_pull_request` despite the agent having verified commits via `git log origin/base..HEAD`. ## Root Cause The safeoutputs `create_pull_request` tool detects pushable commits using `@{upstream}..HEAD`. The agent was unsetting the upstream tracking branch after creating the merge branch (to avoid accidentally pushing to the target branch via plain `git push`), which caused the tool to find no commits. The manual `git push origin ""` instruction was also broken — the agent job only has `contents: read` permission. ## Changes (`code-radiator.md`) - **Remove manual `git push` instruction** — the agent cannot push (read-only token); safeoutputs handles all pushing - **Explicit `do NOT unset upstream` warning** — explains that `@{upstream}` is how the tool detects commits; unsetting it silently breaks PR creation - **Require `base` parameter** when calling `safeoutputs create_pull_request` as a backup reference for commit detection - **Update Important Notes** — replace ambiguous "use `gh` CLI for PR operations" with clear guidance to use safeoutputs for push/create and `gh`/MCP tools only for non-push operations Fixes https://github.com/dotnet/macios/issues/25555. --------- Co-authored-by: rolfbjarne <249268+rolfbjarne@users.noreply.github.com> --- .github/aw/actions-lock.json | 6 +- .github/workflows/code-radiator.lock.yml | 149 +++++++++++++---------- .github/workflows/code-radiator.md | 31 ++--- 3 files changed, 103 insertions(+), 83 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 06276c7f1e4c..25662b0eea48 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -25,10 +25,10 @@ "version": "v7.0.1", "sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" }, - "github/gh-aw-actions/setup@v0.74.8": { + "github/gh-aw-actions/setup@v0.76.1": { "repo": "github/gh-aw-actions/setup", - "version": "v0.74.8", - "sha": "efa55847f72aadb03490d955263ff911bf758700" + "version": "v0.76.1", + "sha": "46d564922b082d0db93244972e8005ea6904ee5f" } }, "containers": { diff --git a/.github/workflows/code-radiator.lock.yml b/.github/workflows/code-radiator.lock.yml index 3db0cc9188d5..abcb3b886b9c 100644 --- a/.github/workflows/code-radiator.lock.yml +++ b/.github/workflows/code-radiator.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c4be7d8ac2692259a94c4639bb109ba34737aa41d51fcd6cc2948df40f5a1bb4","compiler_version":"v0.74.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"efa55847f72aadb03490d955263ff911bf758700","version":"v0.74.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.49"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"abaf2819115d39131e4028d6937c23ce759047f7a526bee9eebb76b928ca1e0c","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.74.8). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.76.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -36,15 +36,15 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 +# - github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.49 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.49 -# - ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 -# - ghcr.io/github/github-mcp-server:v1.0.4 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f +# - ghcr.io/github/gh-aw-firewall/agent:0.25.55 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.55 +# - ghcr.io/github/gh-aw-mcpg:v0.3.19 +# - ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 +# - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 name: "Code Radiator" on: @@ -91,14 +91,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -106,16 +107,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: "claude-sonnet-4.5" - GH_AW_INFO_VERSION: "1.0.48" - GH_AW_INFO_AGENT_VERSION: "1.0.48" - GH_AW_INFO_CLI_VERSION: "v0.74.8" + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AGENT_VERSION: "1.0.52" + GH_AW_INFO_CLI_VERSION: "v0.76.1" GH_AW_INFO_WORKFLOW_NAME: "Code Radiator" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.49" + GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -138,6 +139,7 @@ jobs: sparse-checkout: | .github .agents + .antigravity .claude .codex .crush @@ -148,8 +150,8 @@ jobs: fetch-depth: 1 - name: Save agent config folders for base branch restoration env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" # poutine:ignore untrusted_checkout_exec run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file @@ -167,7 +169,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.74.8" + GH_AW_COMPILED_VERSION: "v0.76.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -190,24 +192,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' + cat << 'GH_AW_PROMPT_87b0926ff2940306_EOF' - GH_AW_PROMPT_f2376eda3a4bbca0_EOF + GH_AW_PROMPT_87b0926ff2940306_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' + cat << 'GH_AW_PROMPT_87b0926ff2940306_EOF' Tools: add_comment(max:10), create_pull_request(max:10), update_pull_request(max:10), add_labels(max:10), push_to_pull_request_branch(max:10), missing_tool, missing_data, noop - GH_AW_PROMPT_f2376eda3a4bbca0_EOF + GH_AW_PROMPT_87b0926ff2940306_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' + cat << 'GH_AW_PROMPT_87b0926ff2940306_EOF' - GH_AW_PROMPT_f2376eda3a4bbca0_EOF + GH_AW_PROMPT_87b0926ff2940306_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' + cat << 'GH_AW_PROMPT_87b0926ff2940306_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -239,12 +241,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_f2376eda3a4bbca0_EOF + GH_AW_PROMPT_87b0926ff2940306_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_f2376eda3a4bbca0_EOF' + cat << 'GH_AW_PROMPT_87b0926ff2940306_EOF' {{#runtime-import .github/workflows/code-radiator.md}} - GH_AW_PROMPT_f2376eda3a4bbca0_EOF + GH_AW_PROMPT_87b0926ff2940306_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -316,6 +318,7 @@ jobs: /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills if-no-files-found: ignore retention-days: 1 @@ -335,15 +338,15 @@ jobs: GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs GH_AW_WORKFLOW_ID_SANITIZED: coderadiator outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} @@ -352,7 +355,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -361,7 +364,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -416,11 +420,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -436,24 +440,28 @@ jobs: - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Restore inline sub-agents from activation artifact env: GH_AW_SUB_AGENT_DIR: ".github/agents" GH_AW_SUB_AGENT_EXT: ".agent.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 ghcr.io/github/github-mcp-server:v1.0.4 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.55 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55 ghcr.io/github/gh-aw-firewall/squid:0.25.55 ghcr.io/github/gh-aw-mcpg:v0.3.19 ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c85768df61d14c26_EOF' - {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} - GH_AW_SAFE_OUTPUTS_CONFIG_c85768df61d14c26_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b50ec395f48eecb1_EOF' + {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review"},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} + GH_AW_SAFE_OUTPUTS_CONFIG_b50ec395f48eecb1_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -792,11 +800,11 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.9' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.19' mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_be302d958a95e8cb_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_76afe8ad36364972_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -840,7 +848,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_be302d958a95e8cb_EOF + GH_AW_MCP_CONFIG_76afe8ad36364972_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -874,7 +882,7 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","docs.github.com","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5","gemini-pro","haiku","any"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["large"],"claude":["agent","sonnet-6x","haiku","any"],"codex":["agent","gpt-5-codex","gpt-5","any"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"copilot":["agent","gpt-5.4","sonnet","gpt-5","any"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent","gemini-pro","gemini-flash","any"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite","copilot/raptor*mini*"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4-5*","anthropic/*sonnet-4.5*","anthropic/*sonnet-4-5*","copilot/*sonnet-3.7*","copilot/*sonnet-3-7*","anthropic/*sonnet-3.7*","anthropic/*sonnet-3-7*","copilot/*sonnet-3.5*","copilot/*sonnet-3-5*","anthropic/*sonnet-3.5*","anthropic/*sonnet-3-5*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","docs.github.com","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then @@ -893,7 +901,7 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.74.8 + GH_AW_VERSION: v0.76.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -908,11 +916,11 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors + - name: Detect agent errors if: always() + id: detect-agent-errors continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} @@ -1096,7 +1104,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1105,7 +1113,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1128,6 +1137,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Code Radiator" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/code-radiator.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "true" @@ -1144,6 +1154,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Code Radiator" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/code-radiator.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} @@ -1161,6 +1172,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Code Radiator" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/code-radiator.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1175,6 +1187,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Code Radiator" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/code-radiator.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1189,6 +1202,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Code Radiator" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/code-radiator.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "code-radiator" @@ -1237,7 +1251,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1246,7 +1260,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1273,7 +1288,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.55 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55 ghcr.io/github/gh-aw-firewall/squid:0.25.55 - name: Check if detection needed id: detection_guard if: always() @@ -1332,11 +1347,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1351,7 +1366,7 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then @@ -1368,7 +1383,7 @@ jobs: COPILOT_MODEL: claude-sonnet-4.5 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.74.8 + GH_AW_VERSION: v0.76.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1442,9 +1457,10 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "claude-sonnet-4.5" - GH_AW_ENGINE_VERSION: "1.0.48" + GH_AW_ENGINE_VERSION: "1.0.52" GH_AW_WORKFLOW_ID: "code-radiator" GH_AW_WORKFLOW_NAME: "Code Radiator" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/code-radiator.md" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} @@ -1461,7 +1477,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1470,7 +1486,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Radiator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-radiator.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1522,7 +1539,7 @@ jobs: ref: ${{ github.event.repository.default_branch }} token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false - fetch-depth: 1 + fetch-depth: 0 - name: Checkout repository if: ((!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch')) && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -1530,7 +1547,7 @@ jobs: ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false - fetch-depth: 1 + fetch-depth: 0 - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: @@ -1563,7 +1580,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"]},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\"},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/code-radiator.md b/.github/workflows/code-radiator.md index 496e72c3da55..8618affe282c 100644 --- a/.github/workflows/code-radiator.md +++ b/.github/workflows/code-radiator.md @@ -42,7 +42,7 @@ safe-outputs: push-to-pull-request-branch: max: 10 target: "*" - title-prefix: "🤖 Merge 'main' => '" + required-title-prefix: "🤖 Merge 'main' => '" update-pull-request: max: 10 --- @@ -137,19 +137,19 @@ If there are merge conflicts: - Add the `do-not-merge` label. - Add a comment requesting human review of the conflict resolution, listing which files were manually resolved. -#### e. Push and create/update the PR +#### e. Create or update the PR -```bash -git push origin "" -``` +Do **NOT** run `git push` manually. The safeoutputs tool handles pushing. + +Use the `create_pull_request` safeoutput tool to push the branch and create/update the PR: +- `branch`: `` (e.g., `merge/main-to-net11.0-20260527`) +- `base`: `` (e.g., `net11.0`) — **always provide this field** +- `title`: `🤖 Merge 'main' => ''` +- `body`: `Automated merge of \`main\` into \`\`.\n\nCreated by the code-radiator workflow.` + +> **Important**: Do NOT unset the upstream tracking branch. After `git checkout -B "" "origin/"`, the upstream is set to `origin/`. Keep it set — the safeoutputs tool relies on this to detect the commits to push. -- If updating an existing PR: the push is sufficient (the PR updates automatically). -- If creating a new PR: - - Title: `🤖 Merge 'main' => ''` - - Body: `Automated merge of \`main\` into \`\`.\n\nCreated by the code-radiator workflow.` - - Base: the target branch - - Head: the local branch - - Enable automerge (merge strategy) on the new PR. +After creating the PR, enable automerge (merge strategy) using the GitHub MCP `enable_auto_merge` tool or `gh pr merge --auto --merge`. ### 3. Summary @@ -178,7 +178,10 @@ Split version strings on `.`, `-`, and `+`. Compare each segment numerically. Ex ## Important Notes -- Never force push. Always use regular `git push`. +- Never force push. +- Do NOT run `git push` manually — the safeoutputs `create_pull_request` tool handles pushing. +- Do NOT unset the upstream tracking branch after creating the local branch. The safeoutputs tool uses `@{upstream}` to detect commits that need to be pushed. Unsetting the upstream causes the tool to report "No changes to commit - no commits found" even when commits exist. +- Always provide the `base` branch when calling `safeoutputs create_pull_request` (e.g., `base: "net11.0"`). - The workflow operates on the current repository checkout. - Run `git fetch origin` before starting to ensure up-to-date remote refs. -- Use `gh` CLI for PR operations (create, comment, list, merge --auto). +- Use the GitHub MCP tools or `gh` CLI for non-push PR operations (comment, list, merge --auto, enable automerge). From 05a09417b3640eab1aa6012293e7aa95f3feafa3 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 12:48:52 +0000 Subject: [PATCH 37/97] [main] Update dependencies from dotnet/dotnet (#25557) 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**: [20260527.9](https://dev.azure.com/dnceng/internal/_build/results?buildId=2985462) ([316084](https://maestro.dot.net/channel/10307/github:dotnet:dotnet/build/316084)) - **Date Produced**: May 27, 2026 7:32:35 PM UTC - **Commit**: [2f68b99e483cd8f4acbc1aa365fc806f1bd2374b](https://github.com/dotnet/dotnet/commit/2f68b99e483cd8f4acbc1aa365fc806f1bd2374b) - **Branch**: [release/10.0.4xx](https://github.com/dotnet/dotnet/tree/release/10.0.4xx) - **Dependency Updates**: - From [10.0.0-beta.26276.120 to 10.0.0-beta.26277.109][1] - Microsoft.DotNet.Arcade.Sdk - Microsoft.DotNet.Build.Tasks.Feed - Microsoft.DotNet.SharedFramework.Sdk - From [10.0.400-preview.0.26276.120 to 10.0.400-preview.0.26277.109][1] - Microsoft.NET.Sdk - From [10.0.400-preview.26276.120 to 10.0.400-preview.26277.109][1] - Microsoft.TemplateEngine.Authoring.Tasks [1]: https://github.com/dotnet/dotnet/compare/c9b6c9515f...2f68b99e48 --- eng/Version.Details.props | 10 +++++----- eng/Version.Details.xml | 20 ++++++++++---------- global.json | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 7cda8864708c..5277e7173d29 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.26276.120 - 10.0.0-beta.26276.120 + 10.0.0-beta.26277.109 + 10.0.0-beta.26277.109 0.11.5-alpha.26070.104 - 10.0.0-beta.26276.120 + 10.0.0-beta.26277.109 10.0.3-servicing.26070.104 10.0.3 10.0.3 - 10.0.400-preview.0.26276.120 + 10.0.400-preview.0.26277.109 10.0.3 - 10.0.400-preview.26276.120 + 10.0.400-preview.26277.109 26.0.11017 18.5.9227 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index f875d42c06e2..e53abe16f371 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,8 +1,8 @@ - + https://github.com/dotnet/dotnet - c9b6c9515ff95716e797906a3eff7179baa272b2 + 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b https://github.com/dotnet/dotnet @@ -95,25 +95,25 @@ - + https://github.com/dotnet/dotnet - c9b6c9515ff95716e797906a3eff7179baa272b2 + 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b - + https://github.com/dotnet/dotnet - c9b6c9515ff95716e797906a3eff7179baa272b2 + 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b - + https://github.com/dotnet/dotnet - c9b6c9515ff95716e797906a3eff7179baa272b2 + 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b https://github.com/dotnet/xharness 51ca379106cfd749a498cb0822210ef1aa926e41 - + https://github.com/dotnet/dotnet - c9b6c9515ff95716e797906a3eff7179baa272b2 + 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b diff --git a/global.json b/global.json index e96f69ff8e58..294c78cab356 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.400-preview.0.26276.120", + "version": "10.0.400-preview.0.26277.109", "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.26276.120" + "dotnet": "10.0.400-preview.0.26277.109" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26276.120" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26277.109" } } From 32352a1a9f0026a9ee69130534d18964350bc723 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 29 May 2026 14:54:11 +0200 Subject: [PATCH 38/97] [tools] Make csproj.inc generation parallel-safe. (#25547) --- tools/common/create-makefile-fragment.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/common/create-makefile-fragment.sh b/tools/common/create-makefile-fragment.sh index 076245d65ca2..d436c538b039 100755 --- a/tools/common/create-makefile-fragment.sh +++ b/tools/common/create-makefile-fragment.sh @@ -33,13 +33,15 @@ fi PROJECT_FILE="$1" PROJECT=$(basename -s .csproj "$PROJECT_FILE") PROJECT_DIR=$(dirname "$PROJECT_FILE") -FRAGMENT_PATH="$2" +FINAL_FRAGMENT_PATH="$2" REFERENCES_PATH=$(pwd)/$PROJECT-references.txt -if test -z "$FRAGMENT_PATH"; then - FRAGMENT_PATH=$PROJECT_FILE.inc +if test -z "$FINAL_FRAGMENT_PATH"; then + FINAL_FRAGMENT_PATH=$PROJECT_FILE.inc fi +FRAGMENT_PATH="$FINAL_FRAGMENT_PATH.$$.tmp" + BUILD_EXECUTABLE="dotnet build" if ! dotnet --version >& /dev/null; then @@ -59,6 +61,7 @@ fi function upon_exit () { rm -f "$PROJECT_DIR/ProjectInspector.csproj" + rm -f "$FRAGMENT_PATH" } trap upon_exit EXIT cp ProjectInspector.csproj "$PROJECT_DIR" @@ -89,6 +92,7 @@ function delete_tmpproj if test -n "$TMPPROJ"; then rm -f "$TMPPROJ" fi + rm -f "$FRAGMENT_PATH" } trap delete_tmpproj EXIT trap delete_tmpproj ERR @@ -138,5 +142,7 @@ if test -z "$ABSOLUTE_PATHS"; then sed "${SED_INPLACE_FLAGS[@]}" "s@$PROJECT_DIR/@@" "$FRAGMENT_PATH" fi +mv "$FRAGMENT_PATH" "$FINAL_FRAGMENT_PATH" + # Cleanup rm -f "${INPUT_PATHS[@]}" From ea694e795473091ac4414e3ba97b42dcffb8b65f Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 1 Jun 2026 07:45:54 +0200 Subject: [PATCH 39/97] [dotnet] Set the 'SdkDevPath' property for a few tasks. --- dotnet/targets/Microsoft.Sdk.R2R.targets | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dotnet/targets/Microsoft.Sdk.R2R.targets b/dotnet/targets/Microsoft.Sdk.R2R.targets index 5db954f9fd45..e083e68c3ff0 100644 --- a/dotnet/targets/Microsoft.Sdk.R2R.targets +++ b/dotnet/targets/Microsoft.Sdk.R2R.targets @@ -277,6 +277,7 @@ MinSupportedOSPlatformVersion="$(MinSupportedOSPlatformVersion)" ProjectDir="$(MSBuildProjectDirectory)" ResourcePrefix="$(_ResourcePrefix)" + SdkDevPath="$(_SdkDevPath)" SdkIsSimulator="$(_SdkIsSimulator)" SdkVersion="$(_SdkVersion)" SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)" @@ -512,6 +513,7 @@ MinSupportedOSPlatformVersion="$(MinSupportedOSPlatformVersion)" ProjectDir="$(MSBuildProjectDirectory)" ResourcePrefix="$(_ResourcePrefix)" + SdkDevPath="$(_SdkDevPath)" SdkIsSimulator="$(_SdkIsSimulator)" SdkVersion="$(_SdkVersion)" SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)" From ed01fc2e50560e706a5a941958db30d73e6fa46d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 1 Jun 2026 08:44:16 +0200 Subject: [PATCH 40/97] [msbuild] Fix Content items with PublishFolderType losing files that match SDK names. Fixes #25497. (#25528) Content items with PublishFolderType (e.g. RootDirectory) would lose files whose names match SDK assemblies or runtime files (e.g. libclrgc.dylib, Microsoft.macOS.dll). This happened because the .NET SDK's ResolveOverlappingItemGroupConflicts task (during ComputeResolvedFilesToPublishList) removes user items when their filename conflicts with SDK package files. Fix this by setting TargetPath metadata (from Link) when Content/BundleResource items with PublishFolderType are first added to ResolvedFileToPublish. This way the conflict resolver sees the full relative path (e.g. Contents/SharedSupport/SubApp.app/.../libclrgc.dylib) instead of just the filename, and doesn't flag them as conflicts with SDK files. Fixes #25497. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- msbuild/Xamarin.Shared/Xamarin.Shared.targets | 16 +++++-- .../AppDelegate.cs | 12 +++++ .../helper/.gitignore | 2 + .../osx-arm64/HeartBeatHandlerMac.dll | 0 .../.xamarin/osx-arm64/Microsoft.CSharp.dll | 0 .../osx-arm64/Microsoft.VisualBasic.dll | 0 .../.xamarin/osx-arm64/Microsoft.macOS.dll | 0 .../Contents/MonoBundle/AsyncIO.dll | 0 .../MonoBundle/HeartbeatHandlerLib.dll | 0 .../System.ServiceModel.Security.dll | 0 .../Contents/MonoBundle/libclrgc.dylib | 0 .../macOS/ContentWithPublishFolderType.csproj | 22 +++++++++ .../ContentWithPublishFolderTypeTest.cs | 48 +++++++++++++++++++ 13 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 tests/dotnet/ContentWithPublishFolderType/AppDelegate.cs create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/.gitignore create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/HeartBeatHandlerMac.dll create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/AsyncIO.dll create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/HeartbeatHandlerLib.dll create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/System.ServiceModel.Security.dll create mode 100644 tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/libclrgc.dylib create mode 100644 tests/dotnet/ContentWithPublishFolderType/macOS/ContentWithPublishFolderType.csproj create mode 100644 tests/dotnet/UnitTests/ContentWithPublishFolderTypeTest.cs diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index d9965c1b6db8..2c5f43be44b8 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -481,10 +481,18 @@ Copyright (C) 2018 Microsoft. All rights reserved. - - - - + + + + %(BundleResource.Link) + + + %(Content.Link) + diff --git a/tests/dotnet/ContentWithPublishFolderType/AppDelegate.cs b/tests/dotnet/ContentWithPublishFolderType/AppDelegate.cs new file mode 100644 index 000000000000..b99d6adb454a --- /dev/null +++ b/tests/dotnet/ContentWithPublishFolderType/AppDelegate.cs @@ -0,0 +1,12 @@ +using System; +using Foundation; + +namespace ContentWithPublishFolderType { + public class Program { + static int Main (string [] args) + { + GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + return 0; + } + } +} diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/.gitignore b/tests/dotnet/ContentWithPublishFolderType/helper/.gitignore new file mode 100644 index 000000000000..9a16fc334d87 --- /dev/null +++ b/tests/dotnet/ContentWithPublishFolderType/helper/.gitignore @@ -0,0 +1,2 @@ +# Override root .gitignore to allow test DLL files +!*.dll diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/HeartBeatHandlerMac.dll b/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/HeartBeatHandlerMac.dll new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll b/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll b/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll b/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/AsyncIO.dll b/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/AsyncIO.dll new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/HeartbeatHandlerLib.dll b/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/HeartbeatHandlerLib.dll new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/System.ServiceModel.Security.dll b/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/System.ServiceModel.Security.dll new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/libclrgc.dylib b/tests/dotnet/ContentWithPublishFolderType/helper/SubApp.app/Contents/MonoBundle/libclrgc.dylib new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dotnet/ContentWithPublishFolderType/macOS/ContentWithPublishFolderType.csproj b/tests/dotnet/ContentWithPublishFolderType/macOS/ContentWithPublishFolderType.csproj new file mode 100644 index 000000000000..f72ebb8816e7 --- /dev/null +++ b/tests/dotnet/ContentWithPublishFolderType/macOS/ContentWithPublishFolderType.csproj @@ -0,0 +1,22 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + Exe + ContentWithPublishFolderType + com.xamarin.contentwithpublishfoldertype + + + + + + + + + + + RootDirectory + Contents/SharedSupport/%(RecursiveDir)%(Filename)%(Extension) + + + diff --git a/tests/dotnet/UnitTests/ContentWithPublishFolderTypeTest.cs b/tests/dotnet/UnitTests/ContentWithPublishFolderTypeTest.cs new file mode 100644 index 000000000000..edd961ca624c --- /dev/null +++ b/tests/dotnet/UnitTests/ContentWithPublishFolderTypeTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Xamarin.Tests { + [TestFixture] + public class ContentWithPublishFolderTypeTest : TestBaseClass { + + [Test] + [TestCase (ApplePlatform.MacOSX, "osx-arm64")] + public void ContentFilesWithSdkAssemblyNamesAreIncluded (ApplePlatform platform, string runtimeIdentifiers) + { + // Regression test for https://github.com/dotnet/macios/issues/25497 + // When Content items have PublishFolderType=RootDirectory and filenames matching + // SDK assemblies (e.g., Microsoft.macOS.dll) or runtime files (e.g., libclrgc.dylib), + // they should still be copied to the app bundle. + var project = "ContentWithPublishFolderType"; + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath); + Clean (project_path); + + var properties = GetDefaultProperties (runtimeIdentifiers); + DotNet.AssertBuild (project_path, properties); + + // These are files in our helper directory that have the same names as SDK assemblies / runtime files. + // They should all be present in the app bundle's SharedSupport directory. + var expectedFiles = new string [] { + // Files that match SDK assembly names (these are the ones that go missing per the bug report) + "Contents/SharedSupport/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll", + "Contents/SharedSupport/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll", + "Contents/SharedSupport/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll", + // A runtime dylib that matches a known runtime file name + "Contents/SharedSupport/SubApp.app/Contents/MonoBundle/libclrgc.dylib", + // Files that don't match SDK assembly names (these work fine) + "Contents/SharedSupport/SubApp.app/Contents/MonoBundle/.xamarin/osx-arm64/HeartBeatHandlerMac.dll", + "Contents/SharedSupport/SubApp.app/Contents/MonoBundle/AsyncIO.dll", + "Contents/SharedSupport/SubApp.app/Contents/MonoBundle/HeartbeatHandlerLib.dll", + "Contents/SharedSupport/SubApp.app/Contents/MonoBundle/System.ServiceModel.Security.dll", + }; + + foreach (var expectedFile in expectedFiles) { + var fullPath = Path.Combine (appPath, expectedFile); + Assert.That (fullPath, Does.Exist, $"Expected file '{expectedFile}' to be in the app bundle"); + } + } + } +} From 438194830ca60ce4c4f58d29d76171274969e2dd Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 1 Jun 2026 08:48:43 +0200 Subject: [PATCH 41/97] [msbuild] Improve diagnostics when simulator runtimes aren't available, Fixes #25298 (#25483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple's Xcode tools (actool, ibtool, etc.) require the simulator runtime even when building for physical devices. When the runtime is missing or the wrong version, these tools fail with unhelpful errors or hang indefinitely. ## Changes After a tool failure, run `xcrun simctl --json-output= list runtimes` to check whether the required simulator runtime is installed. If missing, emit a clear error (E7170) with instructions on how to install it. Additionally, detect simulator runtime version mismatch errors in tool output (e.g. `No simulator runtime version from [...] available to use with iphonesimulator SDK version ...`) and emit E7172 suggesting the user update their simulator runtime. The simctl check uses a 1-minute timeout to avoid hanging, and results are cached per platform/SdkDevPath. ## New diagnostics - **E7170**: Simulator runtime not installed (post-failure simctl check) - **W7171**: Unable to determine simulator runtime availability - **E7172**: Tool error indicates incompatible simulator runtime version Fixes https://github.com/dotnet/macios/issues/25298 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Rolf Bjarne Kvinge --- .../MSBStrings.resx | 18 +++ .../Tasks/XcodeCompilerToolTask.cs | 112 +++++++++++++++++- 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index 5f68eea415e1..5de6bb55b800 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1643,4 +1643,22 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/XcodeCompilerToolTask.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/XcodeCompilerToolTask.cs index 419963539f9b..c189dde986e5 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/XcodeCompilerToolTask.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/XcodeCompilerToolTask.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Text; @@ -13,6 +14,7 @@ using Xamarin.Localization.MSBuild; using Xamarin.MacDev; +using Xamarin.MacDev.Models; using Xamarin.Messaging.Build.Client; using Xamarin.Utils; @@ -154,6 +156,92 @@ static bool IsTranslated () return translated.Value; } + // Cache simulator runtime check results to avoid running simctl multiple times. + // Key includes SdkDevPath because different Xcode installations may have different runtimes. + static ConcurrentDictionary simulatorRuntimeCache = new (); + + /// + /// Returns the platform name used by simctl for the current build platform, or null if no simulator is needed. + /// + string? GetSimulatorPlatformName () + { + switch (Platform) { + case ApplePlatform.iOS: + case ApplePlatform.MacCatalyst: + // Mac Catalyst uses the iOS-based toolchain, so it also needs the iOS simulator runtime. + return "iOS"; + case ApplePlatform.TVOS: + return "tvOS"; + case ApplePlatform.MacOSX: + default: + return null; + } + } + + /// + /// Checks if the required simulator runtime is installed and emits a diagnostic if not. + /// Apple's Xcode tools (actool, ibtool, etc.) require the simulator runtime to function, + /// even when building for physical devices. Call this after a tool failure to provide + /// actionable guidance to the user. + /// + void CheckSimulatorRuntimeAvailable () + { + var simPlatform = GetSimulatorPlatformName (); + if (simPlatform is null) + return; + + var cacheKey = $"{simPlatform}:{SdkDevPath}"; + if (simulatorRuntimeCache.TryGetValue (cacheKey, out var cachedResult)) { + if (!cachedResult) + Log.LogError (MSBStrings.E7175, simPlatform); + return; + } + + var jsonOutputFile = Path.GetTempFileName (); + try { + using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource (cancellationTokenSource.Token); + timeoutCts.CancelAfter (TimeSpan.FromMinutes (1)); + var args = new List { + "simctl", + "list", + "runtimes", + "-j", + "--json-output=" + jsonOutputFile + }; + var rv = ExecuteAsync ("xcrun", args, showErrorIfFailure: false, cancellationToken: timeoutCts.Token).Result; + + if (rv.ExitCode != 0) { + Log.LogWarning (MSBStrings.W7176, simPlatform); + return; + } + + var json = File.ReadAllText (jsonOutputFile); + var runtimes = SimctlOutputParser.ParseRuntimes (json); + + var hasRuntime = runtimes.Any (r => + string.Equals (r.Platform, simPlatform, StringComparison.OrdinalIgnoreCase) && r.IsAvailable); + + simulatorRuntimeCache [cacheKey] = hasRuntime; + if (!hasRuntime) + Log.LogError (MSBStrings.E7175, simPlatform); + } catch (OperationCanceledException) when (cancellationTokenSource.IsCancellationRequested) { + // User cancelled - don't emit diagnostics + } catch (AggregateException ae) when (ae.InnerException is OperationCanceledException && cancellationTokenSource.IsCancellationRequested) { + // User cancelled - don't emit diagnostics + } catch (OperationCanceledException) { + // Timeout + Log.LogWarning (MSBStrings.W7176, simPlatform); + } catch (AggregateException ae) when (ae.InnerException is OperationCanceledException) { + // Timeout + Log.LogWarning (MSBStrings.W7176, simPlatform); + } catch (Exception ex) { + Log.LogWarning (MSBStrings.W7176, simPlatform); + Log.LogMessage (MessageImportance.Low, "Exception while checking simulator runtime: {0}", ex.Message); + } finally { + File.Delete (jsonOutputFile); + } + } + protected int Compile (ITaskItem [] items, string output, ITaskItem manifest) { var environment = new Dictionary (); @@ -193,7 +281,7 @@ protected int Compile (ITaskItem [] items, string output, ITaskItem manifest) if (Log.HasLoggedErrors) return 1; - var rv = ExecuteAsync (executable, args, environment: environment, cancellationToken: cancellationTokenSource.Token).Result; + var rv = ExecuteAsync (executable, args, showErrorIfFailure: true, environment: environment, cancellationToken: cancellationTokenSource.Token).Result; var exitCode = rv.ExitCode; var messages = rv.Output.StandardOutput; File.WriteAllText (manifest.ItemSpec, messages); @@ -206,8 +294,6 @@ protected int Compile (ITaskItem [] items, string output, ITaskItem manifest) if (errors.Length > 0) Log.LogError (null, null, null, items [0].ItemSpec, 0, 0, 0, 0, "{0}", errors); - Log.LogError (MSBStrings.E0117, ToolName, exitCode); - // Note: If the log file exists and is parseable, log those warnings/errors as well... if (File.Exists (manifest.ItemSpec)) { try { @@ -220,6 +306,9 @@ protected int Compile (ITaskItem [] items, string output, ITaskItem manifest) File.Delete (manifest.ItemSpec); } + + // Check if the failure might be caused by a missing simulator runtime. + CheckSimulatorRuntimeAvailable (); } return exitCode; @@ -285,8 +374,14 @@ protected void LogWarningsAndErrors (PDictionary plist, ITaskItem file) if (plist.TryGetValue (string.Format ("com.apple.{0}.errors", ToolName), out array)) { foreach (var item in array.OfType ()) { - if (item.TryGetValue ("description", out message)) + if (item.TryGetValue ("description", out message)) { Log.LogError (ToolName, null, null, file.ItemSpec, 0, 0, 0, 0, "{0}", message.Value); + if (IsSimulatorRuntimeVersionError (message.Value)) { + var simPlatform = GetSimulatorPlatformName (); + if (simPlatform is not null) + Log.LogError (MSBStrings.E7177, simPlatform); + } + } } } @@ -298,6 +393,15 @@ protected void LogWarningsAndErrors (PDictionary plist, ITaskItem file) } } + /// + /// Detects error messages like "No simulator runtime version from [...] available to use with ... SDK version ..." + /// which indicate an incompatible or missing simulator runtime version. + /// + static bool IsSimulatorRuntimeVersionError (string message) + { + return message.IndexOf ("simulator runtime", StringComparison.OrdinalIgnoreCase) >= 0; + } + public void Cancel () { if (ShouldExecuteRemotely ()) { From 67b8de6a9371181faba167eabc141e4c3a60061e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 1 Jun 2026 08:49:30 +0200 Subject: [PATCH 42/97] Add support for Composer Icons (#24722) ## Add support for Icon Composer (.icon) app icons This adds support for Xcode Icon Composer based `.icon` folders introduced in macOS 26 Tahoe's Liquid Glass design system (see #24132). ### Changes - **Recognize `.icon` folders**: Treats `.icon` directories as asset catalogs alongside `.xcassets` - **"Process"/Detect `icon.json` files**: Handles `icon.json` metadata files in addition to `Contents.json` - **Enable `--app-icon` flag**: `.icon`-based icons now pass validation and get the `--app-icon` flag passed to `actool` - **Support alternate icons**: Works with `IncludeAllAppIcons` for runtime icon switching ### Background The new `.icon` format is a folder structure containing: - `icon.json` - Icon metadata (layers, materials, effects) - `Assets/` subfolder - Vector graphics and image assets This replaces static `.icns` files to support Liquid Glass features like translucency, specular lighting, and cross-platform rendering. ### Usage Add `.icon` folders to your project: ```xml AppIcon true ``` The build system will: 1. Recognize the `.icon` folder as a valid app icon 2. Pass it to `actool` with the `--app-icon AppIcon` flag 3. Compile it into `Assets.car` --- This is a recreation of #24476 (from @paulober), because our CI can't work with pull requests from forks. Fixes https://github.com/dotnet/macios/issues/24132. --------- Co-authored-by: paulober <44974737+paulober@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Rolf Bjarne Kvinge --- docs/building-apps/build-items.md | 3 +- dotnet/DefaultCompilationIncludes.md | 9 +- .../Microsoft.Sdk.DefaultItems.template.props | 3 +- msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs | 52 +++-- .../dotnet/AppWithComposerIcon/AppDelegate.cs | 35 +++ .../MacCatalyst/AppWithComposerIcon.csproj | 7 + .../AppWithComposerIcon/MacCatalyst/Makefile | 1 + .../Resources/AppIcon.icon/Assets/back.png | Bin 0 -> 5705 bytes .../Resources/AppIcon.icon/Assets/front.png | Bin 0 -> 5705 bytes .../Resources/AppIcon.icon/icon.json | 16 ++ .../iOS/AppWithComposerIcon.csproj | 7 + tests/dotnet/AppWithComposerIcon/iOS/Makefile | 1 + .../Resources/AppIcon.icon/Assets/back.png | Bin 0 -> 5705 bytes .../Resources/AppIcon.icon/Assets/front.png | Bin 0 -> 5705 bytes .../iOS/Resources/AppIcon.icon/icon.json | 16 ++ .../macOS/AppWithComposerIcon.csproj | 7 + .../dotnet/AppWithComposerIcon/macOS/Makefile | 1 + .../Resources/AppIcon.icon/Assets/back.png | Bin 0 -> 5705 bytes .../Resources/AppIcon.icon/Assets/front.png | Bin 0 -> 5705 bytes .../macOS/Resources/AppIcon.icon/icon.json | 16 ++ .../dotnet/AppWithComposerIcon/shared.csproj | 23 ++ tests/dotnet/AppWithComposerIcon/shared.mk | 3 + .../tvOS/AppWithComposerIcon.csproj | 7 + .../dotnet/AppWithComposerIcon/tvOS/Makefile | 1 + .../Resources/AppIcon.icon/Assets/back.png | Bin 0 -> 5705 bytes .../Resources/AppIcon.icon/Assets/front.png | Bin 0 -> 5705 bytes .../tvOS/Resources/AppIcon.icon/icon.json | 16 ++ tests/dotnet/UnitTests/AppIconTest.cs | 34 +++ .../TaskTests/ACToolTaskTest.cs | 210 ++++++++++++++++++ 29 files changed, 442 insertions(+), 26 deletions(-) create mode 100644 tests/dotnet/AppWithComposerIcon/AppDelegate.cs create mode 100644 tests/dotnet/AppWithComposerIcon/MacCatalyst/AppWithComposerIcon.csproj create mode 100644 tests/dotnet/AppWithComposerIcon/MacCatalyst/Makefile create mode 100644 tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/Assets/back.png create mode 100644 tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/Assets/front.png create mode 100644 tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/icon.json create mode 100644 tests/dotnet/AppWithComposerIcon/iOS/AppWithComposerIcon.csproj create mode 100644 tests/dotnet/AppWithComposerIcon/iOS/Makefile create mode 100644 tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/Assets/back.png create mode 100644 tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/Assets/front.png create mode 100644 tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/icon.json create mode 100644 tests/dotnet/AppWithComposerIcon/macOS/AppWithComposerIcon.csproj create mode 100644 tests/dotnet/AppWithComposerIcon/macOS/Makefile create mode 100644 tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/Assets/back.png create mode 100644 tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/Assets/front.png create mode 100644 tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/icon.json create mode 100644 tests/dotnet/AppWithComposerIcon/shared.csproj create mode 100644 tests/dotnet/AppWithComposerIcon/shared.mk create mode 100644 tests/dotnet/AppWithComposerIcon/tvOS/AppWithComposerIcon.csproj create mode 100644 tests/dotnet/AppWithComposerIcon/tvOS/Makefile create mode 100644 tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/Assets/back.png create mode 100644 tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/Assets/front.png create mode 100644 tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/icon.json diff --git a/docs/building-apps/build-items.md b/docs/building-apps/build-items.md index ff18c071b9e6..2fd81202cf18 100644 --- a/docs/building-apps/build-items.md +++ b/docs/building-apps/build-items.md @@ -186,7 +186,8 @@ Only applicable to iOS and tvOS projects. ## ImageAsset -An item group that contains image assets. +An item group that contains image assets, including files inside asset catalogs +(\*.xcassets) and Icon Composer directories (\*.icon). ## InterfaceDefinition diff --git a/dotnet/DefaultCompilationIncludes.md b/dotnet/DefaultCompilationIncludes.md index 3dec5cdb0a5f..9f14799e6634 100644 --- a/dotnet/DefaultCompilationIncludes.md +++ b/dotnet/DefaultCompilationIncludes.md @@ -33,6 +33,13 @@ All \*.pdf, \*.jpg, \*.png and \*.json files inside asset catalogs (\*.xcassets) in the project directory or any subdirectory are included by default (as `ImageAsset` items). +## Icon Composer files + +All files inside Icon Composer directories (\*.icon) in the project directory +or any subdirectory are included by default (as `ImageAsset` items). Icon +Composer files are created by Xcode's Icon Composer tool (Xcode 26+) and +contain layered app icons with `icon.json` metadata. + ## Atlas Textures All \*.png files inside \*.atlas directories in the project directory or any @@ -52,7 +59,7 @@ included by default (as `Metal` items). All files in the Resources/ subdirectory, except any items in the `Compile` or `EmbeddedResource` item groups, and except the ones mentioned above -(\*.scnassets, \*.storyboard, \*.xib, \*.xcassets, \*.atlas, \*.mlmodel, +(\*.scnassets, \*.storyboard, \*.xib, \*.xcassets, \*.icon, \*.atlas, \*.mlmodel, \*.metal) are included by default (as `BundleResource` items). [1]: https://docs.microsoft.com/en-us/dotnet/core/tools/csproj#default-compilation-includes-in-net-core-projects diff --git a/dotnet/targets/Microsoft.Sdk.DefaultItems.template.props b/dotnet/targets/Microsoft.Sdk.DefaultItems.template.props index d705ae3f56c2..afc08fe18531 100644 --- a/dotnet/targets/Microsoft.Sdk.DefaultItems.template.props +++ b/dotnet/targets/Microsoft.Sdk.DefaultItems.template.props @@ -24,7 +24,7 @@ - + $([MSBuild]::MakeRelative ('$(MSBuildProjectDirectory)', '%(FullPath)')) false true @@ -65,6 +65,7 @@ $(DefaultItemExcludes); $(DefaultExcludesInProjectFolder); $(_ResourcePrefix)\**\*.xcassets\**\*.*; + $(_ResourcePrefix)\**\*.icon\**\*.*; $(_ResourcePrefix)\**\*.storyboard;**\*.xib; $(_ResourcePrefix)\**\*.atlas\*.png; $(_ResourcePrefix)\**\*.mlmodel; diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs index fbdd82f46eb6..829b904260e9 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs @@ -248,12 +248,12 @@ IEnumerable GetCompiledBundleResources (PDictionary output, string in yield break; } - void FindXCAssetsDirectory (string main, string secondary, out string mainResult, out string secondaryResult) + void FindAssetCatalogDirectory (string main, string secondary, out string mainResult, out string secondaryResult) { mainResult = main; secondaryResult = secondary; - while (!string.IsNullOrEmpty (mainResult) && !mainResult.EndsWith (".xcassets", StringComparison.OrdinalIgnoreCase)) { + while (!string.IsNullOrEmpty (mainResult) && !mainResult.EndsWith (".xcassets", StringComparison.OrdinalIgnoreCase) && !mainResult.EndsWith (".icon", StringComparison.OrdinalIgnoreCase)) { mainResult = Path.GetDirectoryName (mainResult)!; if (!string.IsNullOrEmpty (secondaryResult)) secondaryResult = Path.GetDirectoryName (secondaryResult)!; @@ -292,14 +292,14 @@ public override bool Execute () var vpath = BundleResource.GetVirtualProjectPath (this, imageAsset); var catalogFullPath = imageAsset.GetMetadata ("FullPath"); - // get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc) + // get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, .icon, etc) var catalog = Path.GetDirectoryName (vpath)!; catalogFullPath = Path.GetDirectoryName (catalogFullPath)!; var assetType = Path.GetExtension (catalog).TrimStart ('.'); - // keep walking up the directory structure until we get to the .xcassets directory - FindXCAssetsDirectory (catalog, catalogFullPath, out var catalog2, out var catalogFullPath2); + // keep walking up the directory structure until we get to the .xcassets or .icon directory + FindAssetCatalogDirectory (catalog, catalogFullPath, out var catalog2, out var catalogFullPath2); catalog = catalog2; catalogFullPath = catalogFullPath2; @@ -325,11 +325,11 @@ public override bool Execute () continue; } - // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these - if (Path.GetFileName (vpath) != "Contents.json") - continue; - - items.Add (asset); + // Handle both Contents.json (for .xcassets) and icon.json (for .icon folders) + var fileName = Path.GetFileName (vpath); + if (fileName == "Contents.json" || fileName == "icon.json") { + items.Add (asset); + } } // clone any *.xcassets dirs that need cloning @@ -370,18 +370,20 @@ public override bool Execute () File.Copy (src, dest, true); - // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these - if (Path.GetFileName (vpath) != "Contents.json") + // Handle both Contents.json (for .xcassets) and icon.json (for .icon folders) + var fileName = Path.GetFileName (vpath); + if (fileName != "Contents.json" && fileName != "icon.json") continue; item = new TaskItem (dest); assetItem.CopyMetadataTo (item); item.SetMetadata ("Link", vpath); - FindXCAssetsDirectory (Path.GetFullPath (dest), "", out var catalogFullPath, out var _); + FindAssetCatalogDirectory (Path.GetFullPath (dest), "", out var catalogFullPath, out var _); items.Add (new AssetInfo (item, vpath, asset.Catalog, catalogFullPath, asset.AssetType)); } else { - // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these - if (Path.GetFileName (vpath) != "Contents.json") + // Handle both Contents.json (for .xcassets) and icon.json (for .icon folders) + var fileName = Path.GetFileName (vpath); + if (fileName != "Contents.json" && fileName != "icon.json") continue; items.Add (asset); @@ -389,7 +391,7 @@ public override bool Execute () } } - // Note: `items` contains only the Contents.json files at this point + // Note: `items` contains only the Contents.json and icon.json files at this point for (int i = 0; i < items.Count; i++) { var asset = items [i]; var assetItem = asset.Item; @@ -397,16 +399,19 @@ public override bool Execute () var catalog = asset.Catalog; var path = assetItem.GetMetadata ("FullPath"); var assetType = asset.AssetType; + var vpathDirNameWithoutExtension = Path.GetFileNameWithoutExtension (Path.GetDirectoryName (vpath)!); if (Platform == ApplePlatform.TVOS) { - if (assetType.Equals ("imagestack", StringComparison.OrdinalIgnoreCase)) { - imageStacksInAssets.Add (Path.GetFileNameWithoutExtension (Path.GetDirectoryName (vpath)!)); - } else if (assetType.Equals ("brandassets", StringComparison.OrdinalIgnoreCase)) { - brandAssetsInAssets.Add (Path.GetFileNameWithoutExtension (Path.GetDirectoryName (vpath)!)); + if (assetType.Equals ("imagestack", StringComparison.OrdinalIgnoreCase) || assetType.Equals ("icon", StringComparison.OrdinalIgnoreCase)) { + imageStacksInAssets.Add (vpathDirNameWithoutExtension); + } + if (assetType.Equals ("brandassets", StringComparison.OrdinalIgnoreCase) || assetType.Equals ("icon", StringComparison.OrdinalIgnoreCase)) { + brandAssetsInAssets.Add (vpathDirNameWithoutExtension); } } else { - if (assetType.Equals ("appiconset", StringComparison.OrdinalIgnoreCase)) - appIconsInAssets.Add (Path.GetFileNameWithoutExtension (Path.GetDirectoryName (vpath)!)); + if (assetType.Equals ("appiconset", StringComparison.OrdinalIgnoreCase) || assetType.Equals ("icon", StringComparison.OrdinalIgnoreCase)) { + appIconsInAssets.Add (vpathDirNameWithoutExtension); + } } if (unique.Add (catalog)) { @@ -416,7 +421,8 @@ public override bool Execute () catalogs.Add (item); } - if (SdkPlatform != "WatchSimulator") { + // Only process Contents.json files for on-demand resources (not icon.json files) + if (SdkPlatform != "WatchSimulator" && Path.GetFileName (vpath) == "Contents.json") { var text = File.ReadAllText (assetItem.ItemSpec); if (string.IsNullOrEmpty (text)) diff --git a/tests/dotnet/AppWithComposerIcon/AppDelegate.cs b/tests/dotnet/AppWithComposerIcon/AppDelegate.cs new file mode 100644 index 000000000000..13cf28d17265 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/AppDelegate.cs @@ -0,0 +1,35 @@ +using System; +using Foundation; + +#if !__MACOS__ +using UIKit; +#endif + +#nullable enable + +namespace AppWithComposerIcon { +#if !(__MACCATALYST__ || __MACOS__) + public class AppDelegate : UIApplicationDelegate { + public override bool FinishedLaunching (UIApplication app, NSDictionary? options) + { + return true; + } + } +#endif + + public class Program { + static int Main (string [] args) + { +#if __MACCATALYST__ || __MACOS__ +GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + +Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); + +return args.Length; +#else + UIApplication.Main (args, null, typeof (AppDelegate)); + return 0; +#endif + } + } +} diff --git a/tests/dotnet/AppWithComposerIcon/MacCatalyst/AppWithComposerIcon.csproj b/tests/dotnet/AppWithComposerIcon/MacCatalyst/AppWithComposerIcon.csproj new file mode 100644 index 000000000000..6b0e2c773180 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/MacCatalyst/AppWithComposerIcon.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst + + + diff --git a/tests/dotnet/AppWithComposerIcon/MacCatalyst/Makefile b/tests/dotnet/AppWithComposerIcon/MacCatalyst/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/MacCatalyst/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/Assets/back.png b/tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/Assets/back.png new file mode 100644 index 0000000000000000000000000000000000000000..00c6bc6118f0ee916acf51845aec89a4b11970e4 GIT binary patch literal 5705 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGXsN|fTxRNNX4zUHyi~)QjRlI zgu=ABJQBWbWPMk8aVj%V1VLs3NEHLf7KQ{6fsaX?i4ZH<8aRN4 zC4fxCXC#sWl1v8~i=-D1vw=~838A`K@c zFu-bQWjG080r46sE+@foq;MpXNh8Ba323ldBArXqSfr`pBn0%JNg@dh9SlG~g41ai zjiVI;C@PTRqJdEXRJRU-k|Y7>3}lxfITAaQcBusDj=^9!2{{H5LW3X{A>l@X%V`zR z382(OE5k_%=)qDSGz_Aq-)Mz^-YH>g5CEpv1Ry513?wBD(kMTUW_I*w94wiAv_8O^ zIz2jNnIT<)V$2mi~mG|00P0prw_C zGfA@~OKzuFm|_Dr87{LgbMMXW?B3JK@@SOrrfu*ptFv*mAtAdpTDGfU_D@nH- zu1Y8m4d8+q(=en!#fCRQ=Anp2uBK4n5~8*A1EXl`7aKzRP?DN4)hp7+cMX)(V4(RJ zCwp5)Et5U=182!==m+t?fX;4H(+Oct3$ThZ;ZA^T+`He-x3M3z$kL!<><27LM+xWR e+^J}Ej&;}h!)5t8$NPUks2cA8 literal 0 HcmV?d00001 diff --git a/tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/icon.json b/tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/icon.json new file mode 100644 index 000000000000..9cb9eee03bd1 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/icon.json @@ -0,0 +1,16 @@ +{ + "groups" : [ + { + "layers" : [ + { + "image-name" : "back.png", + "name" : "back" + }, + { + "image-name" : "front.png", + "name" : "front" + } + ] + } + ] +} diff --git a/tests/dotnet/AppWithComposerIcon/iOS/AppWithComposerIcon.csproj b/tests/dotnet/AppWithComposerIcon/iOS/AppWithComposerIcon.csproj new file mode 100644 index 000000000000..86d408734aa8 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/iOS/AppWithComposerIcon.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/AppWithComposerIcon/iOS/Makefile b/tests/dotnet/AppWithComposerIcon/iOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/Assets/back.png b/tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/Assets/back.png new file mode 100644 index 0000000000000000000000000000000000000000..00c6bc6118f0ee916acf51845aec89a4b11970e4 GIT binary patch literal 5705 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGXsN|fTxRNNX4zUHyi~)QjRlI zgu=ABJQBWbWPMk8aVj%V1VLs3NEHLf7KQ{6fsaX?i4ZH<8aRN4 zC4fxCXC#sWl1v8~i=-D1vw=~838A`K@c zFu-bQWjG080r46sE+@foq;MpXNh8Ba323ldBArXqSfr`pBn0%JNg@dh9SlG~g41ai zjiVI;C@PTRqJdEXRJRU-k|Y7>3}lxfITAaQcBusDj=^9!2{{H5LW3X{A>l@X%V`zR z382(OE5k_%=)qDSGz_Aq-)Mz^-YH>g5CEpv1Ry513?wBD(kMTUW_I*w94wiAv_8O^ zIz2jNnIT<)V$2mi~mG|00P0prw_C zGfA@~OKzuFm|_Dr87{LgbMMXW?B3JK@@SOrrfu*ptFv*mAtAdpTDGfU_D@nH- zu1Y8m4d8+q(=en!#fCRQ=Anp2uBK4n5~8*A1EXl`7aKzRP?DN4)hp7+cMX)(V4(RJ zCwp5)Et5U=182!==m+t?fX;4H(+Oct3$ThZ;ZA^T+`He-x3M3z$kL!<><27LM+xWR e+^J}Ej&;}h!)5t8$NPUks2cA8 literal 0 HcmV?d00001 diff --git a/tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/icon.json b/tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/icon.json new file mode 100644 index 000000000000..9cb9eee03bd1 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/icon.json @@ -0,0 +1,16 @@ +{ + "groups" : [ + { + "layers" : [ + { + "image-name" : "back.png", + "name" : "back" + }, + { + "image-name" : "front.png", + "name" : "front" + } + ] + } + ] +} diff --git a/tests/dotnet/AppWithComposerIcon/macOS/AppWithComposerIcon.csproj b/tests/dotnet/AppWithComposerIcon/macOS/AppWithComposerIcon.csproj new file mode 100644 index 000000000000..a77287b9ba00 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/macOS/AppWithComposerIcon.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + diff --git a/tests/dotnet/AppWithComposerIcon/macOS/Makefile b/tests/dotnet/AppWithComposerIcon/macOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/Assets/back.png b/tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/Assets/back.png new file mode 100644 index 0000000000000000000000000000000000000000..00c6bc6118f0ee916acf51845aec89a4b11970e4 GIT binary patch literal 5705 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGXsN|fTxRNNX4zUHyi~)QjRlI zgu=ABJQBWbWPMk8aVj%V1VLs3NEHLf7KQ{6fsaX?i4ZH<8aRN4 zC4fxCXC#sWl1v8~i=-D1vw=~838A`K@c zFu-bQWjG080r46sE+@foq;MpXNh8Ba323ldBArXqSfr`pBn0%JNg@dh9SlG~g41ai zjiVI;C@PTRqJdEXRJRU-k|Y7>3}lxfITAaQcBusDj=^9!2{{H5LW3X{A>l@X%V`zR z382(OE5k_%=)qDSGz_Aq-)Mz^-YH>g5CEpv1Ry513?wBD(kMTUW_I*w94wiAv_8O^ zIz2jNnIT<)V$2mi~mG|00P0prw_C zGfA@~OKzuFm|_Dr87{LgbMMXW?B3JK@@SOrrfu*ptFv*mAtAdpTDGfU_D@nH- zu1Y8m4d8+q(=en!#fCRQ=Anp2uBK4n5~8*A1EXl`7aKzRP?DN4)hp7+cMX)(V4(RJ zCwp5)Et5U=182!==m+t?fX;4H(+Oct3$ThZ;ZA^T+`He-x3M3z$kL!<><27LM+xWR e+^J}Ej&;}h!)5t8$NPUks2cA8 literal 0 HcmV?d00001 diff --git a/tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/icon.json b/tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/icon.json new file mode 100644 index 000000000000..9cb9eee03bd1 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/icon.json @@ -0,0 +1,16 @@ +{ + "groups" : [ + { + "layers" : [ + { + "image-name" : "back.png", + "name" : "back" + }, + { + "image-name" : "front.png", + "name" : "front" + } + ] + } + ] +} diff --git a/tests/dotnet/AppWithComposerIcon/shared.csproj b/tests/dotnet/AppWithComposerIcon/shared.csproj new file mode 100644 index 000000000000..702960b9dd8e --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/shared.csproj @@ -0,0 +1,23 @@ + + + + Exe + + AppWithComposerIcon + com.xamarin.appwithcomposericon + + true + + + + + + AppIcon + + + + + + + + diff --git a/tests/dotnet/AppWithComposerIcon/shared.mk b/tests/dotnet/AppWithComposerIcon/shared.mk new file mode 100644 index 000000000000..de0244100278 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/shared.mk @@ -0,0 +1,3 @@ +TOP=../../../.. +TESTNAME=AppWithComposerIcon +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/AppWithComposerIcon/tvOS/AppWithComposerIcon.csproj b/tests/dotnet/AppWithComposerIcon/tvOS/AppWithComposerIcon.csproj new file mode 100644 index 000000000000..bd487ddcd88d --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/tvOS/AppWithComposerIcon.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-tvos + + + diff --git a/tests/dotnet/AppWithComposerIcon/tvOS/Makefile b/tests/dotnet/AppWithComposerIcon/tvOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/tvOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/Assets/back.png b/tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/Assets/back.png new file mode 100644 index 0000000000000000000000000000000000000000..00c6bc6118f0ee916acf51845aec89a4b11970e4 GIT binary patch literal 5705 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGXsN|fTxRNNX4zUHyi~)QjRlI zgu=ABJQBWbWPMk8aVj%V1VLs3NEHLf7KQ{6fsaX?i4ZH<8aRN4 zC4fxCXC#sWl1v8~i=-D1vw=~838A`K@c zFu-bQWjG080r46sE+@foq;MpXNh8Ba323ldBArXqSfr`pBn0%JNg@dh9SlG~g41ai zjiVI;C@PTRqJdEXRJRU-k|Y7>3}lxfITAaQcBusDj=^9!2{{H5LW3X{A>l@X%V`zR z382(OE5k_%=)qDSGz_Aq-)Mz^-YH>g5CEpv1Ry513?wBD(kMTUW_I*w94wiAv_8O^ zIz2jNnIT<)V$2mi~mG|00P0prw_C zGfA@~OKzuFm|_Dr87{LgbMMXW?B3JK@@SOrrfu*ptFv*mAtAdpTDGfU_D@nH- zu1Y8m4d8+q(=en!#fCRQ=Anp2uBK4n5~8*A1EXl`7aKzRP?DN4)hp7+cMX)(V4(RJ zCwp5)Et5U=182!==m+t?fX;4H(+Oct3$ThZ;ZA^T+`He-x3M3z$kL!<><27LM+xWR e+^J}Ej&;}h!)5t8$NPUks2cA8 literal 0 HcmV?d00001 diff --git a/tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/icon.json b/tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/icon.json new file mode 100644 index 000000000000..9cb9eee03bd1 --- /dev/null +++ b/tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/icon.json @@ -0,0 +1,16 @@ +{ + "groups" : [ + { + "layers" : [ + { + "image-name" : "back.png", + "name" : "back" + }, + { + "image-name" : "front.png", + "name" : "front" + } + ] + } + ] +} diff --git a/tests/dotnet/UnitTests/AppIconTest.cs b/tests/dotnet/UnitTests/AppIconTest.cs index 970676172b53..a5eb8dc0d9c3 100644 --- a/tests/dotnet/UnitTests/AppIconTest.cs +++ b/tests/dotnet/UnitTests/AppIconTest.cs @@ -672,5 +672,39 @@ void TestXCAssetsImpl (ApplePlatform platform, string runtimeIdentifiers, Dictio throw; } } + + [TestCase (ApplePlatform.iOS, "iossimulator-arm64")] + [TestCase (ApplePlatform.TVOS, "tvossimulator-arm64")] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64")] + [TestCase (ApplePlatform.MacOSX, "osx-arm64")] + public void ComposerIcon (ApplePlatform platform, string runtimeIdentifiers) + { + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + Configuration.IgnoreIfIgnoredPlatform (platform); + + var project = "AppWithComposerIcon"; + var projectPath = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath); + Clean (projectPath); + + var properties = GetDefaultProperties (runtimeIdentifiers); + DotNet.Execute ("build", projectPath, properties); + + var resourcesDirectory = GetResourcesDirectory (platform, appPath); + + // Verify that the raw .icon files are not in the app bundle as BundleResources + var iconJsonInBundle = Path.Combine (resourcesDirectory, "AppIcon.icon", "icon.json"); + Assert.That (iconJsonInBundle, Does.Not.Exist, "icon.json should not be in the app bundle as a raw BundleResource"); + + var frontPngInBundle = Path.Combine (resourcesDirectory, "AppIcon.icon", "Assets", "front.png"); + Assert.That (frontPngInBundle, Does.Not.Exist, "front.png should not be in the app bundle as a raw BundleResource"); + + var backPngInBundle = Path.Combine (resourcesDirectory, "AppIcon.icon", "Assets", "back.png"); + Assert.That (backPngInBundle, Does.Not.Exist, "back.png should not be in the app bundle as a raw BundleResource"); + + // Verify that the compiled asset catalog exists in the app bundle + var assetsCar = Path.Combine (resourcesDirectory, "Assets.car"); + Assert.That (assetsCar, Does.Exist, "Assets.car"); + } } } + diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs index e41ff90d614e..218b3cf4fa9e 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs @@ -633,5 +633,215 @@ public void XSAppIconAssetsAndAppIcon (ApplePlatform platform) ExecuteTask (actool, 1); Assert.That (Engine.Logger.ErrorEvents [0].Message, Is.EqualTo ("Can't specify both 'XSAppIconAssets' in the Info.plist and 'AppIcon' in the project file. Please select one or the other."), "Error message"); } + + [Test] + [TestCase (ApplePlatform.iOS)] + [TestCase (ApplePlatform.TVOS)] + [TestCase (ApplePlatform.MacCatalyst)] + [TestCase (ApplePlatform.MacOSX)] + public void IconFileSupport (ApplePlatform platform) + { + // Test that .icon folders (Icon Composer format) are recognized as app icons + var projectDir = Cache.CreateTemporaryDirectory (); + var iconFolderPath = Path.Combine (projectDir, "Resources", "AppIcon.icon"); + var assetsPath = Path.Combine (iconFolderPath, "Assets"); + Directory.CreateDirectory (assetsPath); + + // Create a placeholder icon.json file (simplified structure for testing) + var iconJsonPath = Path.Combine (iconFolderPath, "icon.json"); + File.WriteAllText (iconJsonPath, @"{""groups"":[{""layers"":[{""image-name"":""icon_512x512.png"",""name"":""icon""}]}]}"); + + // Create a placeholder image file in the Assets folder + var imagePath = Path.Combine (assetsPath, "icon_512x512.png"); + File.WriteAllText (imagePath, "placeholder image"); + + var actool = CreateACToolTask ( + platform, + projectDir, + out var _, + iconJsonPath + "|Resources/AppIcon.icon/icon.json", + imagePath + "|Resources/AppIcon.icon/Assets/icon_512x512.png" + ); + actool.AppIcon = "AppIcon"; + + // actool may fail on the placeholder .icon content, but the validation phase should pass + actool.Execute (); + + // Verify that no icon validation errors were logged + AssertNoIconValidationErrors (); + } + + [Test] + [TestCase (ApplePlatform.iOS)] + [TestCase (ApplePlatform.TVOS)] + [TestCase (ApplePlatform.MacCatalyst)] + [TestCase (ApplePlatform.MacOSX)] + public void IconFileSupportWithIncludeAllAppIcons (ApplePlatform platform) + { + // Test that .icon folders work with IncludeAllAppIcons + var projectDir = Cache.CreateTemporaryDirectory (); + var iconFolderPath = Path.Combine (projectDir, "Resources", "AppIcon.icon"); + var assetsPath = Path.Combine (iconFolderPath, "Assets"); + Directory.CreateDirectory (assetsPath); + + var iconJsonPath = Path.Combine (iconFolderPath, "icon.json"); + File.WriteAllText (iconJsonPath, @"{""groups"":[{""layers"":[{""image-name"":""icon_512x512.png"",""name"":""icon""}]}]}"); + + var imagePath = Path.Combine (assetsPath, "icon_512x512.png"); + File.WriteAllText (imagePath, "placeholder image"); + + var actool = CreateACToolTask ( + platform, + projectDir, + out var _, + iconJsonPath + "|Resources/AppIcon.icon/icon.json", + imagePath + "|Resources/AppIcon.icon/Assets/icon_512x512.png" + ); + actool.AppIcon = "AppIcon"; + actool.IncludeAllAppIcons = true; + + // actool may fail on the placeholder .icon content, but the validation phase should pass + actool.Execute (); + + // Verify that no icon validation errors were logged + AssertNoIconValidationErrors (); + } + + [Test] + [TestCase (ApplePlatform.iOS)] + [TestCase (ApplePlatform.TVOS)] + [TestCase (ApplePlatform.MacCatalyst)] + [TestCase (ApplePlatform.MacOSX)] + public void IconFileSupportAsAlternateIcon (ApplePlatform platform) + { + // Test that .icon folders work as alternate app icons + var projectDir = Cache.CreateTemporaryDirectory (); + var iconFolderPath = Path.Combine (projectDir, "Resources", "AlternateIcon.icon"); + var assetsPath = Path.Combine (iconFolderPath, "Assets"); + Directory.CreateDirectory (assetsPath); + + var iconJsonPath = Path.Combine (iconFolderPath, "icon.json"); + File.WriteAllText (iconJsonPath, @"{""groups"":[{""layers"":[{""image-name"":""icon_512x512.png"",""name"":""icon""}]}]}"); + + var imagePath = Path.Combine (assetsPath, "icon_512x512.png"); + File.WriteAllText (imagePath, "placeholder image"); + + // Also need a primary icon for the alternate icon test to make sense + var primaryIconPath = Path.Combine (projectDir, "Resources", "AppIcon.icon"); + var primaryAssetsPath = Path.Combine (primaryIconPath, "Assets"); + Directory.CreateDirectory (primaryAssetsPath); + + var primaryIconJsonPath = Path.Combine (primaryIconPath, "icon.json"); + File.WriteAllText (primaryIconJsonPath, @"{""groups"":[{""layers"":[{""image-name"":""icon_512x512.png"",""name"":""icon""}]}]}"); + + var primaryImagePath = Path.Combine (primaryAssetsPath, "icon_512x512.png"); + File.WriteAllText (primaryImagePath, "placeholder image"); + + var actool = CreateACToolTask ( + platform, + projectDir, + out var _, + iconJsonPath + "|Resources/AlternateIcon.icon/icon.json", + imagePath + "|Resources/AlternateIcon.icon/Assets/icon_512x512.png", + primaryIconJsonPath + "|Resources/AppIcon.icon/icon.json", + primaryImagePath + "|Resources/AppIcon.icon/Assets/icon_512x512.png" + ); + actool.AppIcon = "AppIcon"; + actool.AlternateAppIcons = new ITaskItem [] { new TaskItem ("AlternateIcon") }; + + // actool may fail on the placeholder .icon content, but the validation phase should pass + actool.Execute (); + + // Verify that no icon validation errors were logged + AssertNoIconValidationErrors (); + } + + [Test] + [TestCase (ApplePlatform.iOS)] + [TestCase (ApplePlatform.TVOS)] + [TestCase (ApplePlatform.MacCatalyst)] + [TestCase (ApplePlatform.MacOSX)] + public void InexistentIconFile (ApplePlatform platform) + { + // Test that an inexistent .icon-based app icon is correctly reported + var projectDir = Cache.CreateTemporaryDirectory (); + var iconFolderPath = Path.Combine (projectDir, "Resources", "AppIcon.icon"); + var assetsPath = Path.Combine (iconFolderPath, "Assets"); + Directory.CreateDirectory (assetsPath); + + var iconJsonPath = Path.Combine (iconFolderPath, "icon.json"); + File.WriteAllText (iconJsonPath, @"{""groups"":[{""layers"":[{""image-name"":""icon_512x512.png"",""name"":""icon""}]}]}"); + + var imagePath = Path.Combine (assetsPath, "icon_512x512.png"); + File.WriteAllText (imagePath, "placeholder image"); + + var actool = CreateACToolTask ( + platform, + projectDir, + out var _, + iconJsonPath + "|Resources/AppIcon.icon/icon.json", + imagePath + "|Resources/AppIcon.icon/Assets/icon_512x512.png" + ); + actool.AppIcon = "InexistentIcon"; + + ExecuteTask (actool, 1); + + var errorMessages = Engine.Logger.ErrorEvents.Select (e => e.Message).ToList (); + Assert.That (errorMessages.Any (m => m?.Contains ("Can't find the AppIcon 'InexistentIcon'") == true), Is.True, "Should report that InexistentIcon is not found among image resources"); + } + + [Test] + [TestCase (ApplePlatform.iOS)] + [TestCase (ApplePlatform.MacCatalyst)] + [TestCase (ApplePlatform.MacOSX)] + public void MixedXCAssetsAndIconFile (ApplePlatform platform) + { + // Test that .icon folders and .xcassets can coexist in the validation phase + var projectDir = Path.Combine (Configuration.SourceRoot, "tests", "dotnet", "AppWithXCAssets", platform.AsString ()); + var files = Directory.GetFiles (Path.Combine (projectDir, "Resources", "Images.xcassets"), "*", SearchOption.AllDirectories); + var imageAssets = files.Select (v => v + "|" + v.Substring (projectDir.Length + 1)).ToList (); + + // Add a .icon folder alongside the existing .xcassets + var tmpDir = Cache.CreateTemporaryDirectory (); + var iconFolderPath = Path.Combine (tmpDir, "ComposerIcon.icon"); + var assetsPath = Path.Combine (iconFolderPath, "Assets"); + Directory.CreateDirectory (assetsPath); + + var iconJsonPath = Path.Combine (iconFolderPath, "icon.json"); + File.WriteAllText (iconJsonPath, @"{""groups"":[{""layers"":[{""image-name"":""icon_512x512.png"",""name"":""icon""}]}]}"); + + var imagePath = Path.Combine (assetsPath, "icon_512x512.png"); + File.WriteAllText (imagePath, "placeholder image"); + + imageAssets.Add (iconJsonPath + "|Resources/ComposerIcon.icon/icon.json"); + imageAssets.Add (imagePath + "|Resources/ComposerIcon.icon/Assets/icon_512x512.png"); + + var actool = CreateACToolTask ( + platform, + projectDir, + out var _, + imageAssets.ToArray () + ); + actool.AppIcon = "AppIcons"; + + // actool may fail on the placeholder .icon content, but the validation phase should pass + actool.Execute (); + + // Verify that no icon validation errors were logged + AssertNoIconValidationErrors (); + } + + void AssertNoIconValidationErrors () + { + var errorMessages = Engine.Logger.ErrorEvents.Select (e => e.Message).ToList (); + Assert.That (errorMessages, Has.None.Contain ("Can't find the AppIcon"), + "Should not report that AppIcon is not found among image resources"); + Assert.That (errorMessages, Has.None.Contain ("Can't find the AlternateAppIcon"), + "Should not report that AlternateAppIcon is not found among image resources"); + Assert.That (errorMessages, Has.None.Contain ("is specified as both 'AppIcon' and 'AlternateAppIcon'"), + "Should not report icon conflict between AppIcon and AlternateAppIcon"); + Assert.That (errorMessages, Has.None.Contain ("Can't specify both 'XSAppIconAssets'"), + "Should not report XSAppIconAssets conflict"); + } } } From 75e59fc26c46e560111894cc523662ae2c33cbb6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 1 Jun 2026 08:50:12 +0200 Subject: [PATCH 43/97] [docs] Fix reference to the ReferenceNativeSymbol item group. (#25579) --- docs/building-apps/build-properties.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 684cd816cb7d..634396282b90 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -587,7 +587,7 @@ Example: ``` -Custom behavior for specific Objective-C classes can be set using the [ReferenceNativeSymbol](build-items.md#referencenativesymbols) item group: +Custom behavior for specific Objective-C classes can be set using the [ReferenceNativeSymbol](build-items.md#referencenativesymbol) item group: ```xml @@ -617,7 +617,7 @@ Example: ``` -Custom behavior for specific symbols can be set using the [ReferenceNativeSymbol](build-items.md#referencenativesymbols) item group: +Custom behavior for specific symbols can be set using the [ReferenceNativeSymbol](build-items.md#referencenativesymbol) item group: ```xml From 53ffc7202a22a069931b5ef744224c2c772a096b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 07:24:32 -0400 Subject: [PATCH 44/97] [github] Raise Code Radiator PR patch-file limit to 1000 for inter-branch merges (#25576) Inter-branch merge PR creation was blocked because the safe-output patch file cap was too low for `main => net11.0` (195 files). This change raises the cap to accommodate large automated merge PRs. - **Workflow source update** - Added a repository-level safe-output limit in `/.github/workflows/code-radiator.md`: - `safe-outputs.max-patch-files: 1000` - **Compiled workflow parity** - Updated the generated lockfile `/.github/workflows/code-radiator.lock.yml` so runtime safe-output config uses: - `create_pull_request.max_patch_files: 1000` - **Effective config (example)** ```yaml safe-outputs: max-patch-files: 1000 create-pull-request: max: 10 allowed-base-branches: - "net*.0" - "xcode*" - "xcode*.*" ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rolfbjarne <249268+rolfbjarne@users.noreply.github.com> --- .github/workflows/code-radiator.lock.yml | 4 ++-- .github/workflows/code-radiator.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-radiator.lock.yml b/.github/workflows/code-radiator.lock.yml index abcb3b886b9c..d36e5f5b4d16 100644 --- a/.github/workflows/code-radiator.lock.yml +++ b/.github/workflows/code-radiator.lock.yml @@ -460,7 +460,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b50ec395f48eecb1_EOF' - {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review"},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} + {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":1000,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review"},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} GH_AW_SAFE_OUTPUTS_CONFIG_b50ec395f48eecb1_EOF - name: Generate Safe Outputs Tools env: @@ -1580,7 +1580,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\"},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":1000,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\"},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/code-radiator.md b/.github/workflows/code-radiator.md index 8618affe282c..5d4d18d5a0e7 100644 --- a/.github/workflows/code-radiator.md +++ b/.github/workflows/code-radiator.md @@ -25,6 +25,7 @@ checkout: fetch: ["*"] fetch-depth: 0 safe-outputs: + max-patch-files: 1000 create-pull-request: max: 10 allowed-base-branches: From 1fb4e7e3f55d8dfc0cebb4ad820530f82c7b3da9 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 2 Jun 2026 08:00:06 +0200 Subject: [PATCH 45/97] [src] Add opt-in to build the platform assemblies serially. (#25546) The platform assemblies are rather big, and take a while to compile. This also means the C# compiler (csc) uses a lot of computational resources during the process. Each csc process is multi-threaded, and then if make also runs multiple csc processes in parallel, the build may completely thrash the CPU, and csc (aka Roslyn) isn't particularly easy on the memory either, causing any other use of the machine to suffer signicantly. So with this change we optionally build the platform assemblies serially. It looks a bit weird, but the idea is that in the make template, every platform assembly depends on the assembly from the previous template expansion. It slows down the build somewhat (src/ builds in ~5:30 instead of ~3:45 on my 9-year old iMac Pro), so it's opt-in instead of on by default. --- src/Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Makefile b/src/Makefile index 703dc110204c..5752ed549ca3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -435,6 +435,15 @@ $($(2)_DOTNET_BUILD_DIR)/Microsoft.$(1).rsp: Makefile frameworks.sources $(RSP_D $($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%dll $($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%pdb $($(2)_DOTNET_BUILD_DIR)/ref/Microsoft.$(1)%dll $($(2)_DOTNET_BUILD_DIR)/ref/Microsoft.$(1)%xml: $$($(2)_DOTNET_PLATFORM_ASSEMBLY_DEPENDENCIES) $$(ROSLYN_GENERATOR) $$(ROSLYN_ANALYZER) | $$($(2)_DOTNET_PLATFORM_ASSEMBLY_DIR_DEPENDENCIES) $$(call Q_PROF_CSC,dotnet) $(DOTNET_CSC) @$($(2)_DOTNET_BUILD_DIR)/Microsoft.$(1).rsp +ifdef SERIALIZE_CSC +# This is to serialize the csc commands for the platform assembly. The platform assembly takes a while to compile (maxing out the CPU), and csc is already multi-threaded, which +# means that if we're running multiple long csc tasks simultaneously, the CPU of the machine will get absolutely thrashed. csc (aka Roslyn) isn't exactly a cheapskate with memory either... +# It looks a bit weird, but the idea is that in this template, every platform assembly depends on the assembly from the previous template expansion. +# It's opt-in, because it slows down the build a bit. +$($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1).dll: $(LAST_DLL) +LAST_DLL=$($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1).dll +endif + dotnet-$(3):: $($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1).dll DOTNET_TARGETS_$(3) += \ From 957df39ce2025e2287b305e9f96b44e57a76c95b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 2 Jun 2026 09:00:04 +0200 Subject: [PATCH 46/97] [src] Remove NSPrintPreviewGraphicsContext. (#25527) NSPrintPreviewGraphicsContext existed in our bindings because we sometimes get instances of this type, and since its parent type is NSProxy, it causes problems at runtime when we try to figure out which managed type to instantiate. In particular, when: * The app is trimmed * The NSProxy type is trimmed away This happens: MarshalManagedException: ObjCRuntime.RuntimeException: The ObjectiveC class 'NSPrintPreviewGraphicsContext' could not be registered, it does not seem to derive from any known ObjectiveC class (including NSObject). at Registrar.DynamicRegistrar.Lookup(IntPtr class, Boolean throw_on_error) in /Users/builder/azdo/_work/1/s/macios/src/ObjCRuntime/DynamicRegistrar.cs:line 1069 at ObjCRuntime.Class.Lookup(IntPtr klass, Boolean throw_on_error) in /Users/builder/azdo/_work/1/s/macios/src/ObjCRuntime/Class.cs:line 291 at ObjCRuntime.Class.Lookup(IntPtr klass) in /Users/builder/azdo/_work/1/s/macios/src/ObjCRuntime/Class.cs:line 272 at ObjCRuntime.Runtime.GetNSObject[T](IntPtr ptr, IntPtr sel, RuntimeMethodHandle method_handle, Boolean evenInFinalizerQueue, Boolean typeSafe) in /Users/builder/azdo/_work/1/s/macios/src/ObjCRuntime/Runtime.cs:line 1893 at ObjCRuntime.Runtime.GetNSObject[T](IntPtr ptr) in /Users/builder/azdo/_work/1/s/macios/src/ObjCRuntime/Runtime.cs:line 1844 at ObjCRuntime.Runtime.GetNSObject[T](IntPtr ptr, Boolean owns) in /Users/builder/azdo/_work/1/s/macios/src/ObjCRuntime/Runtime.cs:line 1930 at AppKit.NSGraphicsContext.get_CurrentContext() in /Users/builder/azdo/_work/1/s/macios/src/build/dotnet/macos/generated-sources/AppKit/NSGraphicsContext.g.cs:line 411 at PrintableView.DrawPageBorder(CGSize borderSize) in /Users/rolf/test/dotnet/macos-printpreview/Program.cs:line 97 because `NSProxy` doesn't subclass `NSObject`, we can't find a managed type to instantiate. In the Xamarin days we had custom linker support to keep `NSPrintPreviewGraphicsContext` (https://github.com/xamarin/maccore/commit/d047ee123f3bfc28126bb9258878fccfed8d1c7a), but this was never ported to .NET, so this has been broken since forever in .NET. So fix this by: * Removing the `NSPrintPreviewGraphicsContext` binding, it causes problems when inlining calls to Class.GetHandle, because it's not part of the public API. * Ensure `NSProxy` is not trimmed away when `NSGraphicsContext.CurrentContext` is used, this way we'll return an `NSProxy` instance when the native instance is an actual `NSPrintPreviewGraphicsContext`. This required adding support for copying `[DynamicDependency]` attributes from binding code to the generated code. * Also add unit tests. This required removing the WebKit_NSProxy unit test, because it doesn't work anymore (it asserts that the `NSProxy` type is trimmed away, but one of the new tests depends on `NSProxy` not being trimmed away). References: * https://github.com/xamarin/bugzilla-archives/blob/main/16/16505/bug.html * https://github.com/xamarin/maccore/commit/d047ee123f3bfc28126bb9258878fccfed8d1c7a --- src/AppKit/NSPrintPreviewGraphicsContext.cs | 31 ++++++++ src/appkit.cs | 13 ++-- src/bgen/AttributeManager.cs | 1 + src/bgen/Generator.cs | 78 +++++++++++++++++-- src/frameworks.sources | 1 + tests/bgen/BGenTests.cs | 58 ++++++++++++++ .../tests/dynamic-dependency-attribute.cs | 42 ++++++++++ .../Documentation.KnownFailures.txt | 1 - tests/linker/link all/LinkAllMacTest.cs | 70 +++++++++++++++++ tests/linker/link all/LinkAllTest.cs | 9 --- .../macOS-AppKit.ignore | 3 - 11 files changed, 280 insertions(+), 27 deletions(-) create mode 100644 src/AppKit/NSPrintPreviewGraphicsContext.cs create mode 100644 tests/bgen/tests/dynamic-dependency-attribute.cs diff --git a/src/AppKit/NSPrintPreviewGraphicsContext.cs b/src/AppKit/NSPrintPreviewGraphicsContext.cs new file mode 100644 index 000000000000..ccc191f682c9 --- /dev/null +++ b/src/AppKit/NSPrintPreviewGraphicsContext.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; + +#if __MACOS__ && !XAMCORE_5_0 + +namespace AppKit { + [UnsupportedOSPlatform ("maccatalyst")] + [UnsupportedOSPlatform ("macos")] + [Obsolete ("This class does not form part of the public API in macOS, and will be removed in the future.")] + [EditorBrowsable (EditorBrowsableState.Never)] + public partial class NSPrintPreviewGraphicsContext : NSGraphicsContext { + + [EditorBrowsable (EditorBrowsableState.Never)] + public override NativeHandle ClassHandle { get { return default; } } + + [EditorBrowsable (EditorBrowsableState.Never)] + protected NSPrintPreviewGraphicsContext (NSObjectFlag t) : base (t) + { + } + + [EditorBrowsable (EditorBrowsableState.Never)] + protected internal NSPrintPreviewGraphicsContext (NativeHandle handle) : base (handle) + { + } + } /* class NSPrintPreviewGraphicsContext */ +} + +#endif // __MACOS__ && !XAMCORE_5_0 diff --git a/src/appkit.cs b/src/appkit.cs index 349228a5507c..f8eb8010f3c9 100644 --- a/src/appkit.cs +++ b/src/appkit.cs @@ -32,6 +32,7 @@ #nullable enable using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics; using System.ComponentModel; using CoreGraphics; @@ -8701,7 +8702,11 @@ interface NSGraphicsContext { NSGraphicsContext FromGraphicsPort (IntPtr graphicsPort, bool initialFlippedState); [Static, Export ("currentContext"), NullAllowed] - NSGraphicsContext CurrentContext { get; set; } + NSGraphicsContext CurrentContext { + [DynamicDependency (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors, "Foundation.NSProxy", "Microsoft.macOS")] // https://github.com/xamarin/bugzilla-archives/blob/main/16/16505/bug.html + get; + set; + } [Static, Export ("currentContextDrawingToScreen")] bool IsCurrentContextDrawingToScreen { get; } @@ -8964,12 +8969,6 @@ interface NSGridCell : NSCoding { NSLayoutConstraint [] CustomPlacementConstraints { get; set; } } - [NoMacCatalyst] - [BaseType (typeof (NSGraphicsContext))] - [DisableDefaultCtor] - interface NSPrintPreviewGraphicsContext { - } - [NoMacCatalyst] [BaseType (typeof (NSImageRep))] [DisableDefaultCtor] // An uncaught exception was raised: -[NSEPSImageRep init]: unrecognized selector sent to instance 0x1db2d90 diff --git a/src/bgen/AttributeManager.cs b/src/bgen/AttributeManager.cs index 3ceccdd0c76e..9ae2768b7670 100644 --- a/src/bgen/AttributeManager.cs +++ b/src/bgen/AttributeManager.cs @@ -15,6 +15,7 @@ public class AttributeManager { "System.Runtime.CompilerServices.NullableAttribute", "System.Runtime.CompilerServices.NullableContextAttribute", "System.Runtime.CompilerServices.NativeIntegerAttribute", + "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute", }; TypeCache TypeCache { get; } diff --git a/src/bgen/Generator.cs b/src/bgen/Generator.cs index f5559c942d26..f1221a33dafb 100644 --- a/src/bgen/Generator.cs +++ b/src/bgen/Generator.cs @@ -2788,7 +2788,7 @@ public string MakeSignature (MemberInformation minfo, bool is_async, ParameterIn string name = GetMethodName (minfo, is_async); // Some codepaths already write preservation info - PrintAttributes (minfo.mi, preserve: !alreadyPreserved, advice: true, bindAs: true, requiresSuper: true); + PrintAttributes (minfo.mi, preserve: !alreadyPreserved, advice: true, bindAs: true, requiresSuper: true, dynamicDependency: true); if (minfo.is_ctor && minfo.is_protocol_member) { sb.Append ("T? "); @@ -4037,7 +4037,7 @@ void GenerateProperty (Type type, PropertyInfo pi, List? instance_fields pi.Name.GetSafeParamName ()); indent++; if (generate_getter) { - PrintAttributes (pi.GetGetMethod ()!, platform: true, preserve: true, advice: true); + PrintAttributes (pi.GetGetMethod ()!, platform: true, preserve: true, advice: true, dynamicDependency: true); print ("get {"); indent++; @@ -4056,7 +4056,7 @@ void GenerateProperty (Type type, PropertyInfo pi, List? instance_fields print ("}"); } if (generate_setter) { - PrintAttributes (pi.GetSetMethod ()!, platform: true, preserve: true, advice: true); + PrintAttributes (pi.GetSetMethod ()!, platform: true, preserve: true, advice: true, dynamicDependency: true); print ("set {"); indent++; @@ -4130,7 +4130,7 @@ void GenerateProperty (Type type, PropertyInfo pi, List? instance_fields // If property getter or setter has its own WrapAttribute we let the user do whatever their heart desires if (generate_getter) { PrintAttributes (pi, platform: true); - PrintAttributes (pi.GetGetMethod (), platform: true, preserve: true, advice: true); + PrintAttributes (pi.GetGetMethod (), platform: true, preserve: true, advice: true, dynamicDependency: true); print ("get {"); indent++; @@ -4170,7 +4170,7 @@ void GenerateProperty (Type type, PropertyInfo pi, List? instance_fields PrintExport (minfo, sel, export!.ArgumentSemantic); } - PrintAttributes (pi.GetGetMethod (), platform: true, preserve: true, advice: true, notImplemented: true, inlinedType: inlinedType); + PrintAttributes (pi.GetGetMethod (), platform: true, preserve: true, advice: true, notImplemented: true, inlinedType: inlinedType, dynamicDependency: true); if (minfo.is_protocol_member && !minfo.is_static) { print ("get {"); print ($"\treturn _Get{pi.Name.GetSafeParamName ()} (this);"); @@ -4226,7 +4226,7 @@ void GenerateProperty (Type type, PropertyInfo pi, List? instance_fields if (not_implemented_attr is null && (!minfo.is_sealed || !minfo.is_wrapper)) PrintExport (minfo, sel, export!.ArgumentSemantic); - PrintAttributes (pi.GetSetMethod (), platform: true, preserve: true, advice: true, notImplemented: true, inlinedType: inlinedType); + PrintAttributes (pi.GetSetMethod (), platform: true, preserve: true, advice: true, notImplemented: true, inlinedType: inlinedType, dynamicDependency: true); if (minfo.is_protocol_member && !minfo.is_static) { print ("set {"); print ($"\t_Set{pi.Name.GetSafeParamName ()} (this, value);"); @@ -5509,7 +5509,7 @@ public void PrintBindAsAttribute (ICustomAttributeProvider? mi, StringBuilder? s // Not adding the experimental attribute is bad (it would mean that an API // we meant to be experimental ended up being released as stable), so it's // opt-out instead of opt-in. - public void PrintAttributes (ICustomAttributeProvider? mi, bool platform = false, bool preserve = false, bool advice = false, bool notImplemented = false, bool bindAs = false, bool requiresSuper = false, Type? inlinedType = null, bool experimental = true, bool obsolete = false, bool objectiveCFramework = false, bool simulatorAvailability = true) + public void PrintAttributes (ICustomAttributeProvider? mi, bool platform = false, bool preserve = false, bool advice = false, bool notImplemented = false, bool bindAs = false, bool requiresSuper = false, Type? inlinedType = null, bool experimental = true, bool obsolete = false, bool objectiveCFramework = false, bool simulatorAvailability = true, bool dynamicDependency = false) { if (platform) PrintPlatformAttributes (mi as MemberInfo, inlinedType); @@ -5531,6 +5531,70 @@ public void PrintAttributes (ICustomAttributeProvider? mi, bool platform = false PrintObjectiveCFrameworkAttribute (mi); if (simulatorAvailability) PrintSimulatorAvailabilityAttributes (mi); + if (dynamicDependency) + PrintDynamicDependencyAttributes (mi); + } + + public void PrintDynamicDependencyAttributes (ICustomAttributeProvider? mi) + { + if (mi is not MemberInfo memberInfo) + return; + + var allAttribs = memberInfo.GetCustomAttributesData (); + foreach (var attrib in allAttribs) { + if (attrib.GetAttributeType ().FullName != "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute") + continue; + var args = attrib.ConstructorArguments; + var parts = new List (); + foreach (var arg in args) { + if (arg.Value is string stringValue) { + parts.Add ($"\"{stringValue}\""); + } else if (arg.Value is int intValue) { + parts.Add (FormatDynamicallyAccessedMemberTypes (intValue)); + } else if (arg.Value is Type typeValue) { + parts.Add ($"typeof ({typeValue})"); + } else { + exceptions.Add (ErrorHelper.CreateError (99, $"Unexpected attribute argument value for DynamicDependency attribute: {arg.ArgumentType.FullName} => {arg.Value}")); + } + } + print ($"[DynamicDependency ({string.Join (", ", parts)})]"); + } + } + + static string FormatDynamicallyAccessedMemberTypes (int memberTypes) + { + if (memberTypes == -1) // All + return "DynamicallyAccessedMemberTypes.All"; + if (memberTypes == 0) // None + return "DynamicallyAccessedMemberTypes.None"; + + // Use the composite values first to match the original source + var flagValues = new (int value, string name) [] { + (3, "PublicConstructors"), // 3 includes PublicParameterlessConstructor + (1, "PublicParameterlessConstructor"), + (4, "NonPublicConstructors"), + (8, "PublicMethods"), + (16, "NonPublicMethods"), + (32, "PublicFields"), + (64, "NonPublicFields"), + (128, "PublicNestedTypes"), + (256, "NonPublicNestedTypes"), + (512, "PublicProperties"), + (1024, "NonPublicProperties"), + (2048, "PublicEvents"), + (4096, "NonPublicEvents"), + (8192, "Interfaces"), + }; + + var parts = new List (); + var remaining = memberTypes; + foreach (var (value, name) in flagValues) { + if (value != 0 && (remaining & value) == value) { + parts.Add ($"DynamicallyAccessedMemberTypes.{name}"); + remaining &= ~value; + } + } + return string.Join (" | ", parts); } public void PrintExperimentalAttribute (ICustomAttributeProvider? mi) diff --git a/src/frameworks.sources b/src/frameworks.sources index accf45f75e36..c715fddb973f 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -130,6 +130,7 @@ APPKIT_SOURCES = \ AppKit/NSPopUpButtonCell.cs \ AppKit/NSPredicateEditorRowTemplate.cs \ AppKit/NSPrintInfo.cs \ + AppKit/NSPrintPreviewGraphicsContext.cs \ AppKit/NSScreen.cs \ AppKit/NSSegmentedControl.cs \ AppKit/NSSharingService.cs \ diff --git a/tests/bgen/BGenTests.cs b/tests/bgen/BGenTests.cs index 37de37291334..656c8ac2c867 100644 --- a/tests/bgen/BGenTests.cs +++ b/tests/bgen/BGenTests.cs @@ -1155,6 +1155,64 @@ public void GeneratedAttributeOnPropertyAccessors2 () Assert.That (RenderSupportedOSPlatformAttributes (setter), Is.EqualTo (expectedSetterAttributes), "Setter Attributes"); } + [Test] + public void DynamicDependencyAttribute () + { + var bgen = BuildFile (Profile.macOSMobile, "dynamic-dependency-attribute.cs"); + + var type = bgen.ApiAssembly.MainModule.Types.First (v => v.Name == "MyClass"); + var getter = type.Methods.First (v => v.Name == "get_CurrentContext"); + var setter = type.Methods.First (v => v.Name == "set_CurrentContext"); + var doSomething = type.Methods.First (v => v.Name == "DoSomething"); + var doSomethingElse = type.Methods.First (v => v.Name == "DoSomethingElse"); + var doAnother = type.Methods.First (v => v.Name == "DoAnother"); + var doYetAnother = type.Methods.First (v => v.Name == "DoYetAnother"); + var doAll = type.Methods.First (v => v.Name == "DoAll"); + + // (DynamicallyAccessedMemberTypes, string, string) on property getter + var getterDDA = getter.CustomAttributes.Where (ca => ca.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (getterDDA.Length, Is.EqualTo (1), "Getter DynamicDependency count"); + Assert.That ((int) getterDDA [0].ConstructorArguments [0].Value, Is.EqualTo (7), "Getter DynamicDependency MemberTypes (PublicConstructors | NonPublicConstructors)"); + Assert.That (getterDDA [0].ConstructorArguments [1].Value, Is.EqualTo ("Foundation.NSProxy"), "Getter DynamicDependency TypeName"); + Assert.That (getterDDA [0].ConstructorArguments [2].Value, Is.EqualTo ("Microsoft.macOS"), "Getter DynamicDependency AssemblyName"); + + // Setter should not have it + var setterDDA = setter.CustomAttributes.Where (ca => ca.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (setterDDA.Length, Is.EqualTo (0), "Setter DynamicDependency count"); + + // (string, string, string) on method + var methodDDA = doSomething.CustomAttributes.Where (ca => ca.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (methodDDA.Length, Is.EqualTo (1), "DoSomething DynamicDependency count"); + Assert.That (methodDDA [0].ConstructorArguments [0].Value, Is.EqualTo ("Create"), "DoSomething DynamicDependency MemberSignature"); + Assert.That (methodDDA [0].ConstructorArguments [1].Value, Is.EqualTo ("NS.MyClass"), "DoSomething DynamicDependency TypeName"); + Assert.That (methodDDA [0].ConstructorArguments [2].Value, Is.EqualTo ("api0"), "DoSomething DynamicDependency AssemblyName"); + + // (string) - single member signature + var elseDDA = doSomethingElse.CustomAttributes.Where (ca => ca.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (elseDDA.Length, Is.EqualTo (1), "DoSomethingElse DynamicDependency count"); + Assert.That (elseDDA [0].ConstructorArguments.Count, Is.EqualTo (1), "DoSomethingElse DynamicDependency arg count"); + Assert.That (elseDDA [0].ConstructorArguments [0].Value, Is.EqualTo ("Activate"), "DoSomethingElse DynamicDependency MemberSignature"); + + // (DynamicallyAccessedMemberTypes, Type) + var anotherDDA = doAnother.CustomAttributes.Where (ca => ca.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (anotherDDA.Length, Is.EqualTo (1), "DoAnother DynamicDependency count"); + Assert.That ((int) anotherDDA [0].ConstructorArguments [0].Value, Is.EqualTo (8 | 512), "DoAnother DynamicDependency MemberTypes (PublicMethods | PublicProperties)"); + Assert.That (((Mono.Cecil.TypeReference) anotherDDA [0].ConstructorArguments [1].Value).Name, Is.EqualTo ("NSObject"), "DoAnother DynamicDependency Type"); + + // (string, Type) + var yetAnotherDDA = doYetAnother.CustomAttributes.Where (ca => ca.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (yetAnotherDDA.Length, Is.EqualTo (1), "DoYetAnother DynamicDependency count"); + Assert.That (yetAnotherDDA [0].ConstructorArguments [0].Value, Is.EqualTo ("Create"), "DoYetAnother DynamicDependency MemberSignature"); + Assert.That (((Mono.Cecil.TypeReference) yetAnotherDDA [0].ConstructorArguments [1].Value).Name, Is.EqualTo ("NSObject"), "DoYetAnother DynamicDependency Type"); + + // (DynamicallyAccessedMemberTypes.All, string, string) + var allDDA = doAll.CustomAttributes.Where (ca => ca.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (allDDA.Length, Is.EqualTo (1), "DoAll DynamicDependency count"); + Assert.That ((int) allDDA [0].ConstructorArguments [0].Value, Is.EqualTo (-1), "DoAll DynamicDependency MemberTypes (All)"); + Assert.That (allDDA [0].ConstructorArguments [1].Value, Is.EqualTo ("NS.MyClass"), "DoAll DynamicDependency TypeName"); + Assert.That (allDDA [0].ConstructorArguments [2].Value, Is.EqualTo ("api0"), "DoAll DynamicDependency AssemblyName"); + } + [Test] [TestCase (Profile.iOS)] public void NewerAvailabilityInInlinedProtocol (Profile profile) diff --git a/tests/bgen/tests/dynamic-dependency-attribute.cs b/tests/bgen/tests/dynamic-dependency-attribute.cs new file mode 100644 index 000000000000..5ffbc3ac5d1e --- /dev/null +++ b/tests/bgen/tests/dynamic-dependency-attribute.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Foundation; +using ObjCRuntime; + +namespace NS { + [BaseType (typeof (NSObject))] + interface MyClass { + [Static, Export ("currentContext"), NullAllowed] + NSObject CurrentContext { + // (DynamicallyAccessedMemberTypes, string, string) + [DynamicDependency (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors, "Foundation.NSProxy", "Microsoft.macOS")] + get; + set; + } + + // (string, string, string) + [Export ("doSomething")] + [DynamicDependency ("Create", "NS.MyClass", "api0")] + void DoSomething (); + + // (string) - single member signature + [Export ("doSomethingElse")] + [DynamicDependency ("Activate")] + void DoSomethingElse (); + + // (DynamicallyAccessedMemberTypes, Type) + [Export ("doAnother")] + [DynamicDependency (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties, typeof (NSObject))] + void DoAnother (); + + // (string, Type) + [Export ("doYetAnother")] + [DynamicDependency ("Create", typeof (NSObject))] + void DoYetAnother (); + + // (DynamicallyAccessedMemberTypes.All, string, string) + [Export ("doAll")] + [DynamicDependency (DynamicallyAccessedMemberTypes.All, "NS.MyClass", "api0")] + void DoAll (); + } +} diff --git a/tests/cecil-tests/Documentation.KnownFailures.txt b/tests/cecil-tests/Documentation.KnownFailures.txt index 259d2d5ebdf1..9e1603a34248 100644 --- a/tests/cecil-tests/Documentation.KnownFailures.txt +++ b/tests/cecil-tests/Documentation.KnownFailures.txt @@ -25152,7 +25152,6 @@ T:AppKit.NSPrintingPageOrder T:AppKit.NSPrintingPaginationMode T:AppKit.NSPrintPanelOptions T:AppKit.NSPrintPanelResult -T:AppKit.NSPrintPreviewGraphicsContext T:AppKit.NSPrintRenderingQuality T:AppKit.NSProgressIndicatorStyle T:AppKit.NSProgressIndicatorThickness diff --git a/tests/linker/link all/LinkAllMacTest.cs b/tests/linker/link all/LinkAllMacTest.cs index 363e29d8d920..fad1a76cf15f 100644 --- a/tests/linker/link all/LinkAllMacTest.cs +++ b/tests/linker/link all/LinkAllMacTest.cs @@ -2,6 +2,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; @@ -73,6 +74,75 @@ public SerializeMe () SetMe = 1; } } + + // https://github.com/xamarin/bugzilla-archives/blob/main/16/16505/bug.html + [Test] + public void PrintPreview_NSGraphicsContextCurrentContext () + { + TestRuntime.AssertXcodeVersion (26, 0); + + // Verify that accessing NSGraphicsContext.CurrentContext during print preview + // doesn't crash due to the linker trimming NSPrintPreviewGraphicsContext. + var printableView = new PrintableView (new CoreGraphics.CGRect (0, 0, 100, 100)); + + var printInfo = (NSPrintInfo) NSPrintInfo.SharedPrintInfo.Copy (); + printInfo.JobDisposition = "NSPrintPreviewJob"; + + var printOp = NSPrintOperation.FromView (printableView, printInfo); + printOp.ShowsPrintPanel = true; // this is required to trigger the bug + printOp.ShowsProgressPanel = true; + + var closedPreview = false; + var closeAction = new Action (() => { + if (closedPreview) + return; + NSApplication.SharedApplication.AbortModal (); + closedPreview = true; + }); + + printableView.TaskCompletionSource.Task.ContinueWith (task => { + closeAction (); + }, TaskScheduler.FromCurrentSynchronizationContext ()); + + // Auto-close after 3 seconds in case something goes wrong + var timer = NSTimer.CreateScheduledTimer (3.0, (t) => closeAction ()); + NSRunLoop.Current.AddTimer (timer, NSRunLoopMode.ModalPanel); + + printOp.RunOperation (); + + Assert.That (printableView.TaskCompletionSource.Task.IsCompletedSuccessfully, Is.True, "DrawPageBorder was called successfully"); + var context = printableView.TaskCompletionSource.Task.Result; + Assert.That (context, Is.Not.Null, "NSGraphicsContext.CurrentContext was not null during print preview"); + } + } + + class PrintableView : NSView { + public PrintableView (CoreGraphics.CGRect frame) : base (frame) { } + + public TaskCompletionSource TaskCompletionSource = new (); + + public override void DrawPageBorder (CoreGraphics.CGSize borderSize) + { + try { + var context = NSGraphicsContext.CurrentContext; + base.DrawPageBorder (borderSize); + TaskCompletionSource.TrySetResult (context); + } catch (Exception e) { + Console.WriteLine ($"Unexpected exception occurred: {e}"); + TaskCompletionSource.TrySetException (e); + } + } + + public override bool KnowsPageRange (ref Foundation.NSRange range) + { + range = new Foundation.NSRange (1, 1); + return true; + } + + public override CoreGraphics.CGRect RectForPage (nint pageNumber) + { + return Bounds; + } } } #endif // __MACOS__ diff --git a/tests/linker/link all/LinkAllTest.cs b/tests/linker/link all/LinkAllTest.cs index 2ff37fa27fd0..8a21b29c7d13 100644 --- a/tests/linker/link all/LinkAllTest.cs +++ b/tests/linker/link all/LinkAllTest.cs @@ -466,15 +466,6 @@ public void AppleTls () Assert.That (Helper.GetType (fqn), Is.Null, "Should NOT be included (no SslStream or Socket support)"); } - [Test] - // https://bugzilla.xamarin.com/show_bug.cgi?id=59247 - public void WebKit_NSProxy () - { - // this test works only because "Link all" does not use WebKit - var fqn = typeof (NSObject).AssemblyQualifiedName!.Replace ("Foundation.NSObject", "Foundation.NSProxy"); - Assert.That (Helper.GetType (fqn), Is.Null, fqn); - } - static Type type_Task = typeof (Task); [Test] diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-AppKit.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-AppKit.ignore index 268a5aeb27fa..a9a43afadb4c 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-AppKit.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-AppKit.ignore @@ -1,9 +1,6 @@ ## https://bugzilla.xamarin.com/show_bug.cgi?id=30717 !duplicate-register! _NSDatePickerCellDelegate exists as both AppKit.NSDatePickerCell/_NSDatePickerCellDelegate and AppKit.NSDatePicker/_NSDatePickerCellDelegate -## Proxy class for NSGraphicsContext internal to AppKit -!unknown-type! NSPrintPreviewGraphicsContext bound - ## In header as old style unnamaed enum !unknown-native-enum! NSModalResponse bound !unknown-native-enum! NSWindowLevel bound From 56ffb2f440c86d0ef4858c8281a13b2788200d50 Mon Sep 17 00:00:00 2001 From: "CSIGS@microsoft.com" Date: Tue, 2 Jun 2026 00:39:41 -0700 Subject: [PATCH 47/97] LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260528175129236 to main (#25569) LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260528175129236 to main with localized lcls --- .../loc/cs/macios/src/Resources.resx.lcl | 7 ++-- .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/de/macios/src/Resources.resx.lcl | 7 ++-- .../de/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../loc/es/macios/src/Resources.resx.lcl | 7 ++-- .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../fr/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/ja/macios/src/Resources.resx.lcl | 7 ++-- .../ja/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/pl/macios/src/Resources.resx.lcl | 7 ++-- .../pl/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ 13 files changed, 205 insertions(+), 10 deletions(-) diff --git a/macios/Localize/loc/cs/macios/src/Resources.resx.lcl b/macios/Localize/loc/cs/macios/src/Resources.resx.lcl index 9549f1d38696..8e1699b9acaf 100644 --- a/macios/Localize/loc/cs/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/cs/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/de/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/de/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index b2fc70394ad2..564e87794819 100644 --- a/macios/Localize/loc/de/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/de/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/de/macios/src/Resources.resx.lcl b/macios/Localize/loc/de/macios/src/Resources.resx.lcl index 364c5c515e6e..1c29fc1ad7ee 100644 --- a/macios/Localize/loc/de/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/de/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/de/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/de/macios/tools/mtouch/Errors.resx.lcl index 272d0fd57cab..a92aca87125c 100644 --- a/macios/Localize/loc/de/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/de/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/es/macios/src/Resources.resx.lcl b/macios/Localize/loc/es/macios/src/Resources.resx.lcl index 85246e0ce950..b097266a8d41 100644 --- a/macios/Localize/loc/es/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/es/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/fr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/fr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index fada47500cc4..e47ef87b934e 100644 --- a/macios/Localize/loc/fr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/fr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/fr/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/fr/macios/tools/mtouch/Errors.resx.lcl index c46996116ac1..57d5b027ef04 100644 --- a/macios/Localize/loc/fr/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/fr/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/ja/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/ja/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index f71a2b3a978f..429efcd0d6a5 100644 --- a/macios/Localize/loc/ja/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/ja/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/ja/macios/src/Resources.resx.lcl b/macios/Localize/loc/ja/macios/src/Resources.resx.lcl index b042a0a70bac..4ec32400c436 100644 --- a/macios/Localize/loc/ja/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/ja/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/ja/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/ja/macios/tools/mtouch/Errors.resx.lcl index 0326c3f3dc31..0a5c0969fde0 100644 --- a/macios/Localize/loc/ja/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/ja/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/pl/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/pl/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index aff2c57e5e45..10afa0d41504 100644 --- a/macios/Localize/loc/pl/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/pl/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/pl/macios/src/Resources.resx.lcl b/macios/Localize/loc/pl/macios/src/Resources.resx.lcl index 535e5be2e3df..40459f23f097 100644 --- a/macios/Localize/loc/pl/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/pl/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/pl/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/pl/macios/tools/mtouch/Errors.resx.lcl index 8e3eafdf8f62..d81fe2182e28 100644 --- a/macios/Localize/loc/pl/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/pl/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + From d3a1bda206759b5d040721960b3cd1971fe0320e Mon Sep 17 00:00:00 2001 From: "CSIGS@microsoft.com" Date: Tue, 2 Jun 2026 00:54:25 -0700 Subject: [PATCH 48/97] LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260528055153236 to main (#25558) LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260528055153236 to main with localized lcls --- .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../es/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../loc/fr/macios/src/Resources.resx.lcl | 7 ++-- .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/zh-Hans/macios/src/Resources.resx.lcl | 7 ++-- .../macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/zh-Hant/macios/src/Resources.resx.lcl | 7 ++-- .../macios/tools/mtouch/Errors.resx.lcl | 9 +++++ 9 files changed, 150 insertions(+), 6 deletions(-) diff --git a/macios/Localize/loc/es/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/es/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index f20f4cb2c0c9..b692e401e9c4 100644 --- a/macios/Localize/loc/es/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/es/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/es/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/es/macios/tools/mtouch/Errors.resx.lcl index 6ae060269c6e..3040949dddb1 100644 --- a/macios/Localize/loc/es/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/es/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/fr/macios/src/Resources.resx.lcl b/macios/Localize/loc/fr/macios/src/Resources.resx.lcl index 6175b9d20601..d8878181007a 100644 --- a/macios/Localize/loc/fr/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/fr/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/zh-Hans/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/zh-Hans/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 00517d08e59d..2e41c4b28761 100644 --- a/macios/Localize/loc/zh-Hans/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/zh-Hans/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/zh-Hans/macios/src/Resources.resx.lcl b/macios/Localize/loc/zh-Hans/macios/src/Resources.resx.lcl index 2d4649df1b79..f5da49efa398 100644 --- a/macios/Localize/loc/zh-Hans/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/zh-Hans/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/zh-Hans/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/zh-Hans/macios/tools/mtouch/Errors.resx.lcl index c37d02e4d78a..af179127e37e 100644 --- a/macios/Localize/loc/zh-Hans/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/zh-Hans/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/zh-Hant/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/zh-Hant/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index b8f65ed4218a..0c5e6cd287eb 100644 --- a/macios/Localize/loc/zh-Hant/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/zh-Hant/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/zh-Hant/macios/src/Resources.resx.lcl b/macios/Localize/loc/zh-Hant/macios/src/Resources.resx.lcl index 21c2a5d3082f..0074c1598564 100644 --- a/macios/Localize/loc/zh-Hant/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/zh-Hant/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/zh-Hant/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/zh-Hant/macios/tools/mtouch/Errors.resx.lcl index f987a2aa5168..69beebeb0b2d 100644 --- a/macios/Localize/loc/zh-Hant/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/zh-Hant/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + From 151a329cb023cd0b488d20be70b23e7f7d7090c0 Mon Sep 17 00:00:00 2001 From: VS MobileTools Engineering Service 2 Date: Tue, 2 Jun 2026 00:58:42 -0700 Subject: [PATCH 49/97] Localized file check-in by OneLocBuild Task: Build definition ID 14411: Build ID 14245989 (#25593) This is the pull request automatically created by the OneLocBuild task in the build process to check-in localized files generated based upon translation source files (.lcl files) handed-back from the downstream localization pipeline. If there are issues in translations, visit https://aka.ms/icxLocBug and log bugs for fixes. The OneLocBuild wiki is https://aka.ms/onelocbuild and the localization process in general is documented at https://aka.ms/AllAboutLoc. --- .../TranslatedAssemblies/MSBStrings.cs.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.de.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.es.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.fr.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.it.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.ja.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.ko.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.pl.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.pt-BR.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.ru.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.tr.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.zh-Hans.resx | 15 +++++++++++++++ .../TranslatedAssemblies/MSBStrings.zh-Hant.resx | 15 +++++++++++++++ 13 files changed, 195 insertions(+) diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file diff --git a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx index f3125770202e..9f4afd35224d 100644 --- a/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx +++ b/macios/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx @@ -1296,4 +1296,19 @@ The settings file '{0}' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use. + + The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the required simulator runtime is not installed. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line. + Shown when we're unable to check for simulator runtime availability. +{0} - The platform name (e.g. "iOS" or "tvOS"). + + + The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings > Components). + Shown when the tool reports a simulator runtime version mismatch. +{0} - The platform name (e.g. "iOS" or "tvOS"). + \ No newline at end of file From 9d4392d0845ea5c8ac3ea587204f99a5f454930b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 2 Jun 2026 12:19:42 +0200 Subject: [PATCH 50/97] [docs] Minor update to docs about how to update api docs. (#25595) --- docs/update-api-docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/update-api-docs.md b/docs/update-api-docs.md index 58903732193a..ddb1e000babc 100644 --- a/docs/update-api-docs.md +++ b/docs/update-api-docs.md @@ -24,7 +24,7 @@ The steps are: * Copy our platform assemblies and their xml files into their corresponding directory. * Create a new commit and push it to origin. -4. Go here: [Continuous Integration](https://ops.microsoft.com/#/repos/85f784f4-01e7-ffb8-ed06-a012f7d649c0?tabName=ci) (might need VPN enabled, otherwise sometimes you'll get a 403 error page) and then: +4. Go here: [OPS dotnet/macios-api-docs / Continuous Integration](https://ops.microsoft.com/#/repos/85f784f4-01e7-ffb8-ed06-a012f7d649c0?tabName=ci) (if you have to go through the authentication workflow you'll end up on the OPS homepage, in which case just click the link again) and then: * Expand the '.NET macios API docs' job, and then: * Change `Target Repo` -> `Target Branch` to `netX.Y-xcodeZ.W` (same branch as created above). From 18077ca662f1ea0292ded2e5c180ef66982487ea Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 2 Jun 2026 13:31:44 +0200 Subject: [PATCH 51/97] [tests] Fix flaky BasicAuthWorksWhenBearerIsAdvertisedFirst test. Fixes #25597 (#25604) The test was getting HTTP 404 on a CI bot, but the local HttpListener server never returns 404. The likely cause is localhost resolving to ::1 (IPv6) on certain macOS bots while HttpListener with http://*:port/ only binds to IPv4. The request reaches something else and gets 404. Fix: - Use 127.0.0.1 explicitly in both the HttpListener prefix and the request URL to avoid IPv6/hostname resolution mismatches. - Add CI tolerance: if the server received zero requests and the status is 404, mark the test as inconclusive (infrastructure issue, not a code bug). - Add IgnoreInCIIfBadNetwork and timeout handling consistent with other tests in this file. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../NSUrlSessionHandlerTest.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs index 8b1fe66f384a..79c787d387c1 100644 --- a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs +++ b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs @@ -286,14 +286,27 @@ public void BasicAuthWorksWhenBearerIsAdvertisedFirst () using var handler = new NSUrlSessionHandler (); handler.Credentials = new NetworkCredential (username, password); using var client = new HttpClient (handler); - using var request = new HttpRequestMessage (HttpMethod.Get, $"http://localhost:{listeningPort}/test"); + // Use 127.0.0.1 instead of localhost to avoid IPv6 resolution + // issues where NSUrlSession may connect to ::1 while + // HttpListener only binds to IPv4. + using var request = new HttpRequestMessage (HttpMethod.Get, $"http://127.0.0.1:{listeningPort}/test"); var response = await client.SendAsync (request).ConfigureAwait (false); statusCode = response.StatusCode; responseBody = await response.Content.ReadAsStringAsync ().ConfigureAwait (false); }, out var ex); - Assert.That (done, Is.True, "Request timed out"); + if (!done) { + TestRuntime.IgnoreInCI ("Transient localhost server failure - ignore in CI"); + Assert.Inconclusive ("Request timed out."); + } + TestRuntime.IgnoreInCIIfBadNetwork (ex); Assert.That (ex, Is.Null, $"Exception: {ex}"); + // If no request reached the server, the failure is an infrastructure + // issue (e.g. port conflict), not a code bug. + if (Volatile.Read (ref requestIndex) == 0 && statusCode == HttpStatusCode.NotFound) { + TestRuntime.IgnoreInCI ($"Server received no requests and got status {statusCode} - infrastructure issue, ignore in CI"); + Assert.Inconclusive ($"Server received no requests; status was {statusCode}. Likely a port/binding issue."); + } Assert.That (statusCode, Is.EqualTo (HttpStatusCode.OK), "Expected 200 OK after Basic auth negotiation"); Assert.That (responseBody, Is.EqualTo ("authenticated"), "Response body"); Assert.That (firstUnauthenticatedIndex, Is.GreaterThan (0), "Server should have received an unauthenticated request"); @@ -316,7 +329,7 @@ public void BasicAuthWorksWhenBearerIsAdvertisedFirst () for (var port = MinPort; port < MaxPort; port++) { var listener = new HttpListener (); - listener.Prefixes.Add ($"http://*:{port}/"); + listener.Prefixes.Add ($"http://127.0.0.1:{port}/"); try { listener.Start (); listeningPort = port; From 47eb243a3eed2249e64aa9e166915212f75353d4 Mon Sep 17 00:00:00 2001 From: "CSIGS@microsoft.com" Date: Tue, 2 Jun 2026 04:44:20 -0700 Subject: [PATCH 52/97] LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260529053637714 to main (#25575) LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260529053637714 to main with localized lcls --- .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../cs/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/it/macios/src/Resources.resx.lcl | 7 ++-- .../it/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/ko/macios/src/Resources.resx.lcl | 7 ++-- .../ko/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/pt-BR/macios/src/Resources.resx.lcl | 7 ++-- .../pt-BR/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/ru/macios/src/Resources.resx.lcl | 7 ++-- .../ru/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ .../MSBStrings.resx.lcl | 36 +++++++++++++++++++ .../loc/tr/macios/src/Resources.resx.lcl | 7 ++-- .../tr/macios/tools/mtouch/Errors.resx.lcl | 9 +++++ 17 files changed, 295 insertions(+), 10 deletions(-) diff --git a/macios/Localize/loc/cs/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/cs/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 620e5dd4fb7f..44c49d101321 100644 --- a/macios/Localize/loc/cs/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/cs/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/cs/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/cs/macios/tools/mtouch/Errors.resx.lcl index 029bc250773a..e737c9d8e469 100644 --- a/macios/Localize/loc/cs/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/cs/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/it/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/it/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index f1a86dc8993b..c67c30add6ac 100644 --- a/macios/Localize/loc/it/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/it/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/it/macios/src/Resources.resx.lcl b/macios/Localize/loc/it/macios/src/Resources.resx.lcl index 8b231c676455..c6f87770593a 100644 --- a/macios/Localize/loc/it/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/it/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/it/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/it/macios/tools/mtouch/Errors.resx.lcl index 5a5bbaefc1ae..70d4408c9f26 100644 --- a/macios/Localize/loc/it/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/it/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/ko/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/ko/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 638a95bf9892..8f6a9309a649 100644 --- a/macios/Localize/loc/ko/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/ko/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/ko/macios/src/Resources.resx.lcl b/macios/Localize/loc/ko/macios/src/Resources.resx.lcl index 2a6ec47780f7..b6f9d3f7c969 100644 --- a/macios/Localize/loc/ko/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/ko/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/ko/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/ko/macios/tools/mtouch/Errors.resx.lcl index 36785bba0430..073974f830fe 100644 --- a/macios/Localize/loc/ko/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/ko/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/pt-BR/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/pt-BR/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index e9b8116173e4..b36703f288b6 100644 --- a/macios/Localize/loc/pt-BR/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/pt-BR/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/pt-BR/macios/src/Resources.resx.lcl b/macios/Localize/loc/pt-BR/macios/src/Resources.resx.lcl index d12b83b1ff3e..b7ce10acb40c 100644 --- a/macios/Localize/loc/pt-BR/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/pt-BR/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/pt-BR/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/pt-BR/macios/tools/mtouch/Errors.resx.lcl index cb12d7bf13cc..7e153e535bc9 100644 --- a/macios/Localize/loc/pt-BR/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/pt-BR/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/ru/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/ru/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 80fd78c3f715..ff190b437318 100644 --- a/macios/Localize/loc/ru/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/ru/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/ru/macios/src/Resources.resx.lcl b/macios/Localize/loc/ru/macios/src/Resources.resx.lcl index 7bc3cfac722a..095e1f672a82 100644 --- a/macios/Localize/loc/ru/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/ru/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/ru/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/ru/macios/tools/mtouch/Errors.resx.lcl index 720865503b6d..a0a2d50d4c8a 100644 --- a/macios/Localize/loc/ru/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/ru/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/tr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/tr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 94ffb3bad606..3251464cd07f 100644 --- a/macios/Localize/loc/tr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/tr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -3625,6 +3625,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macios/Localize/loc/tr/macios/src/Resources.resx.lcl b/macios/Localize/loc/tr/macios/src/Resources.resx.lcl index 87909f7e2e5e..4cf6e27fa0dc 100644 --- a/macios/Localize/loc/tr/macios/src/Resources.resx.lcl +++ b/macios/Localize/loc/tr/macios/src/Resources.resx.lcl @@ -381,10 +381,13 @@ - + - + + + + diff --git a/macios/Localize/loc/tr/macios/tools/mtouch/Errors.resx.lcl b/macios/Localize/loc/tr/macios/tools/mtouch/Errors.resx.lcl index 69c6f943eadf..2aedad71123b 100644 --- a/macios/Localize/loc/tr/macios/tools/mtouch/Errors.resx.lcl +++ b/macios/Localize/loc/tr/macios/tools/mtouch/Errors.resx.lcl @@ -4573,6 +4573,15 @@ + + + + + + + + + From 58bb1f6407cf9f495463b20918f1dbc45a98dc27 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:16:39 +0000 Subject: [PATCH 53/97] [main] Update dependencies from dotnet/dotnet (#25587) 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**: [20260531.4](https://dev.azure.com/dnceng/internal/_build/results?buildId=2988460) ([316516](https://maestro.dot.net/channel/10307/github:dotnet:dotnet/build/316516)) - **Date Produced**: May 31, 2026 7:18:38 PM UTC - **Commit**: [aec921632e75e1f29327709dd52e98c41f3b55cf](https://github.com/dotnet/dotnet/commit/aec921632e75e1f29327709dd52e98c41f3b55cf) - **Branch**: [release/10.0.4xx](https://github.com/dotnet/dotnet/tree/release/10.0.4xx) - **Dependency Updates**: - From [10.0.0-beta.26277.109 to 10.0.0-beta.26281.104][2] - Microsoft.DotNet.Arcade.Sdk - Microsoft.DotNet.Build.Tasks.Feed - Microsoft.DotNet.SharedFramework.Sdk - From [10.0.400-preview.0.26277.109 to 10.0.400-preview.0.26281.104][2] - Microsoft.NET.Sdk - From [10.0.400-preview.26277.109 to 10.0.400-preview.26281.104][2] - Microsoft.TemplateEngine.Authoring.Tasks [2]: https://github.com/dotnet/dotnet/compare/2f68b99e48...aec921632e --- eng/Version.Details.props | 10 +++++----- eng/Version.Details.xml | 20 ++++++++++---------- global.json | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 5277e7173d29..51bd77497e11 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.26277.109 - 10.0.0-beta.26277.109 + 10.0.0-beta.26281.104 + 10.0.0-beta.26281.104 0.11.5-alpha.26070.104 - 10.0.0-beta.26277.109 + 10.0.0-beta.26281.104 10.0.3-servicing.26070.104 10.0.3 10.0.3 - 10.0.400-preview.0.26277.109 + 10.0.400-preview.0.26281.104 10.0.3 - 10.0.400-preview.26277.109 + 10.0.400-preview.26281.104 26.0.11017 18.5.9227 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index e53abe16f371..f31e396323df 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,8 +1,8 @@ - + https://github.com/dotnet/dotnet - 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b + aec921632e75e1f29327709dd52e98c41f3b55cf https://github.com/dotnet/dotnet @@ -95,25 +95,25 @@ - + https://github.com/dotnet/dotnet - 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b + aec921632e75e1f29327709dd52e98c41f3b55cf - + https://github.com/dotnet/dotnet - 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b + aec921632e75e1f29327709dd52e98c41f3b55cf - + https://github.com/dotnet/dotnet - 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b + aec921632e75e1f29327709dd52e98c41f3b55cf https://github.com/dotnet/xharness 51ca379106cfd749a498cb0822210ef1aa926e41 - + https://github.com/dotnet/dotnet - 2f68b99e483cd8f4acbc1aa365fc806f1bd2374b + aec921632e75e1f29327709dd52e98c41f3b55cf diff --git a/global.json b/global.json index 294c78cab356..b2855704689d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.400-preview.0.26277.109", + "version": "10.0.400-preview.0.26281.104", "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.26277.109" + "dotnet": "10.0.400-preview.0.26281.104" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26277.109" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26281.104" } } From 194f285b35461c96a08d507ab907f31aeab40abb Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 2 Jun 2026 14:47:03 +0200 Subject: [PATCH 54/97] [bgen] Cache raw GetCustomAttributesData() results per provider (#25564) MetadataLoadContext's `GetCustomAttributesData()` returns a fresh `ReadOnlyCollection` on every call. Since bgen queries multiple attribute types per provider (via `GetCustomAttributes`, `HasAttribute`, `HasAttribute(string)`, `IsNullable`), the same provider's raw attribute list was being allocated and discarded many times over. Add a `Dictionary>` that caches the raw result per provider. Changed `GetAttributes()` from static to instance to enable this cache. `HasAttribute(string)` also changed from static to instance (only one caller, already via instance). ``` Baseline average (3 runs): 24.7s, 9164 MB total, gen0=1660, gen1=513 After average (3 runs): 22.1s, 6990 MB total, gen0=1265, gen1=417 Time: -2.6s (-10.5%) Memory: -2174 MB (-23.7%) GC: gen0 -395 (-23.8%), gen1 -96 (-18.7%) ``` --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Rolf Bjarne Kvinge --- src/bgen/AttributeManager.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/bgen/AttributeManager.cs b/src/bgen/AttributeManager.cs index 9ae2768b7670..4e3b3acbb927 100644 --- a/src/bgen/AttributeManager.cs +++ b/src/bgen/AttributeManager.cs @@ -18,6 +18,9 @@ public class AttributeManager { "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute", }; + // Cache raw GetCustomAttributesData() results per provider to avoid repeated reflection allocations. + readonly Dictionary> rawAttributeCache = new (); + TypeCache TypeCache { get; } public AttributeManager (TypeCache typeCache) @@ -501,9 +504,15 @@ public virtual T [] GetCustomAttributes (ICustomAttributeProvider? provider) } [return: NotNullIfNotNull (nameof (provider))] - static IList? GetAttributes (ICustomAttributeProvider? provider) - => provider switch { - null => null, + IList? GetAttributes (ICustomAttributeProvider? provider) + { + if (provider is null) + return null; + + if (rawAttributeCache.TryGetValue (provider, out var cached)) + return cached; + + IList result = provider switch { MemberInfo member => member.GetCustomAttributesData (), Assembly assembly => assembly.GetCustomAttributesData (), ParameterInfo pinfo => pinfo.GetCustomAttributesData (), @@ -511,7 +520,11 @@ public virtual T [] GetCustomAttributes (ICustomAttributeProvider? provider) _ => throw new BindingException (1051, true, provider.GetType ().FullName) }; - public static bool HasAttribute (ICustomAttributeProvider provider, string type_name) + rawAttributeCache [provider] = result; + return result; + } + + public bool HasAttribute (ICustomAttributeProvider provider, string type_name) { var attribs = GetAttributes (provider); for (int i = 0; i < attribs.Count; i++) From a6b6c3cddb41ce3eeafe9211c697de86df43330b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 2 Jun 2026 16:55:37 +0200 Subject: [PATCH 55/97] [tools] Use instance-based Log methods. (#25566) This will become important soon, because much of the code here will run from inside an MSBuild task, and we mustn't call Console.[Error.]WriteLine from inside an MSBuild task (it can cause tasks to deadlock). With these changes it'll be much easier to remap Log calls to MSBuild's Task.LogMessage method. --- msbuild/Xamarin.MacDev.Tasks/Decompress.cs | 2 +- msbuild/Xamarin.MacDev.Tasks/Tasks/BGen.cs | 4 +- .../Tasks/CreateBindingResourcePackage.cs | 2 +- .../Tasks/MergeAppBundles.cs | 6 +- .../Tasks/ParseBundlerArguments.cs | 4 +- .../Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs | 80 ++++---- .../Xamarin.MacDev.Tasks/VerbosityUtils.cs | 41 ++-- .../Xamarin.MacDev.Tasks.csproj | 3 + msbuild/Xamarin.Shared/Xamarin.Shared.targets | 4 +- src/ObjCRuntime/DynamicRegistrar.cs | 2 +- src/ObjCRuntime/Registrar.cs | 9 +- src/bgen/BindingTouch.cs | 46 ++++- src/bgen/bgen.csproj | 1 + tests/dotnet/UnitTests/BundleStructureTest.cs | 4 +- tests/dotnet/UnitTests/DotNetUnitTests.csproj | 3 + .../TaskTests/ParseBundlerArgumentsTests.cs | 2 +- .../VerbosityTest.cs | 16 +- tools/common/Application.cs | 65 ++++-- tools/common/Assembly.cs | 98 ++++----- tools/common/CoreResolver.cs | 20 +- tools/common/Driver.cs | 127 ++++-------- tools/common/Driver.execution.cs | 77 ++++--- tools/common/ErrorHelper.tools.cs | 59 +++--- tools/common/FileCopier.cs | 191 +++++++----------- tools/common/Frameworks.cs | 2 +- tools/common/IToolLog.cs | 68 +++++++ tools/common/MachO.cs | 42 ++-- tools/common/PInvokeWrapperGenerator.cs | 4 +- tools/common/StaticRegistrar.cs | 32 +-- tools/common/Target.cs | 12 +- tools/common/cache.cs | 139 +++++-------- .../dotnet-linker/BackingFieldDelayHandler.cs | 7 +- tools/dotnet-linker/DotNetResolver.cs | 4 + tools/dotnet-linker/LinkerConfiguration.cs | 100 ++++----- .../PreserveSmartEnumConversionsStep.cs | 11 +- .../Steps/ExceptionalMarkHandler.cs | 2 + .../Steps/InlineClassGetHandleStep.cs | 20 +- .../Steps/InlineDlfcnMethodsStep.cs | 6 +- .../Steps/ManagedRegistrarLookupTablesStep.cs | 4 +- .../Steps/ManagedRegistrarStep.cs | 2 +- tools/dotnet-linker/dotnet-linker.csproj | 3 + tools/linker/CoreOptimizeGeneratedCode.cs | 159 ++++++++------- .../MonoTouch.Tuner/ListExportedSymbols.cs | 6 +- tools/linker/RegistrarRemovalTrackingStep.cs | 4 +- tools/mtouch/AssemblyResolver.cs | 20 +- tools/mtouch/mtouch.csproj | 3 + 46 files changed, 754 insertions(+), 762 deletions(-) create mode 100644 tools/common/IToolLog.cs diff --git a/msbuild/Xamarin.MacDev.Tasks/Decompress.cs b/msbuild/Xamarin.MacDev.Tasks/Decompress.cs index 2d4f755378de..234eb49cd852 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Decompress.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Decompress.cs @@ -108,7 +108,7 @@ public static bool TryDecompress (XamarinTask task, string zip, string resource, var stampFile = decompressedResource.TrimEnd ('\\', '/') + ".stamp"; - if (FileCopier.IsUptodate (zip, stampFile, XamarinTask.GetFileCopierReportErrorCallback (log), XamarinTask.GetFileCopierLogCallback (log), check_stamp: false)) + if (FileCopier.IsUptodate (task, zip, stampFile, check_stamp: false)) return true; // We use 'unzip' to extract on !Windows, and System.IO.Compression to extract on Windows. diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/BGen.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/BGen.cs index 1dce72ab1386..cfa8c01e85cf 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/BGen.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/BGen.cs @@ -55,8 +55,6 @@ public class BGen : XamarinTask, ICancelableTask { public string ExtraArgs { get; set; } = string.Empty; - public int Verbosity { get; set; } - public string GeneratedSourcesDir { get; set; } = string.Empty; public string GeneratedSourcesFileList { get; set; } = string.Empty; @@ -229,7 +227,7 @@ public virtual List GenerateCommandLineArguments () } } - cmd.AddRange (VerbosityUtils.Merge (ExtraArgs, (LoggerVerbosity) Verbosity)); + VerbosityUtils.RenderVerbosity (cmd, Verbosity); return CommandLineArgumentBuilder.CreateResponseFile (this, ResponseFilePath, cmd, null); } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CreateBindingResourcePackage.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CreateBindingResourcePackage.cs index 17d859c5ae4d..722104a9e119 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CreateBindingResourcePackage.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CreateBindingResourcePackage.cs @@ -107,7 +107,7 @@ public override bool Execute () Log.LogMessage (MSBStrings.M0121, bindingResourcePath); Directory.CreateDirectory (bindingResourcePath); foreach (var nativeRef in NativeReferences) { - Xamarin.Bundler.FileCopier.UpdateDirectory (nativeRef.ItemSpec, bindingResourcePath, FileCopierReportErrorCallback, FileCopierLogCallback); + Xamarin.Bundler.FileCopier.UpdateDirectory (this, nativeRef.ItemSpec, bindingResourcePath); var bindingOutputPath = Path.Combine (bindingResourcePath, Path.GetFileName (nativeRef.ItemSpec)); if (Directory.Exists (bindingOutputPath)) { diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/MergeAppBundles.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/MergeAppBundles.cs index dd9f8bf3ad13..591180e66f26 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/MergeAppBundles.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/MergeAppBundles.cs @@ -215,7 +215,7 @@ public void CopyTo (string outputDirectory, string? subDirectory = null) } } else { Directory.CreateDirectory (Path.GetDirectoryName (outputFile)!); - if (!FileCopier.IsUptodate (FullPath, outputFile, Task.FileCopierReportErrorCallback, Task.FileCopierLogCallback)) + if (!FileCopier.IsUptodate (Task, FullPath, outputFile)) File.Copy (FullPath, outputFile, true); } @@ -246,7 +246,7 @@ public override bool Execute () sourceDirectory += Path.DirectorySeparatorChar; Log.LogMessage (MessageImportance.Low, $"Copying the single input directory {sourceDirectory} to {targetDirectory}"); - FileCopier.UpdateDirectory (sourceDirectory, targetDirectory, FileCopierReportErrorCallback, FileCopierLogCallback); + FileCopier.UpdateDirectory (this, sourceDirectory, targetDirectory); return !Log.HasLoggedErrors; } @@ -428,7 +428,7 @@ void MergeMachOFiles (string output, IList input) var sourceFiles = input.Select (v => v.FullPath).ToArray (); - if (FileCopier.IsUptodate (sourceFiles, new string [] { output }, FileCopierReportErrorCallback, FileCopierLogCallback)) + if (FileCopier.IsUptodate (this, sourceFiles, new string [] { output })) return; Log.LogMessage (MessageImportance.Low, $"Lipoing '{input [0].RelativePath}' for the merged app bundle from the following sources:\n\t{string.Join ("\n\t", input.Select (v => v.FullPath))}"); diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArguments.cs index 3da5e058b86a..3f2ba75108b7 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArguments.cs @@ -64,7 +64,7 @@ public class ParseBundlerArguments : XamarinTask { public string? SkipMarkingNSObjectsInUserAssemblies { get; set; } [Output] - public string? Verbosity { get; set; } + public string? BundlerVerbosity { get; set; } [Output] public string? Warn { get; set; } @@ -307,7 +307,7 @@ public override bool Execute () } if (verbosity.HasValue) - Verbosity = verbosity.Value.ToString (); + BundlerVerbosity = verbosity.Value.ToString (); } return !Log.HasLoggedErrors; diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs index 2ad8f996c312..3cd5a9386821 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs @@ -9,6 +9,7 @@ using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; +using Xamarin.Bundler; using Xamarin.Localization.MSBuild; using Xamarin.Messaging.Build.Client; using Xamarin.Utils; @@ -17,7 +18,7 @@ #nullable enable namespace Xamarin.MacDev.Tasks { - public abstract class XamarinTask : Task, IHasSessionId, ICustomLogger { + public abstract class XamarinTask : Task, IHasSessionId, ICustomLogger, IToolLog { public string SessionId { get; set; } = string.Empty; @@ -25,6 +26,16 @@ public abstract class XamarinTask : Task, IHasSessionId, ICustomLogger { public string SdkDevPath { get; set; } = string.Empty; + int? verbosity; + public int Verbosity { + get { + if (!verbosity.HasValue) + verbosity = VerbosityUtils.GetVerbosityLevel (Environment.CommandLine); + return verbosity.Value; + } + set => verbosity = value; + } + public string GetSdkDevPath () { if (string.IsNullOrEmpty (SdkDevPath)) { @@ -278,48 +289,6 @@ internal static bool CopyInputsToRemoteServerAsync (T task) where T : Task, I } } - internal protected static ReportErrorCallback GetFileCopierReportErrorCallback (TaskLoggingHelper log) - { - return new ReportErrorCallback ((int code, string format, object? [] arguments) => { - FileCopierReportErrorCallback (log, code, format, arguments); - }); - } - - internal protected static void FileCopierReportErrorCallback (TaskLoggingHelper log, int code, string format, params object? [] arguments) - { - log.LogError (format, arguments); - } - - protected void FileCopierReportErrorCallback (int code, string format, params object? [] arguments) - { - FileCopierReportErrorCallback (Log, code, format, arguments); - } - - internal protected static LogCallback GetFileCopierLogCallback (TaskLoggingHelper log) - { - return new LogCallback ((int min_verbosity, string format, object? [] arguments) => { - FileCopierLogCallback (log, min_verbosity, format, arguments); - }); - } - - protected static void FileCopierLogCallback (TaskLoggingHelper log, int min_verbosity, string format, params object? [] arguments) - { - MessageImportance importance; - if (min_verbosity <= 0) { - importance = MessageImportance.High; - } else if (min_verbosity <= 1) { - importance = MessageImportance.Normal; - } else { - importance = MessageImportance.Low; - } - log.LogMessage (importance, format, arguments); - } - - protected void FileCopierLogCallback (int min_verbosity, string format, params object? [] arguments) - { - FileCopierLogCallback (Log, min_verbosity, format, arguments); - } - protected string GetNonEmptyStringOrFallback (ITaskItem item, string metadataName, string fallbackValue, string? fallbackName = null, bool required = false) { return GetNonEmptyStringOrFallback (item, metadataName, out var _, fallbackValue, fallbackName, required); @@ -400,7 +369,8 @@ protected static string GetExecutable (List arguments, string toolName, #region Xamarin.MacDev.ICustomLogger void ICustomLogger.LogError (string message, Exception? ex) { - Log.LogError (message); + if (!string.IsNullOrEmpty (message)) + Log.LogError (message); if (ex is not null) Log.LogErrorFromException (ex); } @@ -420,5 +390,27 @@ void ICustomLogger.LogDebug (string messageFormat, params object? [] args) Log.LogMessage (MessageImportance.Low, messageFormat, args); } #endregion + + #region Xamarin.Bundler.IToolLog + void IToolLog.Log (string message) + { + ((ICustomLogger) this).LogInfo (message); + } + + void IToolLog.LogError (string message) + { + ((ICustomLogger) this).LogError (message, null); + } + + void IToolLog.LogException (Exception exception) + { + ((ICustomLogger) this).LogError ("", exception); + } + + void IToolLog.LogError (Exception exception) + { + ((ICustomLogger) this).LogError ("", exception); + } + #endregion } } diff --git a/msbuild/Xamarin.MacDev.Tasks/VerbosityUtils.cs b/msbuild/Xamarin.MacDev.Tasks/VerbosityUtils.cs index f0797ef078fd..02211f93bcc3 100644 --- a/msbuild/Xamarin.MacDev.Tasks/VerbosityUtils.cs +++ b/msbuild/Xamarin.MacDev.Tasks/VerbosityUtils.cs @@ -10,39 +10,28 @@ namespace Xamarin.MacDev.Tasks { // needs to be verbosity-aware public static class VerbosityUtils { - // verbosity can be set in multiple ways - // this makes it a consistent interpretation of them - static public string [] Merge (string extraArguments, LoggerVerbosity taskVerbosity) + public static void RenderVerbosity (IList arguments, int taskVerbosity) { - string [] result = Array.Empty (); - var empty_extra = String.IsNullOrEmpty (extraArguments); - // We give the priority to the extra arguments given to the tools - if (empty_extra || (!empty_extra && !extraArguments.Contains ("-q") && !extraArguments.Contains ("-v"))) { - // if nothing is specified fall back to msbuild settings - // first check if some were supplied on the command-line - result = GetVerbosityLevel (Environment.CommandLine); - // if not then use the default from the msbuild config files, which Visual Studio for Mac can override (to match the IDE setting for msbuild) - if (result.Length == 0) - result = GetVerbosityLevel (taskVerbosity); - } - return result; + if (taskVerbosity == 0) + return; + + for (var i = 0; i < Math.Abs (taskVerbosity); i++) + arguments.Add (taskVerbosity < 0 ? "-q" : "-v"); } // // This is an hack, since there can be multiple loggers. - // However it's the most common use case and `mtouch` logs - // are often the most important to gather and developers expect - // a single change (in verbosity) to do the job and be consistent in CI. + // However it should cover most use cases. // // msbuild argument format // -verbosity: Display this amount of information in the event log. // The available verbosity levels are: q[uiet], m[inimal], // n[ormal], d[etailed], and diag[nostic]. (Short form: -v) // - static public string [] GetVerbosityLevel (string commandLine) + public static int GetVerbosityLevel (string commandLine) { if (!StringUtils.TryParseArguments (commandLine, out var args, out _)) - return GetVerbosityLevel (LoggerVerbosity.Normal); + return 0; var hasBinaryLog = false; foreach (var arg in args) { @@ -99,20 +88,20 @@ static public string [] GetVerbosityLevel (string commandLine) // The values here come from: https://github.com/mono/monodevelop/blob/143f9b6617123a0841a5cc5a2a4e13b309535792/main/src/core/MonoDevelop.Projects.Formats.MSBuild/MonoDevelop.Projects.MSBuild.Shared/RemoteBuildEngineMessages.cs#L186 // Assume 'Normal (2)' is the default verbosity (no change), and the other values follow from there. - static public string [] GetVerbosityLevel (LoggerVerbosity v) + public static int GetVerbosityLevel (LoggerVerbosity v) { switch ((LoggerVerbosity) v) { case LoggerVerbosity.Quiet: - return new [] { "-q", "-q", "-q", "-q" }; + return -4; case LoggerVerbosity.Minimal: - return new [] { "-q", "-q" }; + return -2; case LoggerVerbosity.Normal: default: - return Array.Empty (); + return 0; case LoggerVerbosity.Detailed: - return new [] { "-v", "-v" }; + return 2; case LoggerVerbosity.Diagnostic: - return new [] { "-v", "-v", "-v", "-v" }; + return 4; } } } diff --git a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj index 8f4555f6dfd4..713db306ebef 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj +++ b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj @@ -75,6 +75,9 @@ JsonExtensions.cs + + IToolLog.cs + StringUtils.cs diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 2c5f43be44b8..8307a85ebf48 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -2298,7 +2298,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. NoDSymUtil="$(NoDSymUtil)" PackageDebugSymbols="$(PackageDebugSymbols)" Registrar="$(Registrar)" - Verbosity="$(_BundlerVerbosity)" + BundlerVerbosity="$(_BundlerVerbosity)" > @@ -2315,7 +2315,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. - + diff --git a/src/ObjCRuntime/DynamicRegistrar.cs b/src/ObjCRuntime/DynamicRegistrar.cs index 93dc571ced6b..0c27e3d8130f 100644 --- a/src/ObjCRuntime/DynamicRegistrar.cs +++ b/src/ObjCRuntime/DynamicRegistrar.cs @@ -806,7 +806,7 @@ protected override bool TryGetAttribute (Type type, string attributeNamespace, s return attribute is not null; } - protected override void ReportError (int code, string message, params object [] args) + protected override void ReportError (int code, string message, params object? [] args) { Runtime.NSLog (String.Format (message, args)); } diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index 3b7c402bd3cf..d33f63746e84 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -2741,8 +2741,9 @@ protected string ToSignature (TType type, ObjCMember? member, ref bool success, return "#"; if (IsINativeObject (type)) { - if (!IsGenericType (type) && !IsInterface (type) && !IsNSObject (type) && IsAbstract (type)) - ErrorHelper.Show (CreateWarning (4179, member, Errors.MT4179, type.FullName, member?.FullName)); + if (!IsGenericType (type) && !IsInterface (type) && !IsNSObject (type) && IsAbstract (type)) { + ReportWarning (4179, Errors.MT4179, type.FullName, member?.FullName); + } if (IsNSObject (type) && forProperty) { return "@\"" + GetExportedTypeName (type) + "\""; } else { @@ -2811,7 +2812,7 @@ internal static void NSLog (string format, params object [] args) } #endif - protected virtual void ReportError (int code, string message, params object [] args) + protected virtual void ReportError (int code, string message, params object? [] args) { // Using Console.WriteLine here is error prone, since if we get an early error // we'll end up crashing/infinite recursion since Console.WriteLine is redirected @@ -2820,7 +2821,7 @@ protected virtual void ReportError (int code, string message, params object [] a R.NSLog (String.Format (message, args)); } - protected virtual void ReportWarning (int code, string message, params object [] args) + protected virtual void ReportWarning (int code, string message, params object? [] args) { // Using Console.WriteLine here is error prone, since if we get an early error // we'll end up crashing/infinite recursion since Console.WriteLine is redirected diff --git a/src/bgen/BindingTouch.cs b/src/bgen/BindingTouch.cs index 5afd87e41b01..72aa883e0985 100644 --- a/src/bgen/BindingTouch.cs +++ b/src/bgen/BindingTouch.cs @@ -42,7 +42,7 @@ using System.Threading; #endif -public class BindingTouch : IDisposable { +public class BindingTouch : IDisposable, IToolLog { public static ApplePlatform [] AllPlatforms = new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }; public static PlatformName [] AllPlatformNames = new PlatformName [] { PlatformName.iOS, PlatformName.MacOSX, PlatformName.TvOS, PlatformName.MacCatalyst }; public PlatformName CurrentPlatform; @@ -156,8 +156,8 @@ public bool TryCreateOptionSet (BindingTouchConfig config, string [] args) { "d=", "Defines a symbol", v => config.Defines.Add (v) }, { "api=", "Adds a API definition source file", v => config.ApiSources.Add (v) }, { "s=", "Adds a source file required to build the API", v => config.CoreSources.Add (v) }, - { "q", "Quiet", v => ErrorHelper.Verbosity-- }, - { "v", "Sets verbose mode", v => ErrorHelper.Verbosity++ }, + { "q", "Quiet", v => Verbosity-- }, + { "v", "Sets verbose mode", v => Verbosity++ }, { "x=", "Adds the specified file to the build, used after the core files are compiled", v => config.ExtraSources.Add (v) }, { "e", "Generates smaller classes that can not be subclassed (previously called 'external mode')", v => config.IsExternal = true }, { "p", "Sets private mode", v => config.IsPublicMode = false }, @@ -522,7 +522,7 @@ void Compile (List arguments, int errorCode, string? tmpdir) arguments.Insert (i - 1, compile_command [i]); } - if (Driver.RunCommand (compile_command [0], arguments, null, out var compile_output, true, Driver.Verbosity) != 0) + if (Driver.RunCommand (this, compile_command [0], arguments, null, out var compile_output, true, Verbosity) != 0) throw ErrorHelper.CreateError (errorCode, $"{compiler} {StringUtils.FormatArguments (arguments)}\n{compile_output}".Replace ("\n", "\n\t")); var output = string.Join (Environment.NewLine, compile_output.ToString ().Split (new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries)); if (!string.IsNullOrEmpty (output)) @@ -537,10 +537,10 @@ bool TryLoadApi (string? name, [NotNullWhen (true)] out Assembly? assembly) try { assembly = universe?.LoadFromAssemblyPath (name); } catch (Exception e) { - if (Driver.Verbosity > 0) - Console.WriteLine (e); + if (Verbosity > 0) + Log (e.ToString ()); - Console.Error.WriteLine ("Error loading {0}", name); + LogError ($"Error loading {name}"); } return assembly is not null; @@ -575,4 +575,36 @@ public void Dispose () Dispose (disposing: true); GC.SuppressFinalize (this); } + + public void Log (string message) + { + Console.WriteLine (message); + } + + public void LogError (string message) + { + Console.Error.WriteLine (message); + } + + public void LogError (Exception exception) + { + ErrorHelper.Show (exception); + } + + public void LogException (Exception exception) + { + ErrorHelper.Show (exception); + } + + int verbosity = 0; + public int Verbosity { + get => verbosity; + set => verbosity = value; + } +} + +namespace Xamarin.Bundler { + public partial class Driver { + public static int GetDefaultVerbosity () => 0; + } } diff --git a/src/bgen/bgen.csproj b/src/bgen/bgen.csproj index 8e7a65e7d227..fa063bd5b43a 100644 --- a/src/bgen/bgen.csproj +++ b/src/bgen/bgen.csproj @@ -36,6 +36,7 @@ + diff --git a/tests/dotnet/UnitTests/BundleStructureTest.cs b/tests/dotnet/UnitTests/BundleStructureTest.cs index f01bbb58fd5f..56d6ae67f259 100644 --- a/tests/dotnet/UnitTests/BundleStructureTest.cs +++ b/tests/dotnet/UnitTests/BundleStructureTest.cs @@ -1,5 +1,7 @@ #nullable enable +using Xamarin.Bundler; + namespace Xamarin.Tests { [TestFixture] public class BundleStructureTest : TestBaseClass { @@ -793,7 +795,7 @@ static void AssertLibraryArchitectures (string appBundle, string [] runtimeIdent return false; }); foreach (var lib in libraries) { - var libArchitectures = renderArchitectures (MachO.GetArchitectures (lib)); + var libArchitectures = renderArchitectures (MachO.GetArchitectures (ConsoleLog.Instance, lib)); Assert.That (libArchitectures, Is.EqualTo (expectedArchitectures), $"Architectures in {lib}"); } } diff --git a/tests/dotnet/UnitTests/DotNetUnitTests.csproj b/tests/dotnet/UnitTests/DotNetUnitTests.csproj index f78948890a0e..e0bbb37b7ce5 100644 --- a/tests/dotnet/UnitTests/DotNetUnitTests.csproj +++ b/tests/dotnet/UnitTests/DotNetUnitTests.csproj @@ -45,6 +45,9 @@ external\Cache.cs + + external\IToolLog.cs + external\TargetFramework.cs diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ParseBundlerArgumentsTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ParseBundlerArgumentsTests.cs index bea30b23b970..48ea288f44ad 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ParseBundlerArgumentsTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ParseBundlerArgumentsTests.cs @@ -250,7 +250,7 @@ public void Verbosity (string input, string output) var task = CreateTask (); task.ExtraArgs = input; ExecuteTask (task, message: input); - Assert.That (task.Verbosity, Is.EqualTo (output), "Equality"); + Assert.That (task.BundlerVerbosity, Is.EqualTo (output), "Equality"); } [TestCase ("--nowarn", "-1")] diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/VerbosityTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/VerbosityTest.cs index 87765cf7267e..4930ef8dc74c 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/VerbosityTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/VerbosityTest.cs @@ -43,15 +43,15 @@ public void FromString (string commandLine, LoggerVerbosity expected) Assert.That (VerbosityUtils.GetVerbosityLevel (commandLine), Is.EqualTo (result), commandLine); } - [TestCase (LoggerVerbosity.Quiet, "-q -q -q -q")] - [TestCase (LoggerVerbosity.Minimal, "-q -q")] - [TestCase (LoggerVerbosity.Normal, "")] - [TestCase (LoggerVerbosity.Detailed, "-v -v")] - [TestCase (LoggerVerbosity.Diagnostic, "-v -v -v -v")] - [TestCase ((LoggerVerbosity) (-1), "")] - public void FromLoggerVerbosity (LoggerVerbosity v, string expectedResult) + [TestCase (LoggerVerbosity.Quiet, -4)] + [TestCase (LoggerVerbosity.Minimal, -2)] + [TestCase (LoggerVerbosity.Normal, 0)] + [TestCase (LoggerVerbosity.Detailed, 2)] + [TestCase (LoggerVerbosity.Diagnostic, 4)] + [TestCase ((LoggerVerbosity) (-1), 0)] + public void FromLoggerVerbosity (LoggerVerbosity v, int expectedResult) { - var s = String.Join (" ", VerbosityUtils.GetVerbosityLevel (v)); + var s = VerbosityUtils.GetVerbosityLevel (v); Assert.That (s, Is.EqualTo (expectedResult), v.ToString ()); } } diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 56f008d19b66..6aad8900c70c 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -68,7 +68,7 @@ public enum RegistrarMode { TrimmableStatic, } - public partial class Application { + public partial class Application : IToolLog { public Cache? Cache; public string AppDirectory = "."; public bool DeadStrip = true; @@ -268,7 +268,7 @@ public Version GetMacCatalystiOSVersion (Version macOSVersion) #if LEGACY_TOOLS if (macOSVersion.Major >= 26 && Driver.SdkRoot is null) { // this shouldn't happen for normal builds, nor for customers, so just show an internal 99 warning. - ErrorHelper.Warning (99, Errors.MX0099, $"No Xcode configured, assuming the macOS version {macOSVersion} is identical to the Mac Catalyst/iOS version."); + ErrorHelper.Warning (this, 99, Errors.MX0099, $"No Xcode configured, assuming the macOS version {macOSVersion} is identical to the Mac Catalyst/iOS version."); return macOSVersion; } #endif @@ -286,6 +286,7 @@ public Application (LinkerConfiguration configuration) this.LinkContext = new Tuner.DerivedLinkContext (configuration, this); #endif this.StaticRegistrar = new StaticRegistrar (this); + this.Resolver = new PlatformResolver (this); } #if !LEGACY_TOOLS @@ -419,9 +420,9 @@ public string PlatformName { } } - public static bool IsUptodate (string source, string target, bool check_contents = false, bool check_stamp = true) + public static bool IsUptodate (IToolLog log, string source, string target, bool check_contents = false, bool check_stamp = true) { - return FileCopier.IsUptodate (source, target, check_contents, check_stamp); + return FileCopier.IsUptodate (log, source, target, check_contents, check_stamp); } public static void RemoveResource (ModuleDefinition module, string name) @@ -493,14 +494,14 @@ public static bool ExtractResource (ModuleDefinition module, string name, string // // If check_stamp is true, the function will use the timestamp of a "target".stamp file // if it's later than the timestamp of the "target" file itself. - public static bool IsUptodate (IEnumerable sources, IEnumerable targets, bool check_stamp = true) + public static bool IsUptodate (IToolLog log, IEnumerable sources, IEnumerable targets, bool check_stamp = true) { - return FileCopier.IsUptodate (sources, targets, check_stamp); + return FileCopier.IsUptodate (log, sources, targets, check_stamp); } - public static void UpdateDirectory (string source, string target) + public static void UpdateDirectory (IToolLog log, string source, string target) { - FileCopier.UpdateDirectory (source, target); + FileCopier.UpdateDirectory (log, source, target); } public void InitializeCommon () @@ -539,13 +540,13 @@ public void InitializeCommon () if (!package_managed_debug_symbols.HasValue) { package_managed_debug_symbols = EnableDebug; } else if (package_managed_debug_symbols.Value && IsLLVM) { - ErrorHelper.Warning (3007, Errors.MX3007); + ErrorHelper.Warning (this, 3007, Errors.MX3007); } Optimizations.Initialize (this, out var messages); - ErrorHelper.Show (messages); - if (Driver.Verbosity > 3) - Driver.Log (4, $"Enabled optimizations: {Optimizations}"); + ErrorHelper.Show (this, messages); + if (this.Verbosity > 3) + this.Log (4, $"Enabled optimizations: {Optimizations}"); } void InitializeDeploymentTarget () @@ -575,7 +576,7 @@ public void RunRegistrar () throw ErrorHelper.CreateError (99, "RegistrarOutputLibrary must be specified."); var RootAssembly = RootAssemblies [0]; var resolvedAssemblies = new Dictionary (); - var resolver = new PlatformResolver () { + var resolver = new PlatformResolver (this) { RootDirectory = Path.GetDirectoryName (RootAssembly), }; resolver.Configure (); @@ -583,7 +584,7 @@ public void RunRegistrar () var ps = new ReaderParameters (); ps.AssemblyResolver = resolver; foreach (var reference in References) { - var r = resolver.Load (reference); + var r = resolver.Load (this, reference); if (r is null) throw ErrorHelper.CreateError (2002, Errors.MT2002, reference); } @@ -598,21 +599,21 @@ public void RunRegistrar () try { AssemblyDefinition lastAssembly = ps.AssemblyResolver.Resolve (AssemblyNameReference.Parse (rootName), new ReaderParameters ()); if (lastAssembly is null) { - ErrorHelper.Warning (7, Errors.MX0007, rootName); + ErrorHelper.Warning (this, 7, Errors.MX0007, rootName); continue; } if (resolvedAssemblies.TryGetValue (rootName, out var previousAssembly)) { if (lastAssembly.MainModule.RuntimeVersion != previousAssembly.MainModule.RuntimeVersion) { - Driver.Log (2, "Attemping to load an assembly another time {0} (previous {1})", lastAssembly.FullName, previousAssembly.FullName); + this.Log (2, "Attemping to load an assembly another time {0} (previous {1})", lastAssembly.FullName, previousAssembly.FullName); } continue; } resolvedAssemblies.Add (rootName, lastAssembly); - Driver.Log (3, "Loaded {0}", lastAssembly.MainModule.FileName); + this.Log (3, "Loaded {0}", lastAssembly.MainModule.FileName); } catch (Exception ex) { - ErrorHelper.Warning (9, ex, Errors.MX0009, $"{rootName}: {ex.Message}"); + ErrorHelper.Warning (this, 9, ex, Errors.MX0009, $"{rootName}: {ex.Message}"); continue; } } @@ -1191,7 +1192,7 @@ public bool VerifyDynamicFramework (string framework_path) } if (!dynamic) - Driver.Log (1, "The framework {0} is a framework of static libraries, and will not be copied to the app.", framework_path); + this.Log (1, "The framework {0} is a framework of static libraries, and will not be copied to the app.", framework_path); return dynamic; } @@ -1209,5 +1210,31 @@ public static void SetDefaultHiddenWarnings () ErrorHelper.ParseWarningLevel (ErrorHelper.WarningLevel.Disable, "4189"); // The class '{0}' will not be registered because it has been removed from the {1} SDK. ErrorHelper.ParseWarningLevel (ErrorHelper.WarningLevel.Disable, "4190"); // The class '{0}' will not be registered because the {1} framework has been deprecated from the {2} SDK. } + + public void Log (string message) + { + Console.WriteLine (message); + } + + public void LogError (string message) + { + Console.Error.WriteLine (message); + } + + public void LogError (Exception exception) + { + ErrorHelper.Show (this, exception); + } + + public void LogException (Exception exception) + { + ErrorHelper.Show (this, exception); + } + + int verbosity = Driver.GetDefaultVerbosity (); + public int Verbosity { + get => verbosity; + set => verbosity = value; + } } } diff --git a/tools/common/Assembly.cs b/tools/common/Assembly.cs index 1ec7535fa828..6ad8d9141958 100644 --- a/tools/common/Assembly.cs +++ b/tools/common/Assembly.cs @@ -69,7 +69,7 @@ public string FullPath { #if !LEGACY_TOOLS is_framework_assembly = App.Configuration.FrameworkAssemblies.Contains (GetIdentity (full_path)); #else - var real_full_path = Application.GetRealPath (full_path); + var real_full_path = Application.GetRealPath (App, full_path); is_framework_assembly = real_full_path.StartsWith (Path.GetDirectoryName (Path.GetDirectoryName (App.Resolver.FrameworkDirectory))!, StringComparison.Ordinal); #endif } @@ -132,7 +132,7 @@ public void LoadSymbols () } } catch { // do not let stale file crash us - Driver.Log (3, "Invalid debugging symbols for {0} ignored", FullPath); + App.Log (3, "Invalid debugging symbols for {0} ignored", FullPath); } } @@ -155,12 +155,12 @@ public void ExtractNativeLinkInfo () string resourceBundlePath = Path.ChangeExtension (FullPath, ".resources"); if (Directory.Exists (resourceBundlePath)) { - Driver.Log (3, $"Found a binding resource package for the assembly '{FullPath}' in {resourceBundlePath}, so not looking for any libraries embedded in the assembly."); + App.Log (3, $"Found a binding resource package for the assembly '{FullPath}' in {resourceBundlePath}, so not looking for any libraries embedded in the assembly."); return; } var zipPath = resourceBundlePath + ".zip"; if (File.Exists (zipPath)) { - Driver.Log (3, $"Found a binding resource package for the assembly '{FullPath}' in {zipPath}, so not looking for any libraries embedded in the assembly."); + App.Log (3, $"Found a binding resource package for the assembly '{FullPath}' in {zipPath}, so not looking for any libraries embedded in the assembly."); return; } @@ -242,7 +242,7 @@ void ProcessNativeReferenceOptions (NativeReferenceMetadata metadata) { // We can't add -dead_strip if there are any LinkWith attributes where smart linking is disabled. if (!metadata.SmartLink) { - Driver.Log (3, $"The library '{metadata.LibraryName}', shipped with the assembly '{FullPath}', sets SmartLink=false, which will disable passing -dead_strip to the native linker (and make the app bigger)."); + App.Log (3, $"The library '{metadata.LibraryName}', shipped with the assembly '{FullPath}', sets SmartLink=false, which will disable passing -dead_strip to the native linker (and make the app bigger)."); App.DeadStrip = false; } @@ -290,23 +290,23 @@ bool TryExtractNativeLibrary (AssemblyDefinition assembly, NativeReferenceMetada library = null; return false; } - var path = Path.Combine (App.Cache.Location, metadata.LibraryName); + var path = Path.Combine (App.Cache.GetLocation (App), metadata.LibraryName); library = null; - if (!Application.IsUptodate (FullPath, path)) { + if (!Application.IsUptodate (App, FullPath, path)) { if (!Application.ExtractResource (assembly.MainModule, metadata.LibraryName, path, false)) { - ErrorHelper.Warning (1308, Errors.MX1308 /* Could not extract the native library '{0}' from the assembly '{1}', because it doesn't contain the resource '{2}'. */, metadata.LibraryName, FullPath, metadata.LibraryName); + ErrorHelper.Warning (App, 1308, Errors.MX1308 /* Could not extract the native library '{0}' from the assembly '{1}', because it doesn't contain the resource '{2}'. */, metadata.LibraryName, FullPath, metadata.LibraryName); return false; } - Driver.Log (3, "Extracted third-party binding '{0}' from '{1}' to '{2}'", metadata.LibraryName, FullPath, path); + App.Log (3, "Extracted third-party binding '{0}' from '{1}' to '{2}'", metadata.LibraryName, FullPath, path); LogNativeReference (metadata); } else { - Driver.Log (3, "Target '{0}' is up-to-date.", path); + App.Log (3, "Target '{0}' is up-to-date.", path); } if (!File.Exists (path)) - ErrorHelper.Warning (1302, Errors.MT1302, metadata.LibraryName, path); + ErrorHelper.Warning (App, 1302, Errors.MT1302, metadata.LibraryName, path); library = path; return true; @@ -318,39 +318,39 @@ bool TryExtractFramework (AssemblyDefinition assembly, NativeReferenceMetadata m framework = null; return false; } - var path = Path.Combine (App.Cache.Location, metadata.LibraryName); + var path = Path.Combine (App.Cache.GetLocation (App), metadata.LibraryName); var zipPath = path + ".zip"; framework = null; - if (!Application.IsUptodate (FullPath, zipPath)) { + if (!Application.IsUptodate (App, FullPath, zipPath)) { if (!Application.ExtractResource (assembly.MainModule, metadata.LibraryName, zipPath, false)) { - ErrorHelper.Warning (1307, Errors.MX1307 /* Could not extract the native framework '{0}' from the assembly '{1}', because it doesn't contain the resource '{2}'. */, metadata.LibraryName, FullPath, metadata.LibraryName); + ErrorHelper.Warning (App, 1307, Errors.MX1307 /* Could not extract the native framework '{0}' from the assembly '{1}', because it doesn't contain the resource '{2}'. */, metadata.LibraryName, FullPath, metadata.LibraryName); return false; } - Driver.Log (3, "Extracted third-party framework '{0}' from '{1}' to '{2}'", metadata.LibraryName, FullPath, zipPath); + App.Log (3, "Extracted third-party framework '{0}' from '{1}' to '{2}'", metadata.LibraryName, FullPath, zipPath); LogNativeReference (metadata); } else { - Driver.Log (3, "Target '{0}' is up-to-date.", path); + App.Log (3, "Target '{0}' is up-to-date.", path); } if (!File.Exists (zipPath)) { - ErrorHelper.Warning (1302, Errors.MT1302, metadata.LibraryName, FullPath); + ErrorHelper.Warning (App, 1302, Errors.MT1302, metadata.LibraryName, FullPath); if (assembly.MainModule.HasResources) { - Driver.Log (3, $"The assembly {FullPath} has {assembly.MainModule.Resources.Count} resources:"); + App.Log (3, $"The assembly {FullPath} has {assembly.MainModule.Resources.Count} resources:"); foreach (var res in assembly.MainModule.Resources) { - Driver.Log (3, $" {res.ResourceType}: {res.Name}"); + App.Log (3, $" {res.ResourceType}: {res.Name}"); } } else { - Driver.Log (3, $"The assembly {FullPath} does not have any resources."); + App.Log (3, $"The assembly {FullPath} does not have any resources."); } } else { if (!Directory.Exists (path)) Directory.CreateDirectory (path); - if (Driver.RunCommand ("/usr/bin/unzip", "-u", "-o", "-d", path, zipPath) != 0) + if (Driver.RunCommand (App, "/usr/bin/unzip", "-u", "-o", "-d", path, zipPath) != 0) throw ErrorHelper.CreateError (1303, Errors.MT1303, metadata.LibraryName, zipPath); } @@ -358,19 +358,19 @@ bool TryExtractFramework (AssemblyDefinition assembly, NativeReferenceMetadata m return true; } - static void LogNativeReference (NativeReferenceMetadata metadata) + void LogNativeReference (NativeReferenceMetadata metadata) { - Driver.Log (3, " LibraryName: {0}", metadata.LibraryName); - Driver.Log (3, " From: {0}", metadata.Attribute is not null ? "LinkWith" : "Binding Manifest"); - Driver.Log (3, " ForceLoad: {0}", metadata.ForceLoad); - Driver.Log (3, " Frameworks: {0}", metadata.Frameworks); - Driver.Log (3, " IsCxx: {0}", metadata.IsCxx); - Driver.Log (3, " LinkWithSwiftSystemLibraries: {0}", metadata.LinkWithSwiftSystemLibraries); - Driver.Log (3, " LinkerFlags: {0}", metadata.LinkerFlags); - Driver.Log (3, " LinkTarget: {0}", metadata.LinkTarget); - Driver.Log (3, " NeedsGccExceptionHandling: {0}", metadata.NeedsGccExceptionHandling); - Driver.Log (3, " SmartLink: {0}", metadata.SmartLink); - Driver.Log (3, " WeakFrameworks: {0}", metadata.WeakFrameworks); + App.Log (3, " LibraryName: {0}", metadata.LibraryName); + App.Log (3, " From: {0}", metadata.Attribute is not null ? "LinkWith" : "Binding Manifest"); + App.Log (3, " ForceLoad: {0}", metadata.ForceLoad); + App.Log (3, " Frameworks: {0}", metadata.Frameworks); + App.Log (3, " IsCxx: {0}", metadata.IsCxx); + App.Log (3, " LinkWithSwiftSystemLibraries: {0}", metadata.LinkWithSwiftSystemLibraries); + App.Log (3, " LinkerFlags: {0}", metadata.LinkerFlags); + App.Log (3, " LinkTarget: {0}", metadata.LinkTarget); + App.Log (3, " NeedsGccExceptionHandling: {0}", metadata.NeedsGccExceptionHandling); + App.Log (3, " SmartLink: {0}", metadata.SmartLink); + App.Log (3, " WeakFrameworks: {0}", metadata.WeakFrameworks); } public static LinkWithAttribute GetLinkWithAttribute (CustomAttribute attr) @@ -438,12 +438,12 @@ void AddFramework (string file) { if (Driver.GetFrameworks (App).TryGetValue (file, out var framework)) { if (framework.IsFrameworkUnavailable (App)) { - ErrorHelper.Warning (182, Errors.MX0182 /* Not linking with the framework {0} (referenced by a module reference in {1}) because it's not available on the current platform ({2}). */, framework.Name, FileName, App.PlatformName); + ErrorHelper.Warning (App, 182, Errors.MX0182 /* Not linking with the framework {0} (referenced by a module reference in {1}) because it's not available on the current platform ({2}). */, framework.Name, FileName, App.PlatformName); return; } if (framework.Version > App.SdkVersion) { - ErrorHelper.Warning (135, Errors.MX0135, file, FileName, App.PlatformName, framework.Version, App.SdkVersion); + ErrorHelper.Warning (App, 135, Errors.MX0135, file, FileName, App.PlatformName, framework.Version, App.SdkVersion); return; } } @@ -451,10 +451,10 @@ void AddFramework (string file) var strong = (framework is null) || (App.DeploymentTarget >= (App.IsSimulatorBuild ? framework.VersionAvailableInSimulator ?? framework.Version : framework.Version)); if (strong) { if (Frameworks.Add (file)) - Driver.Log (3, "Linking with the framework {0} because it's referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking with the framework {0} because it's referenced by a module reference in {1}", file, FileName); } else { if (WeakFrameworks.Add (file)) - Driver.Log (3, "Linking (weakly) with the framework {0} because it's referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking (weakly) with the framework {0} because it's referenced by a module reference in {1}", file, FileName); } } @@ -491,7 +491,7 @@ public void ComputeLinkerFlags () string file = Path.GetFileNameWithoutExtension (name); if (App.IsFrameworkUnavailable (file)) { - Driver.Log (3, "Not linking with {0} (referenced by a module reference in {1}) because it's not available in the current SDK.", file, FileName); + App.Log (3, "Not linking with {0} (referenced by a module reference in {1}) because it's not available in the current SDK.", file, FileName); continue; } @@ -510,12 +510,12 @@ public void ComputeLinkerFlags () break; case "sqlite3": LinkerFlags.Add ("-lsqlite3"); - Driver.Log (3, "Linking with {0} because it's referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking with {0} because it's referenced by a module reference in {1}", file, FileName); break; case "libsqlite3": // remove lib prefix LinkerFlags.Add ("-l" + file.Substring (3)); - Driver.Log (3, "Linking with {0} because it's referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking with {0} because it's referenced by a module reference in {1}", file, FileName); break; case "libcompression": LinkerFlags.Add (GetCompressionLinkingFlag ()); @@ -524,22 +524,22 @@ public void ComputeLinkerFlags () case "libGLESv2": // special case for OpenGLES.framework if (Frameworks.Add ("OpenGLES")) - Driver.Log (3, "Linking with the framework OpenGLES because {0} is referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking with the framework OpenGLES because {0} is referenced by a module reference in {1}", file, FileName); break; case "vImage": case "vecLib": // sub-frameworks if (Frameworks.Add ("Accelerate")) - Driver.Log (3, "Linking with the framework Accelerate because {0} is referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking with the framework Accelerate because {0} is referenced by a module reference in {1}", file, FileName); break; case "openal32": if (Frameworks.Add ("OpenAL")) - Driver.Log (3, "Linking with the framework OpenAL because {0} is referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking with the framework OpenAL because {0} is referenced by a module reference in {1}", file, FileName); break; #if !LEGACY_TOOLS case "Carbon": if (App.Platform != ApplePlatform.MacOSX) { - Driver.Log (3, $"Not linking with the framework {file} (referenced by a module reference in {FileName}) because it doesn't exist on the target platform."); + App.Log (3, $"Not linking with the framework {file} (referenced by a module reference in {FileName}) because it doesn't exist on the target platform."); break; } break; @@ -553,13 +553,13 @@ public void ComputeLinkerFlags () // CoreServices has multiple sub-frameworks that can be used by customer code if (path.StartsWith ("/System/Library/Frameworks/CoreServices.framework/", StringComparison.Ordinal)) { if (Frameworks.Add ("CoreServices")) - Driver.Log (3, "Linking with the framework CoreServices because {0} is referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking with the framework CoreServices because {0} is referenced by a module reference in {1}", file, FileName); break; } // ApplicationServices has multiple sub-frameworks that can be used by customer code if (path.StartsWith ("/System/Library/Frameworks/ApplicationServices.framework/", StringComparison.Ordinal)) { if (Frameworks.Add ("ApplicationServices")) - Driver.Log (3, "Linking with the framework ApplicationServices because {0} is referenced by a module reference in {1}", file, FileName); + App.Log (3, "Linking with the framework ApplicationServices because {0} is referenced by a module reference in {1}", file, FileName); break; } } @@ -572,7 +572,7 @@ public void ComputeLinkerFlags () if (UnresolvedModuleReferences is null) UnresolvedModuleReferences = new HashSet (); UnresolvedModuleReferences.Add (mr); - Driver.Log (3, "Could not resolve the module reference {0} in {1}", file, FileName); + App.Log (3, "Could not resolve the module reference {0} in {1}", file, FileName); } break; } @@ -698,14 +698,14 @@ public void Update (Application app, IEnumerable assemblies) // new assembly var asm = new Assembly (app, assembly); Add (asm); - Driver.Log (1, "The linker added the assembly '{0}' to '{1}' to satisfy a reference.", asm.Identity, app.Name); + app.Log (1, "The linker added the assembly '{0}' to '{1}' to satisfy a reference.", asm.Identity, app.Name); } else { this [identity].AssemblyDefinition = assembly; } } foreach (var removed in current) { - Driver.Log (1, "The linker removed the assembly '{0}' from '{1}' since there is no more reference to it.", this [removed].Identity, app.Name); + app.Log (1, "The linker removed the assembly '{0}' from '{1}' since there is no more reference to it.", this [removed].Identity, app.Name); Remove (removed); } } diff --git a/tools/common/CoreResolver.cs b/tools/common/CoreResolver.cs index 458da5cf086c..b35f575c9fc1 100644 --- a/tools/common/CoreResolver.cs +++ b/tools/common/CoreResolver.cs @@ -60,7 +60,7 @@ protected ReaderParameters CreateDefaultReaderParameters (string path) return parameters; } - public virtual AssemblyDefinition? Load (string fileName) + public virtual AssemblyDefinition? Load (IToolLog log, string fileName) { if (!File.Exists (fileName)) return null; @@ -70,7 +70,7 @@ protected ReaderParameters CreateDefaultReaderParameters (string path) return assembly; try { - fileName = Application.GetRealPath (fileName); + fileName = Application.GetRealPath (log, fileName); // Check the architecture-specific directory if (Path.GetDirectoryName (fileName) == FrameworkDirectory && !string.IsNullOrEmpty (ArchDirectory)) { @@ -89,14 +89,14 @@ protected ReaderParameters CreateDefaultReaderParameters (string path) // Warn about this. var pdb = Path.ChangeExtension (fileName, "pdb"); if (File.Exists (pdb)) - ErrorHelper.Show (ErrorHelper.CreateWarning (178, Errors.MX0178, fileName)); + ErrorHelper.Show (log, ErrorHelper.CreateWarning (178, Errors.MX0178, fileName)); } // Don't load native .pdb symbols, because we won't be able to write them back out again (so just drop them) if (assembly.MainModule?.SymbolReader?.GetType ()?.FullName == "Mono.Cecil.Pdb.NativePdbReader") { parameters.ReadSymbols = false; parameters.SymbolReaderProvider = null; assembly = ModuleDefinition.ReadModule (fileName, parameters).Assembly; - ErrorHelper.Show (ErrorHelper.CreateWarning (178, Errors.MX0178, fileName)); + ErrorHelper.Show (log, ErrorHelper.CreateWarning (178, Errors.MX0178, fileName)); } } catch (IOException ex) when (ex.GetType ().FullName == "Microsoft.Cci.Pdb.PdbException") { // Microsoft.Cci.Pdb.PdbException is not public, so we have to check the runtime type :/ symbolLoadFailure = true; @@ -108,7 +108,7 @@ protected ReaderParameters CreateDefaultReaderParameters (string path) parameters.SymbolReaderProvider = null; assembly = ModuleDefinition.ReadModule (fileName, parameters).Assembly; // only report the warning (on symbols) if we can actually load the assembly itself (otherwise it's more confusing than helpful) - ErrorHelper.Show (ErrorHelper.CreateWarning (129, Errors.MX0129, fileName)); + ErrorHelper.Show (log, ErrorHelper.CreateWarning (129, Errors.MX0129, fileName)); } } catch (Exception e) { throw new ProductException (9, true, e, Errors.MX0009, fileName); @@ -126,23 +126,23 @@ public AssemblyDefinition CacheAssembly (AssemblyDefinition assembly) return assembly; } - protected AssemblyDefinition? SearchDirectory (string name, string directory, string extension = ".dll") + protected AssemblyDefinition? SearchDirectory (IToolLog log, string name, string directory, string extension = ".dll") { if (!Directory.Exists (directory)) return null; - var file = DirectoryGetFile (directory, name + extension); + var file = DirectoryGetFile (log, directory, name + extension); if (file.Length > 0) - return Load (file); + return Load (log, file); return null; } - static string DirectoryGetFile (string directory, string file) + static string DirectoryGetFile (IToolLog log, string directory, string file) { var files = Directory.GetFiles (directory, file); if (files is not null && files.Length > 0) { if (files.Length > 1) { - ErrorHelper.Warning (133, Errors.MX0133, file, Environment.NewLine, string.Join ("\n", files)); + ErrorHelper.Warning (log, 133, Errors.MX0133, file, Environment.NewLine, string.Join ("\n", files)); } return files [0]; } diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index 5ff3ad4d8fe9..c04f81cd0221 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -27,12 +27,12 @@ public static int Main (string [] args) { try { Console.OutputEncoding = new UTF8Encoding (false, false); - SetCurrentLanguage (); + SetCurrentLanguage (ConsoleLog.Instance); return Main2 (args); } catch (Exception e) { - ErrorHelper.Show (e); + ErrorHelper.Show (ConsoleLog.Instance, e); } finally { - Watch ("Total time", 0); + Watch (ConsoleLog.Instance, "Total time", 0); } return 0; } @@ -40,8 +40,8 @@ public static int Main (string [] args) // Returns true if the process should exit (with a 0 exit code; failures are propagated using exceptions) static void ParseOptions (Application app, Mono.Options.OptionSet options, string [] args) { - options.Add ("v|verbose", "Specify how verbose the output should be. This can be passed multiple times to increase the verbosity.", v => Verbosity++); - options.Add ("q|quiet", "Specify how quiet the output should be. This can be passed multiple times to increase the silence.", v => Verbosity--); + options.Add ("v|verbose", "Specify how verbose the output should be. This can be passed multiple times to increase the verbosity.", v => app.Verbosity++); + options.Add ("q|quiet", "Specify how quiet the output should be. This can be passed multiple times to increase the silence.", v => app.Verbosity--); options.Add ("reference=", "Add an assembly to be processed.", v => app.References.Add (v)); options.Add ("sdkroot=", "Specify the location of Apple SDKs, default to 'xcode-select' value.", v => sdk_root = v); options.Add ("sdk=", "Specifies the SDK version to compile against (version, for example \"10.9\"). For Mac Catalyst, this is the macOS version of the SDK.", v => { @@ -85,17 +85,7 @@ static void ParseOptions (Application app, Mono.Options.OptionSet options, strin } #endif // !LEGACY_TOOLS - public static int Verbosity { - get { return ErrorHelper.Verbosity; } - set { ErrorHelper.Verbosity = value; } - } - - static Driver () - { - Verbosity = GetDefaultVerbosity (); - } - - static int GetDefaultVerbosity () + public static int GetDefaultVerbosity () { var v = 0; var fn = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), $".{NAME}-verbosity"); @@ -107,35 +97,6 @@ static int GetDefaultVerbosity () return v; } - public static void Log (string value) - { - Log (0, value); - } - - public static void Log (string format, params object? [] args) - { - Log (0, format, args); - } - - public static void Log (int min_verbosity, string value) - { - if (min_verbosity > Verbosity) - return; - - Console.WriteLine (value); - } - - public static void Log (int min_verbosity, string format, params object? [] args) - { - if (min_verbosity > Verbosity) - return; - - if (args.Length > 0) - Console.WriteLine (format, args); - else - Console.WriteLine (format); - } - static TargetFramework targetFramework; public static TargetFramework TargetFramework { @@ -149,7 +110,7 @@ static void FileMove (string source, string target) File.Move (source, target); } - static void MoveIfDifferent (string path, string tmp, bool use_stamp = false) + static void MoveIfDifferent (IToolLog log, string path, string tmp, bool use_stamp = false) { // Don't read the entire file into memory, it can be quite big in certain cases. @@ -158,10 +119,10 @@ static void MoveIfDifferent (string path, string tmp, bool use_stamp = false) using (var fs1 = new FileStream (path, FileMode.Open, FileAccess.Read)) { using (var fs2 = new FileStream (tmp, FileMode.Open, FileAccess.Read)) { if (fs1.Length != fs2.Length) { - Log (3, "New file '{0}' has different length, writing new file.", path); + log.Log (3, "New file '{0}' has different length, writing new file.", path); move = true; } else { - move = !Cache.CompareStreams (fs1, fs2); + move = !Cache.CompareStreams (log, fs1, fs2); } } } @@ -169,13 +130,13 @@ static void MoveIfDifferent (string path, string tmp, bool use_stamp = false) if (move) { FileMove (tmp, path); } else { - Log (3, "Target {0} is up-to-date.", path); + log.Log (3, "Target {0} is up-to-date.", path); if (use_stamp) - Driver.Touch (path + ".stamp"); + Driver.Touch (log, path + ".stamp"); } } - public static void WriteIfDifferent (string path, string contents, bool use_stamp = false) + public static void WriteIfDifferent (IToolLog log, string path, string contents, bool use_stamp = false) { var tmp = path + ".tmp"; @@ -185,36 +146,36 @@ public static void WriteIfDifferent (string path, string contents, bool use_stam if (!string.IsNullOrEmpty (dir)) Directory.CreateDirectory (dir); File.WriteAllText (path, contents); - Log (3, "File '{0}' does not exist, creating it.", path); + log.Log (3, "File '{0}' does not exist, creating it.", path); return; } File.WriteAllText (tmp, contents); - MoveIfDifferent (path, tmp, use_stamp); + MoveIfDifferent (log, path, tmp, use_stamp); } catch (Exception e) { File.WriteAllText (path, contents); - ErrorHelper.Warning (1014, e, Errors.MT1014, path, e.Message); + ErrorHelper.Warning (log, 1014, e, Errors.MT1014, path, e.Message); } finally { File.Delete (tmp); } } - public static void WriteIfDifferent (string path, byte [] contents, bool use_stamp = false) + public static void WriteIfDifferent (IToolLog log, string path, byte [] contents, bool use_stamp = false) { var tmp = path + ".tmp"; try { if (!File.Exists (path)) { File.WriteAllBytes (path, contents); - Log (3, "File '{0}' does not exist, creating it.", path); + log.Log (3, "File '{0}' does not exist, creating it.", path); return; } File.WriteAllBytes (tmp, contents); - MoveIfDifferent (path, tmp, use_stamp); + MoveIfDifferent (log, path, tmp, use_stamp); } catch (Exception e) { File.WriteAllBytes (path, contents); - ErrorHelper.Warning (1014, e, Errors.MT1014, path, e.Message); + ErrorHelper.Warning (log, 1014, e, Errors.MT1014, path, e.Message); } finally { File.Delete (tmp); } @@ -244,7 +205,7 @@ public static Version XcodeVersion { } } - static void SetCurrentLanguage () + static void SetCurrentLanguage (IToolLog log) { // There's no way to change the current culture from the command-line // without changing the system settings, so honor LANG if set. @@ -269,14 +230,14 @@ static void SetCurrentLanguage () var culture = CultureInfo.GetCultureInfo (lang); if (culture is not null) { CultureInfo.DefaultThreadCurrentCulture = culture; - Log (2, $"The current language was set to '{culture.DisplayName}' according to the LANG environment variable (LANG={lang_variable})."); + log.Log (2, $"The current language was set to '{culture.DisplayName}' according to the LANG environment variable (LANG={lang_variable})."); } } catch (Exception e) { - ErrorHelper.Warning (124, e, Errors.MT0124, lang, lang_variable, e.Message); + ErrorHelper.Warning (log, 124, e, Errors.MT0124, lang, lang_variable, e.Message); } } - public static void Touch (IEnumerable filenames, DateTime? timestamp = null) + public static void Touch (IToolLog log, IEnumerable filenames, DateTime? timestamp = null) { if (timestamp is null) timestamp = DateTime.Now; @@ -290,14 +251,14 @@ public static void Touch (IEnumerable filenames, DateTime? timestamp = n } fi.LastWriteTime = timestamp.Value; } catch (Exception e) { - ErrorHelper.Warning (128, Errors.MT0128, filename, e.Message); + ErrorHelper.Warning (log, 128, Errors.MT0128, filename, e.Message); } } } - public static void Touch (params string [] filenames) + public static void Touch (IToolLog log, params string [] filenames) { - Touch ((IEnumerable) filenames); + Touch (log, (IEnumerable) filenames); } static int watch_level; @@ -314,13 +275,11 @@ public static int WatchLevel { } } - public static void Watch (string msg, int level) + public static void Watch (IToolLog log, string msg, int level) { if ((watch is null) || (level > WatchLevel)) return; - for (int i = 0; i < level; i++) - Console.Write ("!"); - Console.WriteLine ("Timestamp {0}: {1} ms", msg, watch.ElapsedMilliseconds); + log.Log ($"{new string ('!', level)}Timestamp {msg}: {watch.ElapsedMilliseconds} ms"); } internal static PDictionary? FromPList (string name) @@ -332,11 +291,11 @@ public static void Watch (string msg, int level) const string XcodeDefault = "/Applications/Xcode.app"; - static string? FindSystemXcode () + static string? FindSystemXcode (IToolLog log) { var output = new StringBuilder (); - if (Driver.RunCommand ("xcode-select", new [] { "-p" }, output: output) != 0) { - ErrorHelper.Warning (59, Errors.MX0059, output.ToString ()); + if (Driver.RunCommand (log, "xcode-select", new [] { "-p" }, output: output) != 0) { + ErrorHelper.Warning (log, 59, Errors.MX0059, output.ToString ()); return null; } return output.ToString ().Trim (); @@ -427,31 +386,31 @@ public static string GetProductAssembly (Application app) public static void ValidateXcode (Application app, bool accept_any_xcode_version, bool warn_if_not_found) { if (sdk_root is null) { - sdk_root = FindSystemXcode (); + sdk_root = FindSystemXcode (app); if (sdk_root is null) { // FindSystemXcode showed a warning in this case. In particular do not use 'string.IsNullOrEmpty' here, // because FindSystemXcode may return an empty string (with no warning printed) if the xcode-select command // succeeds, but returns nothing. sdk_root = null; } else if (!Directory.Exists (sdk_root)) { - ErrorHelper.Warning (60, Errors.MX0060, sdk_root); + ErrorHelper.Warning (app, 60, Errors.MX0060, sdk_root); sdk_root = null; } else { if (!accept_any_xcode_version) - ErrorHelper.Warning (61, Errors.MT0061, sdk_root); + ErrorHelper.Warning (app, 61, Errors.MT0061, sdk_root); } if (sdk_root is null) { sdk_root = XcodeDefault; if (!Directory.Exists (sdk_root)) { if (warn_if_not_found) { // mmp: and now we give up, but don't throw like mtouch, because we don't want to change behavior (this sometimes worked it appears) - ErrorHelper.Warning (56, Errors.MX0056); + ErrorHelper.Warning (app, 56, Errors.MX0056); return; // Can't validate the version below if we can't even find Xcode... } throw ErrorHelper.CreateError (56, Errors.MX0056); } - ErrorHelper.Warning (62, Errors.MT0062, sdk_root); + ErrorHelper.Warning (app, 62, Errors.MT0062, sdk_root); } } else if (!Directory.Exists (sdk_root)) { throw ErrorHelper.CreateError (55, Errors.MT0055, sdk_root); @@ -481,7 +440,7 @@ public static void ValidateXcode (Application app, bool accept_any_xcode_version throw ErrorHelper.CreateError (58, Errors.MT0058, Path.GetDirectoryName (Path.GetDirectoryName (DeveloperDirectory)), plist_path); } - Driver.Log (1, "Using Xcode {0} ({2}) found in {1}", XcodeVersion, sdk_root, XcodeProductVersion); + app.Log (1, "Using Xcode {0} ({2}) found in {1}", XcodeVersion, sdk_root, XcodeProductVersion); } internal static bool TryParseBool (string value, out bool result) @@ -607,7 +566,7 @@ static bool XcrunFind (Application app, ApplePlatform platform, bool is_simulato // We also want to print out what happened if something went wrong, and in that case we don't want stdout // and stderr captured separately, because related lines could end up printed far from eachother in time, // and that's confusing. So capture stdout and stderr by themselves, and also capture both together. - int ret = RunCommand ("xcrun", args, env, + int ret = RunCommand (app, "xcrun", args, env, (v) => { lock (both) { both.AppendLine (v); @@ -624,11 +583,11 @@ static bool XcrunFind (Application app, ApplePlatform platform, bool is_simulato if (ret == 0) { path = stdout.ToString ().Trim (); if (!File.Exists (path)) { - ErrorHelper.Warning (5315, Errors.MX5315 /* The tool xcrun failed to return a valid result (the file {0} does not exist). Check build log for details. */, tool, path); + ErrorHelper.Warning (app, 5315, Errors.MX5315 /* The tool xcrun failed to return a valid result (the file {0} does not exist). Check build log for details. */, tool, path); return false; } } else { - Log (1, "Failed to locate the developer tool '{0}', 'xcrun {1}' returned with the exit code {2}:\n{3}", tool, string.Join (" ", args), ret, both.ToString ()); + app.Log (1, "Failed to locate the developer tool '{0}', 'xcrun {1}' returned with the exit code {2}:\n{3}", tool, string.Join (" ", args), ret, both.ToString ()); } return ret == 0; @@ -642,7 +601,7 @@ public static void RunXcodeTool (Application app, string tool, params string [] public static void RunXcodeTool (Application app, string tool, IList arguments) { var executable = FindTool (app, tool); - var rv = RunCommand (executable, arguments); + var rv = RunCommand (app, executable, arguments); if (rv != 0) throw ErrorHelper.CreateError (5309, Errors.MX5309 /* Failed to execute the tool '{0}', it failed with an error code '{1}'. Please check the build log for details. */, tool, rv); } @@ -695,7 +654,7 @@ public static void RunLipoAndCreateDsym (Application app, string output, IEnumer if (Directory.Exists (outputDsymDir)) Directory.Delete (outputDsymDir, true); Directory.Move (dsymFolders [0], outputDsymDir); - RunCommand ("/usr/bin/mdimport", outputDsymDir); + RunCommand (app, "/usr/bin/mdimport", outputDsymDir); } } @@ -707,7 +666,7 @@ public static void RunLipo (Application app, IList options) public static void CreateDsym (Application app, string output_dir, string appname, string dsym_dir) { RunDsymUtil (app, Path.Combine (output_dir, appname), "-num-threads", "4", "-z", "-o", dsym_dir); - RunCommand ("/usr/bin/mdimport", dsym_dir); + RunCommand (app, "/usr/bin/mdimport", dsym_dir); } public static void RunDsymUtil (Application app, params string [] options) diff --git a/tools/common/Driver.execution.cs b/tools/common/Driver.execution.cs index bb576ccec230..72c09d3a3d7f 100644 --- a/tools/common/Driver.execution.cs +++ b/tools/common/Driver.execution.cs @@ -18,76 +18,76 @@ namespace Xamarin.Bundler { public partial class Driver { - public static int RunCommand (string path, IList args, Dictionary? env, out StringBuilder output, bool suppressPrintOnErrors, int verbose) + public static int RunCommand (IToolLog log, string path, IList args, Dictionary? env, out StringBuilder output, bool suppressPrintOnErrors, int verbose) { output = new StringBuilder (); - return RunCommand (path, args, env, output, suppressPrintOnErrors, verbose); + return RunCommand (log, path, args, env, output, suppressPrintOnErrors, verbose); } - public static int RunCommand (string path, IList args, Dictionary? env, out StringBuilder output, bool suppressPrintOnErrors) + public static int RunCommand (IToolLog log, string path, IList args, Dictionary? env, out StringBuilder output, bool suppressPrintOnErrors) { output = new StringBuilder (); - return RunCommand (path, args, env, output, suppressPrintOnErrors, Verbosity); + return RunCommand (log, path, args, env, output, suppressPrintOnErrors, log.Verbosity); } - public static int RunCommand (string path, IList args, Dictionary? env, StringBuilder output, bool suppressPrintOnErrors, int verbosity) + public static int RunCommand (IToolLog log, string path, IList args, Dictionary? env, StringBuilder output, bool suppressPrintOnErrors, int verbosity) { - return RunCommand (path, args, env, output, output, suppressPrintOnErrors, verbosity); + return RunCommand (log, path, args, env, output, output, suppressPrintOnErrors, verbosity); } - public static int RunCommand (string path, IList args, Dictionary? env, StringBuilder output, bool suppressPrintOnErrors = false) + public static int RunCommand (IToolLog log, string path, IList args, Dictionary? env, StringBuilder output, bool suppressPrintOnErrors = false) { - return RunCommand (path, args, env, output, output, suppressPrintOnErrors, Verbosity); + return RunCommand (log, path, args, env, output, output, suppressPrintOnErrors, log.Verbosity); } - public static int RunCommand (string path, params string [] args) + public static int RunCommand (IToolLog log, string path, params string [] args) { - return RunCommand (path, args, null, (Action?) null, (Action?) null, false, Verbosity); + return RunCommand (log, path, args, null, (Action?) null, (Action?) null, false, log.Verbosity); } - public static int RunCommand (string path, IList args) + public static int RunCommand (IToolLog log, string path, IList args) { - return RunCommand (path, args, null, (Action?) null, (Action?) null, false, Verbosity); + return RunCommand (log, path, args, null, (Action?) null, (Action?) null, false, log.Verbosity); } - public static int RunCommand (string path, IList args, StringBuilder? output) + public static int RunCommand (IToolLog log, string path, IList args, StringBuilder? output) { - return RunCommand (path, args, null, output, output, false, Verbosity); + return RunCommand (log, path, args, null, output, output, false, log.Verbosity); } - public static int RunCommand (string path, IList args, StringBuilder? output, bool suppressPrintOnErrors) + public static int RunCommand (IToolLog log, string path, IList args, StringBuilder? output, bool suppressPrintOnErrors) { - return RunCommand (path, args, null, output, output, suppressPrintOnErrors, Verbosity); + return RunCommand (log, path, args, null, output, output, suppressPrintOnErrors, log.Verbosity); } - public static int RunCommand (string path, IList args, StringBuilder? output, StringBuilder? error) + public static int RunCommand (IToolLog log, string path, IList args, StringBuilder? output, StringBuilder? error) { - return RunCommand (path, args, null, output, error, false, Verbosity); + return RunCommand (log, path, args, null, output, error, false, log.Verbosity); } - public static int RunCommand (string path, IList args, StringBuilder? output, StringBuilder? error, bool suppressPrintOnErrors) + public static int RunCommand (IToolLog log, string path, IList args, StringBuilder? output, StringBuilder? error, bool suppressPrintOnErrors) { - return RunCommand (path, args, null, output, error, suppressPrintOnErrors, Verbosity); + return RunCommand (log, path, args, null, output, error, suppressPrintOnErrors, log.Verbosity); } - public static int RunCommand (string path, IList args, Dictionary? env, StringBuilder? output, StringBuilder? error, bool suppressPrintOnErrors, int verbosity) + public static int RunCommand (IToolLog log, string path, IList args, Dictionary? env, StringBuilder? output, StringBuilder? error, bool suppressPrintOnErrors, int verbosity) { var output_received = output is null ? null : new Action ((v) => { if (v is not null) output.AppendLine (v); }); var error_received = error is null ? null : new Action ((v) => { if (v is not null) error.AppendLine (v); }); - return RunCommand (path, args, env, output_received, error_received, suppressPrintOnErrors, verbosity); + return RunCommand (log, path, args, env, output_received, error_received, suppressPrintOnErrors, verbosity); } - static int RunCommand (string path, IList args, Dictionary? env, Action? output_received, bool suppressPrintOnErrors) + static int RunCommand (IToolLog log, string path, IList args, Dictionary? env, Action? output_received, bool suppressPrintOnErrors) { - return RunCommand (path, args, env, output_received, output_received, suppressPrintOnErrors, Verbosity); + return RunCommand (log, path, args, env, output_received, output_received, suppressPrintOnErrors, log.Verbosity); } - static int RunCommand (string path, IList args, Dictionary? env, Action? output_received, Action? error_received) + static int RunCommand (IToolLog log, string path, IList args, Dictionary? env, Action? output_received, Action? error_received) { - return RunCommand (path, args, env, output_received, error_received, false, Verbosity); + return RunCommand (log, path, args, env, output_received, error_received, false, log.Verbosity); } - static int RunCommand (string path, IList args, Dictionary? env, Action? output_received, Action? error_received, bool suppressPrintOnErrors, int verbosity) + static int RunCommand (IToolLog log, string path, IList args, Dictionary? env, Action? output_received, Action? error_received, bool suppressPrintOnErrors, int verbosity) { var output = new StringBuilder (); var outputCallback = new Action ((line) => { @@ -103,8 +103,7 @@ static int RunCommand (string path, IList args, Dictionary 0) - Console.WriteLine ($"{path} {StringUtils.FormatArguments (args)}"); + log.Log (1, $"{path} {StringUtils.FormatArguments (args)}"); var p = Execution.RunWithCallbacksAsync (path, args, env, outputCallback, errorCallback).Result; @@ -118,33 +117,27 @@ static int RunCommand (string path, IList args, Dictionary 0 && output?.Length > 0 && !suppressPrintOnErrors) { - Console.WriteLine (output.ToString ()); + log.Log (output.ToString ()); } return p.ExitCode; } - public static Task RunCommandAsync (string path, IList args, Dictionary? env = null, Action? output_received = null, bool suppressPrintOnErrors = false, int? verbosity = null) + public static Task RunCommandAsync (IToolLog log, string path, IList args, Dictionary? env = null, Action? output_received = null, bool suppressPrintOnErrors = false, int? verbosity = null) { - return Task.Run (() => RunCommand (path, args, env, output_received, output_received, suppressPrintOnErrors, verbosity ?? Verbosity)); + return Task.Run (() => RunCommand (log, path, args, env, output_received, output_received, suppressPrintOnErrors, verbosity ?? log.Verbosity)); } - public static Task RunCommandAsync (string path, IList args, Dictionary? env = null, StringBuilder? output = null, bool suppressPrintOnErrors = false, int? verbosity = null) + public static Task RunCommandAsync (IToolLog log, string path, IList args, Dictionary? env = null, StringBuilder? output = null, bool suppressPrintOnErrors = false, int? verbosity = null) { - return Task.Run (() => RunCommand (path, args, env, output, output, suppressPrintOnErrors, verbosity ?? Verbosity)); + return Task.Run (() => RunCommand (log, path, args, env, output, output, suppressPrintOnErrors, verbosity ?? log.Verbosity)); } - -#if BGENERATOR - internal static int Verbosity => ErrorHelper.Verbosity; -#elif !LEGACY_TOOLS && !BUNDLER - internal static int Verbosity; -#endif } } diff --git a/tools/common/ErrorHelper.tools.cs b/tools/common/ErrorHelper.tools.cs index cbfef0ddb7d0..275f0d04a894 100644 --- a/tools/common/ErrorHelper.tools.cs +++ b/tools/common/ErrorHelper.tools.cs @@ -39,7 +39,6 @@ public enum WarningLevel { } static Dictionary? warning_levels; - public static int Verbosity { get; set; } #pragma warning disable 649 public static Func? IsExpectedException; @@ -253,18 +252,18 @@ public static ProductException Create (Application app, int code, bool error, Ex return e; } - public static void Warning (int code, string message, params object [] args) + public static void Warning (IToolLog log, int code, string message, params object [] args) { - Show (new ProductException (code, false, message, args)); + Show (log, new ProductException (code, false, message, args)); } - public static void Warning (int code, Exception innerException, string message, params object [] args) + public static void Warning (IToolLog log, int code, Exception innerException, string message, params object [] args) { - Show (new ProductException (code, false, innerException, message, args)); + Show (log, new ProductException (code, false, innerException, message, args)); } // Shows any warnings, and if there are any errors, throws an AggregateException. - public static void ThrowIfErrors (IList exceptions) + public static void ThrowIfErrors (IToolLog log, IList exceptions) { if (exceptions?.Any () != true) return; @@ -274,28 +273,28 @@ public static void ThrowIfErrors (IList exceptions) var warnings = grouped.SingleOrDefault ((v) => v.Key); if (warnings?.Any () == true) - Show (warnings); + Show (log, warnings); var errors = grouped.SingleOrDefault ((v) => !v.Key); if (errors?.Any () == true) throw new AggregateException (errors); } - public static void Show (IEnumerable list) + public static void Show (IToolLog log, IEnumerable list) { var exceptions = CollectExceptions (list); bool error = false; foreach (var ex in exceptions) - error |= ShowInternal (ex); + error |= ShowInternal (log, ex); if (error) Exit (1); } - public static void Show (Exception e) + public static void Show (IToolLog log, Exception e) { - Show (new Exception [] { e }); + Show (log, new Exception [] { e }); } static void Exit (int exitCode) @@ -305,7 +304,7 @@ static void Exit (int exitCode) Environment.Exit (exitCode); } - static bool ShowInternal (Exception e) + static bool ShowInternal (IToolLog log, Exception e) { var mte = e as ProductException; bool error = true; @@ -316,39 +315,39 @@ static bool ShowInternal (Exception e) if (!error && GetWarningLevel (mte.Code) == WarningLevel.Disable) return false; // This is an ignored warning. - Console.Error.WriteLine (mte.ToString ()); + log.LogError (mte.ToString ()); - ShowInner (e); + ShowInner (log, e); - if (Verbosity > 2 && !string.IsNullOrEmpty (e.StackTrace)) - Console.Error.WriteLine (e.StackTrace); + if (log.Verbosity > 2 && !string.IsNullOrEmpty (e.StackTrace)) + log.LogError (e.StackTrace); } else if (IsExpectedException is null || !IsExpectedException (e)) { - Console.Error.WriteLine ("error " + Prefix + "0000: Unexpected error - Please file a bug report at https://github.com/dotnet/macios/issues/new"); - Console.Error.WriteLine (e.ToString ()); + log.LogError ("error " + Prefix + "0000: Unexpected error - Please file a bug report at https://github.com/dotnet/macios/issues/new"); + log.LogError (e.ToString ()); } else { - Console.Error.WriteLine (e.ToString ()); - ShowInner (e); - if (Verbosity > 2 && !string.IsNullOrEmpty (e.StackTrace)) - Console.Error.WriteLine (e.StackTrace); + log.LogError (e.ToString ()); + ShowInner (log, e); + if (log.Verbosity > 2 && !string.IsNullOrEmpty (e.StackTrace)) + log.LogError (e.StackTrace); } return error; } - static void ShowInner (Exception e) + static void ShowInner (IToolLog log, Exception e) { var ie = e.InnerException; if (ie is null) return; - if (Verbosity > 3) { - Console.Error.WriteLine ("--- inner exception"); - Console.Error.WriteLine (ie); - Console.Error.WriteLine ("---"); - } else if (Verbosity > 0 || ie is ProductException) { - Console.Error.WriteLine ("\t{0}", ie.Message); + if (log.Verbosity > 3) { + log.LogError ("--- inner exception"); + log.LogError (ie.ToString ()); + log.LogError ("---"); + } else if (log.Verbosity > 0 || ie is ProductException) { + log.LogError ($"\t{ie.Message}"); } - ShowInner (ie); + ShowInner (log, ie); } } } diff --git a/tools/common/FileCopier.cs b/tools/common/FileCopier.cs index 06d1176453bc..d10e93b59827 100644 --- a/tools/common/FileCopier.cs +++ b/tools/common/FileCopier.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -27,6 +28,7 @@ enum CopyFileFlags : uint { enum CopyFileState : uint { StatusCB = 6, + StatusCtx = 7, } enum CopyFileStep { @@ -57,81 +59,39 @@ enum CopyFileWhat { [DllImport ("/usr/lib/libSystem.dylib")] static extern int copyfile_state_free (IntPtr state); +#if NET [DllImport ("/usr/lib/libSystem.dylib")] - static extern int copyfile_state_set (IntPtr state, CopyFileState flag, IntPtr value); - + static extern unsafe int copyfile_state_set (IntPtr state, CopyFileState flag, delegate* unmanaged value); +#else delegate CopyFileResult CopyFileCallbackDelegate (CopyFileWhat what, CopyFileStep stage, IntPtr state, string src, string dst, IntPtr ctx); +#endif + + [DllImport ("/usr/lib/libSystem.dylib")] + static extern unsafe int copyfile_state_set (IntPtr state, CopyFileState flag, IntPtr value); [DllImport ("/usr/lib/libSystem.dylib", SetLastError = true)] static extern int copyfile (string @from, string @to, IntPtr state, CopyFileFlags flags); - // This code is shared between our packaging tools (mmp\mtouch) and msbuild tasks - public delegate void LogCallback (int verbosity, string format, params object? [] arguments); - public delegate void ReportErrorCallback (int code, string format, params object? [] arguments); - - [ThreadStatic] - static LogCallback? logCallback; - - [ThreadStatic] - static ReportErrorCallback? reportErrorCallback; - - static void Log (int min_verbosity, string format, params object? [] arguments) + static void ReportError (IToolLog log, int code, string format, params object? [] arguments) { - if (logCallback is not null) { - logCallback (min_verbosity, format, arguments); - return; - } - #if LEGACY_TOOLS || BUNDLER - // LogMessage and LogError are instance objects on the tasks themselves and bubbling an event up is not ideal - Driver.Log (min_verbosity, format, arguments); + log.LogError (ErrorHelper.CreateError (code, format, arguments)); #else - Console.WriteLine (format, arguments); + log.LogError (new Exception ($"{code} {string.Format (format, arguments)}")); #endif } - static void ReportError (int code, string format, params object? [] arguments) - { - if (reportErrorCallback is not null) { - reportErrorCallback (code, format, arguments); - return; - } - -#if LEGACY_TOOLS || BUNDLER - throw ErrorHelper.CreateError (code, format, arguments); -#else - // msbuild handles uncaught exceptions as a task error - throw new Exception ($"{code} {string.Format (format, arguments)}"); -#endif - } - - public static void UpdateDirectory (string source, string target, ReportErrorCallback reportErrorCallback, LogCallback logCallback) - { - try { - FileCopier.reportErrorCallback = reportErrorCallback; - FileCopier.logCallback = logCallback; - UpdateDirectory (source, target); - } finally { - FileCopier.reportErrorCallback = null; - FileCopier.logCallback = null; - } - } - -#if LEGACY_TOOLS || BUNDLER - public static void UpdateDirectory (string source, string target) -#else - static void UpdateDirectory (string source, string target) -#endif + public static void UpdateDirectory (IToolLog log, string source, string target) { // first chance, try to update existing content inside `target` - if (TryUpdateDirectory (source, target, out var err)) + if (TryUpdateDirectory (log, source, target, out var err)) return; // 2nd chance, remove `target` then copy everything - Log (1, "Could not update `{0}` content (error #{1} : {2}), trying to overwrite everything...", target, err, strerror (err)); + log.Log (1, "Could not update `{0}` content (error #{1} : {2}), trying to overwrite everything...", target, err, strerror (err)); Directory.Delete (target, true); - if (!TryUpdateDirectory (source, target, out err)) - ReportError (1022, Errors.MT1022, source, target, err, strerror (err)); + if (!TryUpdateDirectory (log, source, target, out err)) + ReportError (log, 1022, Errors.MT1022, source, target, err, strerror (err)); } static bool? use_managed_copying; @@ -150,16 +110,16 @@ static bool UseManagedCopying { } } - static bool TryUpdateDirectory (string source, string target, out int errno) + static bool TryUpdateDirectory (IToolLog log, string source, string target, out int errno) { if (UseManagedCopying) - return TryUpdateDirectoryWindows (source, target, out errno); - return TryUpdateDirectoryMacOS (source, target, out errno); + return TryUpdateDirectoryWindows (log, source, target, out errno); + return TryUpdateDirectoryMacOS (log, source, target, out errno); } - static bool TryUpdateDirectoryWindows (string source, string target, out int errno) + static bool TryUpdateDirectoryWindows (IToolLog log, string source, string target, out int errno) { - Log (1, $"Copying {source} to {target} recursively"); + log.Log (1, $"Copying {source} to {target} recursively"); errno = 0; Directory.CreateDirectory (target); @@ -170,39 +130,47 @@ static bool TryUpdateDirectoryWindows (string source, string target, out int err foreach (var sourceFile in dir.GetFiles ()) { var sourcePath = sourceFile.FullName; var targetPath = Path.Combine (target, Path.GetFileName (source), sourceFile.Name); - CopyIfNeeded (sourcePath, targetPath); + CopyIfNeeded (log, sourcePath, targetPath); } foreach (var subdir in dir.GetDirectories ()) { - rv &= TryUpdateDirectoryWindows (Path.Combine (source, subdir.Name), Path.Combine (target, Path.GetFileName (source)), out errno); + rv &= TryUpdateDirectoryWindows (log, Path.Combine (source, subdir.Name), Path.Combine (target, Path.GetFileName (source)), out errno); } } else { var targetPath = Path.Combine (target, Path.GetFileName (source)); - CopyIfNeeded (source, targetPath); + CopyIfNeeded (log, source, targetPath); } return rv; } - static void CopyIfNeeded (string source, string target) + static void CopyIfNeeded (IToolLog log, string source, string target) { - if (IsUptodate (source, target)) { - Log (3, "Target '{0}' is up-to-date", target); + if (IsUptodate (log, source, target)) { + log.Log (3, "Target '{0}' is up-to-date", target); } else { Directory.CreateDirectory (Path.GetDirectoryName (target)!); File.Copy (source, target, true); - Log (1, "Copied {0} to {1}", source, target); + log.Log (1, "Copied {0} to {1}", source, target); } } - static bool TryUpdateDirectoryMacOS (string source, string target, out int errno) + static bool TryUpdateDirectoryMacOS (IToolLog log, string source, string target, out int errno) { Directory.CreateDirectory (target); // Mono's File.Copy can't handle symlinks (the symlinks are followed instead of copied), // so we need to use native functions directly. Luckily OSX provides exactly what we need. IntPtr state = copyfile_state_alloc (); + var logHandle = GCHandle.Alloc (log); try { +#if NET + unsafe { + copyfile_state_set (state, CopyFileState.StatusCB, &CopyFileCallback); + } +#else CopyFileCallbackDelegate del = CopyFileCallback; copyfile_state_set (state, CopyFileState.StatusCB, Marshal.GetFunctionPointerForDelegate (del)); +#endif + copyfile_state_set (state, CopyFileState.StatusCtx, (IntPtr) logHandle); int rv = copyfile (source, target, state, CopyFileFlags.Data | CopyFileFlags.Recursive | CopyFileFlags.Nofollow | CopyFileFlags.Clone); if (rv == 0) { errno = 0; // satisfy compiler and make sure not to pick up some older error code @@ -212,25 +180,36 @@ static bool TryUpdateDirectoryMacOS (string source, string target, out int errno return false; } } finally { + logHandle.Free (); copyfile_state_free (state); } } // do not call `Marshal.GetLastWin32Error` inside this method since it's called while the p/invoke is executing and will return `260` +#if NET + [UnmanagedCallersOnly] + static CopyFileResult CopyFileCallback (CopyFileWhat what, CopyFileStep stage, IntPtr state, IntPtr sourcePtr, IntPtr targetPtr, IntPtr ctx) + { + var source = Marshal.PtrToStringUTF8 (sourcePtr)!; + var target = Marshal.PtrToStringUTF8 (targetPtr)!; +#else static CopyFileResult CopyFileCallback (CopyFileWhat what, CopyFileStep stage, IntPtr state, string source, string target, IntPtr ctx) { - // Console.WriteLine ("CopyFileCallback ({0}, {1}, 0x{2}, {3}, {4}, 0x{5})", what, stage, state.ToString ("x"), source, target, ctx.ToString ("x")); +#endif + var log = (IToolLog) GCHandle.FromIntPtr (ctx).Target!; + + // log.Log ("CopyFileCallback ({0}, {1}, 0x{2}, {3}, {4}, 0x{5})", what, stage, state.ToString ("x"), source, target, ctx.ToString ("x")); switch (what) { case CopyFileWhat.File: - if (!IsUptodate (source, target)) { + if (!IsUptodate (log, source, target)) { if (stage == CopyFileStep.Finish) - Log (1, "Copied {0} to {1}", source, target); + log.Log (1, "Copied {0} to {1}", source, target); else if (stage == CopyFileStep.Err) { - Log (1, "Could not copy the file '{0}' to '{1}'", source, target); + log.Log (1, "Could not copy the file '{0}' to '{1}'", source, target); return CopyFileResult.Quit; } else if (stage == CopyFileStep.Start) { if (File.Exists (target) || Directory.Exists (target)) { - Log (1, "Deleted target {0}, it's not up-to-date", target); + log.Log (1, "Deleted target {0}, it's not up-to-date", target); // This callback won't be called for directories, but we can get here for symlinks to directories. // This means that File.Delete should always work (no need to check for a directory to call Directory.Delete) File.Delete (target); @@ -238,7 +217,7 @@ static CopyFileResult CopyFileCallback (CopyFileWhat what, CopyFileStep stage, I } return CopyFileResult.Continue; } else { - Log (3, "Target '{0}' is up-to-date", target); + log.Log (3, "Target '{0}' is up-to-date", target); return CopyFileResult.Skip; } case CopyFileWhat.Dir: @@ -247,36 +226,20 @@ static CopyFileResult CopyFileCallback (CopyFileWhat what, CopyFileStep stage, I case CopyFileWhat.CopyXattr: return CopyFileResult.Continue; case CopyFileWhat.Error: - Log (1, "Could not copy the file '{0}' to '{1}'", source, target); + log.Log (1, "Could not copy the file '{0}' to '{1}'", source, target); return CopyFileResult.Quit; default: return CopyFileResult.Continue; } } - public static bool IsUptodate (string source, string target, ReportErrorCallback reportErrorCallback, LogCallback logCallback, bool check_contents = false, bool check_stamp = true) - { - try { - FileCopier.reportErrorCallback = reportErrorCallback; - FileCopier.logCallback = logCallback; - return IsUptodate (source, target, check_contents, check_stamp); - } finally { - FileCopier.reportErrorCallback = null; - FileCopier.logCallback = null; - } - } - // Checks if the source file has a time stamp later than the target file. // // Optionally check if the contents of the files are different after checking the timestamp. // // If check_stamp is true, the function will use the timestamp of a "target".stamp file // if it's later than the timestamp of the "target" file itself. -#if LEGACY_TOOLS || BUNDLER - public static bool IsUptodate (string source, string target, bool check_contents = false, bool check_stamp = true) -#else - static bool IsUptodate (string source, string target, bool check_contents = false, bool check_stamp = true) -#endif + public static bool IsUptodate (IToolLog log, string source, string target, bool check_contents = false, bool check_stamp = true) { #if LEGACY_TOOLS || BUNDLER // msbuild does not have force if (Driver.Force) @@ -286,14 +249,14 @@ static bool IsUptodate (string source, string target, bool check_contents = fals var tfi = new FileInfo (target); if (!tfi.Exists) { - Log (3, "Target '{0}' does not exist.", target); + log.Log (3, "Target '{0}' does not exist.", target); return false; } if (check_stamp) { var tfi_stamp = new FileInfo (target + ".stamp"); if (tfi_stamp.Exists && tfi_stamp.LastWriteTimeUtc > tfi.LastWriteTimeUtc) { - Log (3, "Target '{0}' has a stamp file with newer timestamp ({1} > {2}), using the stamp file's timestamp", target, tfi_stamp.LastWriteTimeUtc, tfi.LastWriteTimeUtc); + log.Log (3, "Target '{0}' has a stamp file with newer timestamp ({1} > {2}), using the stamp file's timestamp", target, tfi_stamp.LastWriteTimeUtc, tfi.LastWriteTimeUtc); tfi = tfi_stamp; } } @@ -301,13 +264,13 @@ static bool IsUptodate (string source, string target, bool check_contents = fals var sfi = new FileInfo (source); if (sfi.LastWriteTimeUtc <= tfi.LastWriteTimeUtc) { - Log (3, "Prerequisite '{0}' is older than the target '{1}'.", source, target); + log.Log (3, "Prerequisite '{0}' is older than the target '{1}'.", source, target); return true; } #if LEGACY_TOOLS || BUNDLER // msbuild usages do not require CompareFiles optimization - if (check_contents && Cache.CompareFiles (source, target)) { - Log (3, "Prerequisite '{0}' is newer than the target '{1}', but the contents are identical.", source, target); + if (check_contents && Cache.CompareFiles (log, source, target)) { + log.Log (3, "Prerequisite '{0}' is newer than the target '{1}', but the contents are identical.", source, target); return true; } #else @@ -315,31 +278,15 @@ static bool IsUptodate (string source, string target, bool check_contents = fals throw new NotImplementedException ("Checking file contents is not supported"); #endif - Log (3, "Prerequisite '{0}' is newer than the target '{1}'.", source, target); + log.Log (3, "Prerequisite '{0}' is newer than the target '{1}'.", source, target); return false; } - public static bool IsUptodate (IEnumerable sources, IEnumerable targets, ReportErrorCallback reportErrorCallback, LogCallback logCallback, bool check_stamp = true) - { - try { - FileCopier.reportErrorCallback = reportErrorCallback; - FileCopier.logCallback = logCallback; - return IsUptodate (sources, targets, check_stamp); - } finally { - FileCopier.reportErrorCallback = null; - FileCopier.logCallback = null; - } - } - // Checks if any of the source files have a time stamp later than any of the target files. // // If check_stamp is true, the function will use the timestamp of a "target".stamp file // if it's later than the timestamp of the "target" file itself. -#if LEGACY_TOOLS || BUNDLER - public static bool IsUptodate (IEnumerable sources, IEnumerable targets, bool check_stamp = true) -#else - static bool IsUptodate (IEnumerable sources, IEnumerable targets, bool check_stamp = true) -#endif + public static bool IsUptodate (IToolLog log, IEnumerable sources, IEnumerable targets, bool check_stamp = true) { #if LEGACY_TOOLS || BUNDLER // msbuild does not have force if (Driver.Force) @@ -355,7 +302,7 @@ static bool IsUptodate (IEnumerable sources, IEnumerable targets foreach (var s in sources) { var sfi = new FileInfo (s); if (!sfi.Exists) { - Log (3, "Prerequisite '{0}' does not exist.", s); + log.Log (3, "Prerequisite '{0}' does not exist.", s); return false; } @@ -370,26 +317,26 @@ static bool IsUptodate (IEnumerable sources, IEnumerable targets foreach (var t in targets) { var tfi = new FileInfo (t); if (!tfi.Exists) { - Log (3, "Target '{0}' does not exist.", t); + log.Log (3, "Target '{0}' does not exist.", t); return false; } if (check_stamp) { var tfi_stamp = new FileInfo (t + ".stamp"); if (tfi_stamp.Exists && tfi_stamp.LastWriteTimeUtc > tfi.LastWriteTimeUtc) { - Log (3, "Target '{0}' has a stamp file with newer timestamp ({1} > {2}), using the stamp file's timestamp", t, tfi_stamp.LastWriteTimeUtc, tfi.LastWriteTimeUtc); + log.Log (3, "Target '{0}' has a stamp file with newer timestamp ({1} > {2}), using the stamp file's timestamp", t, tfi_stamp.LastWriteTimeUtc, tfi.LastWriteTimeUtc); tfi = tfi_stamp; } } var lwt = tfi.LastWriteTimeUtc; if (max_source > lwt) { - Log (3, "Prerequisite '{0}' is newer than target '{1}' ({2} vs {3}).", max_s, t, max_source, lwt); + log.Log (3, "Prerequisite '{0}' is newer than target '{1}' ({2} vs {3}).", max_s, t, max_source, lwt); return false; } } - Log (3, "Prerequisite(s) '{0}' are all older than the target(s) '{1}'.", string.Join ("', '", sources.ToArray ()), string.Join ("', '", targets.ToArray ())); + log.Log (3, "Prerequisite(s) '{0}' are all older than the target(s) '{1}'.", string.Join ("', '", sources.ToArray ()), string.Join ("', '", targets.ToArray ())); return true; } diff --git a/tools/common/Frameworks.cs b/tools/common/Frameworks.cs index 8f626d2d973e..3970120f17fa 100644 --- a/tools/common/Frameworks.cs +++ b/tools/common/Frameworks.cs @@ -841,7 +841,7 @@ static void Gather (Application app, IEnumerable assemblies, static bool FilterFrameworks (Application app, Framework framework) { if (framework.IsFrameworkUnavailable (app)) { - Driver.Log (3, "Not linking with the framework {0} because it's not available in the current SDK.", framework.Name); + app.Log (3, "Not linking with the framework {0} because it's not available in the current SDK.", framework.Name); return false; } diff --git a/tools/common/IToolLog.cs b/tools/common/IToolLog.cs new file mode 100644 index 000000000000..1bc5a9c41d31 --- /dev/null +++ b/tools/common/IToolLog.cs @@ -0,0 +1,68 @@ +namespace Xamarin.Bundler; + +public interface IToolLog { + int Verbosity { get; } + void Log (string message); + void LogError (string message); + // Log an error we raise ourselves (through an exception) + void LogError (Exception exception); + // Log an unexpected exception + void LogException (Exception exception); +} + +public static class IToolLogExtensions { + public static void Log (this IToolLog log, string format, params object? [] args) + { + log.Log (string.Format (format, args)); + } + + public static void Log (this IToolLog log, int min_verbosity, string message) + { + if (min_verbosity > log.Verbosity) + return; + + log.Log (message); + } + + public static void Log (this IToolLog log, int min_verbosity, string format, params object? [] args) + { + if (min_verbosity > log.Verbosity) + return; + + Log (log, format, args); + } +} + +#if !MSBUILD_TASKS +public class ConsoleLog : IToolLog { + public readonly static IToolLog Instance = new ConsoleLog (); + +#if TESTS + int verbosity = 0; +#else + int verbosity = Driver.GetDefaultVerbosity (); +#endif + + public int Verbosity { get => verbosity; } + + public void Log (string message) + { + Console.WriteLine (message); + } + + public void LogError (string message) + { + Console.Error.WriteLine (message); + } + + public void LogError (Exception exception) + { + Console.Error.WriteLine (exception); + } + + public void LogException (Exception exception) + { + Console.Error.WriteLine (exception); + } +} +#endif // !MSBUILD_TASKS diff --git a/tools/common/MachO.cs b/tools/common/MachO.cs index 0aa183a1cffc..ff06994660b7 100644 --- a/tools/common/MachO.cs +++ b/tools/common/MachO.cs @@ -269,7 +269,7 @@ public static IEnumerable Read (string filename) } } - public static List GetArchitectures (string file) + public static List GetArchitectures (IToolLog log, string file) { var result = new List (); @@ -309,7 +309,7 @@ public static List GetArchitectures (string file) result.Add (GetArch (System.Net.IPAddress.NetworkToHostOrder (reader.ReadInt32 ()), System.Net.IPAddress.NetworkToHostOrder (reader.ReadInt32 ()))); break; default: - Console.WriteLine ("File '{0}' is neither a Universal binary nor a Mach-O binary (magic: 0x{1})", file, magic.ToString ("x")); + log.Log ("File '{0}' is neither a Universal binary nor a Mach-O binary (magic: 0x{1})", file, magic.ToString ("x")); break; } } @@ -1023,10 +1023,10 @@ public MachO.LoadCommands Command { } #if DEBUG - public virtual void Dump () + public virtual void Dump (IToolLog log) { - Console.WriteLine (" cmd: {0}", cmd); - Console.WriteLine (" cmdsize: {0}", cmdsize); + log.Log (" cmd: {0}", cmd); + log.Log (" cmdsize: {0}", cmdsize); } #endif } @@ -1038,13 +1038,13 @@ public class DylibLoadCommand : LoadCommand { public uint compatibility_version; #if DEBUG - public override void Dump () + public override void Dump (IToolLog log) { - base.Dump (); - Console.WriteLine (" name: {0}", name); - Console.WriteLine (" timestamp: {0}", timestamp); - Console.WriteLine (" current_version: {0}", current_version); - Console.WriteLine (" compatibility_version: {0}", compatibility_version); + base.Dump (log); + log.Log (" name: {0}", name); + log.Log (" timestamp: {0}", timestamp); + log.Log (" current_version: {0}", current_version); + log.Log (" compatibility_version: {0}", compatibility_version); } #endif } @@ -1056,13 +1056,13 @@ public class DylibIdCommand : LoadCommand { public uint compatibility_version; #if DEBUG - public override void Dump () + public override void Dump (IToolLog log) { - base.Dump (); - Console.WriteLine (" name: {0}", name); - Console.WriteLine (" timestamp: {0}", timestamp); - Console.WriteLine (" current_version: {0}", current_version); - Console.WriteLine (" compatibility_version: {0}", compatibility_version); + base.Dump (log); + log.Log (" name: {0}", name); + log.Log (" timestamp: {0}", timestamp); + log.Log (" current_version: {0}", current_version); + log.Log (" compatibility_version: {0}", compatibility_version); } #endif } @@ -1071,11 +1071,11 @@ public class UuidCommand : LoadCommand { public byte []? uuid; #if DEBUG - public override void Dump () + public override void Dump (IToolLog log) { - base.Dump (); - Console.WriteLine (" cmd: {0}", cmd); - Console.WriteLine (" uuid: {0}", uuid); + base.Dump (log); + log.Log (" cmd: {0}", cmd); + log.Log (" uuid: {0}", uuid); } #endif } diff --git a/tools/common/PInvokeWrapperGenerator.cs b/tools/common/PInvokeWrapperGenerator.cs index a0c4592a5ccb..d8ba33801ac6 100644 --- a/tools/common/PInvokeWrapperGenerator.cs +++ b/tools/common/PInvokeWrapperGenerator.cs @@ -70,8 +70,8 @@ public void End () Registrar.GeneratePInvokeWrappersEnd (); - Driver.WriteIfDifferent (HeaderPath, hdr.ToString () + "\n" + decls.ToString () + "\n" + ifaces.ToString () + "\n", true); - Driver.WriteIfDifferent (SourcePath, mthds.ToString () + "\n" + sb.ToString () + "\n", true); + Driver.WriteIfDifferent (App, HeaderPath, hdr.ToString () + "\n" + decls.ToString () + "\n" + ifaces.ToString () + "\n", true); + Driver.WriteIfDifferent (App, SourcePath, mthds.ToString () + "\n" + sb.ToString () + "\n", true); } public void ProcessMethod (MethodDefinition method) diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 9891a0c2ec6e..be257833d956 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -726,14 +726,14 @@ protected override bool LaxMode { } } - protected override void ReportError (int code, string message, params object [] args) + protected override void ReportError (int code, string message, params object? [] args) { throw ErrorHelper.CreateError (code, message, args); } - protected override void ReportWarning (int code, string message, params object [] args) + protected override void ReportWarning (int code, string message, params object? [] args) { - ErrorHelper.Show (ErrorHelper.CreateWarning (code, message, args)); + ErrorHelper.Show (App, ErrorHelper.CreateWarning (code, message, args)); } public static int GetValueTypeSize (TypeDefinition type) @@ -2103,7 +2103,7 @@ void CheckNamespace (string ns, List exceptions) namespaces.Add (ns); if (Driver.GetFrameworks (App).TryGetValue (ns, out var fw) && fw.IsFrameworkUnavailable (App)) { - Driver.Log (5, "Not importing the framework {0} in the generated registrar code because it's not available in the current platform.", ns); + App.Log (5, "Not importing the framework {0} in the generated registrar code because it's not available in the current platform.", ns); return; } @@ -2742,9 +2742,9 @@ public void Rewrite () var rewriter = new Rewriter (map_dict, GetAssemblies (), LinkContext); var result = rewriter.Process (); if (!string.IsNullOrEmpty (result)) { - Driver.Log (5, $"Not redirecting class handles because {result}"); + App.Log (5, $"Not redirecting class handles because {result}"); } - ErrorHelper.ThrowIfErrors (exceptions); + ErrorHelper.ThrowIfErrors (App, exceptions); } #endif } @@ -3201,7 +3201,7 @@ void Specialize (AutoIndentStringBuilder sb, out string initialization_method) sb.WriteLine (map.ToString ()); sb.WriteLine (map_init.ToString ()); - ErrorHelper.ThrowIfErrors (exceptions); + ErrorHelper.ThrowIfErrors (App, exceptions); } bool TryGetIntPtrBoolCtor (TypeDefinition type, List exceptions, [NotNullWhen (true)] out MethodDefinition? ctor) @@ -4400,11 +4400,11 @@ void SpecializePrepareReturnValue (AutoIndentStringBuilder sb, ObjCMethod method var token = "INVALID_TOKEN_REF"; if (App.Optimizations.OptimizeBlockLiteralSetupBlock == true) { if (type.Is ("System", "Delegate") || type.Is ("System", "MulticastDelegate")) { - ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173, type.FullName, descriptiveMethodName)); + ErrorHelper.Show (App, ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173, type.FullName, descriptiveMethodName)); } else { var delegateMethod = type.Resolve ().GetMethods ().FirstOrDefault ((v) => v.Name == "Invoke"); if (delegateMethod is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173_A, type.FullName, descriptiveMethodName)); + ErrorHelper.Show (App, ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173_A, type.FullName, descriptiveMethodName)); } else { signature = "\"" + ComputeSignature (method.DeclaringType.Type, null, method, isBlockSignature: true) + "\""; } @@ -4689,7 +4689,7 @@ public bool TryFindMethod (MethodDefinition method, [NotNullWhen (true)] out Obj // One common variation is that the IDE will add the BlockProxy attribute found in base methods when the user overrides those methods, // which unfortunately doesn't compile (because the type passed to the BlockProxy attribute is internal), and then // the user just modifies the attribute to something that compiles. - ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4175, method, $"{(string.IsNullOrEmpty (param.Name) ? $"Parameter #{param.Index + 1}" : $"The parameter '{param.Name}'")} in the method '{GetTypeFullName (method.DeclaringType)}.{GetDescriptiveMethodName (method)}' has an invalid BlockProxy attribute (the type passed to the attribute does not have a 'Create' method).")); + ErrorHelper.Show (App, ErrorHelper.CreateWarning (App, 4175, method, $"{(string.IsNullOrEmpty (param.Name) ? $"Parameter #{param.Index + 1}" : $"The parameter '{param.Name}'")} in the method '{GetTypeFullName (method.DeclaringType)}.{GetDescriptiveMethodName (method)}' has an invalid BlockProxy attribute (the type passed to the attribute does not have a 'Create' method).")); // Returning null will make the caller look for the attribute in the base implementation. } return createMethod; @@ -4966,7 +4966,7 @@ void GenerateConversionToManaged (TypeReference inputType, TypeReference outputT func = GetNSStringToSmartEnumFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName, managedClassExpression, out nativeTypeName); if (!IsSmartEnum (underlyingManagedType, out var _, out var getValueMethod)) { // method linked away!? this should already be verified - ErrorHelper.Show (ErrorHelper.CreateWarning (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); + ErrorHelper.Show (App, ErrorHelper.CreateWarning (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); token = "INVALID_TOKEN_REF"; } else if (TryCreateTokenReference (getValueMethod, TokenType.Method, out var get_value_method_token_ref, out _)) { token = $"0x{get_value_method_token_ref:X} /* {getValueMethod.FullName} */"; @@ -5062,7 +5062,7 @@ void GenerateConversionToNative (TypeReference inputType, TypeReference outputTy func = GetSmartEnumToNSStringFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName, classVariableName); if (!IsSmartEnum (underlyingManagedType, out var getConstantMethod, out var _)) { // method linked away!? this should already be verified - ErrorHelper.Show (ErrorHelper.CreateWarning (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); + ErrorHelper.Show (App, ErrorHelper.CreateWarning (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); token = "INVALID_TOKEN_REF"; } else if (TryCreateTokenReference (getConstantMethod, TokenType.Method, out var get_constant_method_token_ref, out _)) { token = $"0x{get_constant_method_token_ref:X} /* {getConstantMethod.FullName} */"; @@ -5340,7 +5340,7 @@ string TryGeneratePInvokeWrapper (PInvokeWrapperGenerator state, MethodDefinitio sb.WriteLine ("}"); sb.WriteLine (); } else { - // Console.WriteLine ("Signature already processed: {0} for {1}.{2}", signature.ToString (), method.DeclaringType.FullName, method.Name); + // App.Log ("Signature already processed: {0} for {1}.{2}", signature.ToString (), method.DeclaringType.FullName, method.Name); } return wrapperName; @@ -5383,7 +5383,7 @@ public void Register (PlatformResolver? resolver, IEnumerable exceptions) [DllImport ("libc", SetLastError = true)] static extern string realpath (string path, IntPtr zero); - public static string GetRealPath (string path, bool warnIfNoSuchPathExists = true) + public static string GetRealPath (IToolLog log, string path, bool warnIfNoSuchPathExists = true) { // For some reason realpath doesn't always like filenames only, and will randomly fail. // Prepend the current directory if there's no directory specified. @@ -80,7 +80,7 @@ public static string GetRealPath (string path, bool warnIfNoSuchPathExists = tru var errno = Marshal.GetLastWin32Error (); if (warnIfNoSuchPathExists || (errno != 2)) - ErrorHelper.Warning (54, Errors.MT0054, path, FileCopier.strerror (errno), errno); + ErrorHelper.Warning (log, 54, Errors.MT0054, path, FileCopier.strerror (errno), errno); return path; } @@ -135,7 +135,7 @@ public void ComputeLinkerFlags () sb.AppendLine ("}"); sb.AppendLine (); - Driver.WriteIfDifferent (reference_m, sb.ToString (), true); + Driver.WriteIfDifferent (App, reference_m, sb.ToString (), true); return reference_m; } @@ -193,7 +193,7 @@ public void GenerateMain (StringBuilder sb, ApplePlatform platform, Abi abi, str throw ErrorHelper.CreateError (71, Errors.MX0071, platform, App.ProductName); } } - Driver.WriteIfDifferent (main_source, sb.ToString (), true); + Driver.WriteIfDifferent (App, main_source, sb.ToString (), true); } catch (ProductException) { throw; } catch (Exception e) { @@ -315,7 +315,7 @@ void GenerateMainImpl (StringWriter sw, Abi abi) sw.WriteLine ("\txamarin_executable_name = \"{0}\";", assembly_name); if (app.XamarinRuntime == XamarinRuntime.MonoVM) sw.WriteLine ("\tmono_use_llvm = {0};", enable_llvm ? "TRUE" : "FALSE"); - sw.WriteLine ("\txamarin_log_level = {0};", Driver.Verbosity.ToString (CultureInfo.InvariantCulture)); + sw.WriteLine ("\txamarin_log_level = {0};", Verbosity.ToString (CultureInfo.InvariantCulture)); sw.WriteLine ("\txamarin_arch_name = \"{0}\";", abi.AsArchString ()); if (!app.IsDefaultMarshalManagedExceptionMode) sw.WriteLine ("\txamarin_marshal_managed_exception_mode = MarshalManagedExceptionMode{0};", app.MarshalManagedExceptions); diff --git a/tools/common/cache.cs b/tools/common/cache.cs index a89b6db7753b..1dcba0073ef9 100644 --- a/tools/common/cache.cs +++ b/tools/common/cache.cs @@ -33,77 +33,79 @@ public bool IsCacheTemporary { } // see --cache=DIR - public string Location { - get { - if (cache_dir is null) { - do { - cache_dir = Path.Combine (Path.GetTempPath (), NAME + ".cache", Path.GetRandomFileName ()); - if (File.Exists (cache_dir) || Directory.Exists (cache_dir)) - continue; - Directory.CreateDirectory (cache_dir); - break; - } while (true); - - cache_dir = Application.GetRealPath (cache_dir); - - temporary_cache = true; - if (!Directory.Exists (cache_dir)) - Directory.CreateDirectory (cache_dir); -#if DEBUG - Console.WriteLine ("Cache defaults to {0}", cache_dir); -#endif - } - return cache_dir; - } - set { - cache_dir = value; + public string GetLocation (IToolLog log) + { + if (cache_dir is null) { + do { + cache_dir = Path.Combine (Path.GetTempPath (), NAME + ".cache", Path.GetRandomFileName ()); + if (File.Exists (cache_dir) || Directory.Exists (cache_dir)) + continue; + Directory.CreateDirectory (cache_dir); + break; + } while (true); + + cache_dir = Application.GetRealPath (log, cache_dir); + + temporary_cache = true; if (!Directory.Exists (cache_dir)) Directory.CreateDirectory (cache_dir); - cache_dir = Application.GetRealPath (Path.GetFullPath (cache_dir)); +#if DEBUG + log.Log ("Cache defaults to {0}", cache_dir); +#endif } + return cache_dir; + } + + public void SetLocation (IToolLog log, string value) + { + cache_dir = value; + if (!Directory.Exists (cache_dir)) + Directory.CreateDirectory (cache_dir); + cache_dir = Application.GetRealPath (log, Path.GetFullPath (cache_dir)); } - public void Clean () + public void Clean (IToolLog log) { + var location = GetLocation (log); #if DEBUG - Console.WriteLine ("Cache.Clean: {0}", Location); + log.Log ("Cache.Clean: {0}", location); #endif - Directory.Delete (Location, true); - Directory.CreateDirectory (Location); + Directory.Delete (location, true); + Directory.CreateDirectory (location); } - public static bool CompareFiles (string a, string b, bool ignore_cache = false) + public static bool CompareFiles (IToolLog log, string a, string b, bool ignore_cache = false) { if (Driver.Force && !ignore_cache) { - Driver.Log (6, "Files {0} and {1} are considered different because -f was passed to " + NAME + ".", a, b); + log.Log (6, "Files {0} and {1} are considered different because -f was passed to " + NAME + ".", a, b); return false; } if (!File.Exists (b)) { - Driver.Log (6, "Files {0} and {1} are considered different because the latter doesn't exist.", a, b); + log.Log (6, "Files {0} and {1} are considered different because the latter doesn't exist.", a, b); return false; } using (var astream = new FileStream (a, FileMode.Open, FileAccess.Read, FileShare.Read)) { using (var bstream = new FileStream (b, FileMode.Open, FileAccess.Read, FileShare.Read)) { bool rv; - Driver.Log (6, "Comparing files {0} and {1}...", a, b); - rv = CompareStreams (astream, bstream, ignore_cache); - Driver.Log (6, " > {0}", rv ? "Identical" : "Different"); + log.Log (6, "Comparing files {0} and {1}...", a, b); + rv = CompareStreams (log, astream, bstream, ignore_cache); + log.Log (6, " > {0}", rv ? "Identical" : "Different"); return rv; } } } - public unsafe static bool CompareStreams (Stream astream, Stream bstream, bool ignore_cache = false) + public unsafe static bool CompareStreams (IToolLog log, Stream astream, Stream bstream, bool ignore_cache = false) { if (Driver.Force && !ignore_cache) { - Driver.Log (6, " > streams are considered different because -f was passed to " + NAME + "."); + log.Log (6, " > streams are considered different because -f was passed to " + NAME + "."); return false; } if (astream.Length != bstream.Length) { - Driver.Log (6, " > streams are considered different because their lengths do not match."); + log.Log (6, " > streams are considered different because their lengths do not match."); return false; } @@ -151,20 +153,20 @@ void CollectArgumentsForCache (IList args, int firstArgument, StringBuil public bool IsCacheValid (Application app) { var name = "arguments"; - var pcache = Path.Combine (Location, name); + var pcache = Path.Combine (GetLocation (app), name); if (!File.Exists (pcache)) { - Driver.Log (3, "A full rebuild will be performed because the cache is either incomplete or entirely missing."); + app.Log (3, "A full rebuild will be performed because the cache is either incomplete or entirely missing."); return false; } else if (GetArgumentsForCacheData (app) != File.ReadAllText (pcache)) { - Driver.Log (3, "A full rebuild will be performed because the arguments to " + NAME + " has changed with regards to the cached data."); + app.Log (3, "A full rebuild will be performed because the arguments to " + NAME + " has changed with regards to the cached data."); return false; } // Check if mtouch/mmp has been modified. var executable = System.Reflection.Assembly.GetExecutingAssembly ().Location; - if (!Application.IsUptodate (executable, pcache)) { - Driver.Log (3, "A full rebuild will be performed because " + NAME + " has been modified."); + if (!Application.IsUptodate (app, executable, pcache)) { + app.Log (3, "A full rebuild will be performed because " + NAME + " has been modified."); return false; } @@ -174,7 +176,7 @@ public bool IsCacheValid (Application app) public bool VerifyCache (Application app) { if (!IsCacheValid (app)) { - Clean (); + Clean (app); return false; } @@ -184,54 +186,7 @@ public bool VerifyCache (Application app) public void ValidateCache (Application app) { var name = "arguments"; - var pcache = Path.Combine (Location, name); + var pcache = Path.Combine (GetLocation (app), name); File.WriteAllText (pcache, GetArgumentsForCacheData (app)); } - -#if false - static public void ComputeDependencies (IEnumerable assemblies, MonoTouchResolver resolver) - { - // note: Parallel.ForEach (with lock to add on 'digests') turns out (much) slower - // (linksdk.app with 20 assemblies) - // likely because it's faster (using commoncrypto) than it seems - foreach (string a in assemblies) { - string key = Path.GetFileNameWithoutExtension (a); - using (Stream fs = File.OpenRead (a)) { - string digest = ComputeDigest (fs, 140); - digests.Add (key, digest); - } - } - - Dictionary> dependencies = new Dictionary> (); - foreach (string a in assemblies) { - HashSet references; - AssemblyDefinition ad = resolver.Load (a); - foreach (AssemblyNameReference ar in ad.MainModule.AssemblyReferences) { - if (!dependencies.TryGetValue (ar.Name, out references)) { - references = new HashSet (); - dependencies.Add (ar.Name, references); - } - references.Add (ad.Name.Name); - } - } -#if DEBUG - foreach (var kvp in dependencies) { - Console.WriteLine ("The following assemblies depends on {0}", kvp.Key); - foreach (var s in kvp.Value) - Console.WriteLine ("\t{0}", s); - } -#endif - // if a dependency has changed everything that depends on it must be cleaned - foreach (var kvp in dependencies) { - string cname = kvp.Key + ".*.cache." + GetDigestForAssembly (kvp.Key) + ".o"; - var files = Directory.GetFiles (Location, cname); - if (files.Length != 0) - continue; - - Clean (kvp.Key + "*"); - foreach (var deps in kvp.Value) - Clean (deps + "*"); - } - } -#endif } diff --git a/tools/dotnet-linker/BackingFieldDelayHandler.cs b/tools/dotnet-linker/BackingFieldDelayHandler.cs index be9b46adf828..c77ad969e721 100644 --- a/tools/dotnet-linker/BackingFieldDelayHandler.cs +++ b/tools/dotnet-linker/BackingFieldDelayHandler.cs @@ -68,6 +68,7 @@ protected override void Process (MethodDefinition method) public static void ReapplyDisposedFields (DerivedLinkContext context, string operation) { // note: all methods in the dictionary are marked (since they were added from an IMarkHandler) + var app = context.App; foreach ((var method, var body) in dispose) { foreach (var ins in body.Instructions) { switch (ins.OpCode.OperandType) { @@ -79,9 +80,9 @@ public static void ReapplyDisposedFields (DerivedLinkContext context, string ope var store_field = ins; var load_null = ins.Previous; var load_this = ins.Previous.Previous; - if (OptimizeGeneratedCodeHandler.ValidateInstruction (method, store_field, operation, Code.Stfld) && - OptimizeGeneratedCodeHandler.ValidateInstruction (method, load_null, operation, Code.Ldnull) && - OptimizeGeneratedCodeHandler.ValidateInstruction (method, load_this, operation, Code.Ldarg_0)) { + if (OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, store_field, operation, Code.Stfld) && + OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, load_null, operation, Code.Ldnull) && + OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, load_this, operation, Code.Ldarg_0)) { store_field.OpCode = OpCodes.Nop; load_null.OpCode = OpCodes.Nop; load_this.OpCode = OpCodes.Nop; diff --git a/tools/dotnet-linker/DotNetResolver.cs b/tools/dotnet-linker/DotNetResolver.cs index 37e04bb9e1f9..f71c2ec5b4cb 100644 --- a/tools/dotnet-linker/DotNetResolver.cs +++ b/tools/dotnet-linker/DotNetResolver.cs @@ -8,6 +8,10 @@ namespace Xamarin.Linker { public class DotNetResolver : CoreResolver { + public DotNetResolver (Application app) + { + } + public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters) { throw new NotImplementedException ($"Unable to resolve the assembly reference {name}"); diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index b42db901cbad..a7d9909785ed 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -47,7 +47,7 @@ public class LinkerConfiguration { public Version? SdkVersion { get; private set; } public string SdkRootDirectory { get; private set; } = string.Empty; public string TypeMapFilePath { get; set; } = string.Empty; - public int Verbosity => Driver.Verbosity; + public int Verbosity => Application.Verbosity; public string XamarinNativeLibraryDirectory { get; private set; } = string.Empty; static ConditionalWeakTable configurations = new ConditionalWeakTable (); @@ -397,7 +397,7 @@ public static LinkerConfiguration GetInstance (LinkContext context) case "Verbosity": if (!int.TryParse (value, out var verbosity)) throw new InvalidOperationException ($"Invalid Verbosity '{value}' in {linker_file}"); - Driver.Verbosity += verbosity; + Application.Verbosity += verbosity; break; case "Warn": try { @@ -438,7 +438,7 @@ public static LinkerConfiguration GetInstance (LinkContext context) if (!StringUtils.IsNullOrEmpty (user_optimize_flags)) { var messages = new List (); Application.Optimizations.Parse (Application.Platform, user_optimize_flags, messages); - ErrorHelper.Show (messages); + ErrorHelper.Show (Application, messages); } if (use_llvm) { @@ -447,7 +447,7 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application.CreateCache (significantLines.ToArray ()); if (Application.Cache is not null) - Application.Cache.Location = CacheDirectory; + Application.Cache.SetLocation (Application, CacheDirectory); if (DeploymentTarget is not null) Application.DeploymentTarget = DeploymentTarget; if (SdkVersion is not null) { @@ -472,7 +472,7 @@ public static LinkerConfiguration GetInstance (LinkContext context) throw ErrorHelper.CreateError (99, "Inconsistent platforms. TargetFramework={0}, Platform={1}", Driver.TargetFramework.Platform, Platform); if (Application.XamarinRuntime != XamarinRuntime.MonoVM && Application.UseInterpreter) { - Driver.Log (4, "The interpreter is enabled, but the current runtime isn't MonoVM. The interpreter settings will be ignored."); + Application.Log (4, "The interpreter is enabled, but the current runtime isn't MonoVM. The interpreter settings will be ignored."); Application.UnsetInterpreter (); } @@ -539,52 +539,52 @@ AssemblyBuildTarget ParseLinkMode (string value, string variableName) public void Write () { if (Verbosity > 0) { - Console.WriteLine ($"LinkerConfiguration:"); - Console.WriteLine ($" ABI: {Abi.AsArchString ()}"); - Console.WriteLine ($" AOTArguments: {string.Join (", ", Application.AotArguments)}"); - Console.WriteLine ($" AOTOutputDirectory: {AOTOutputDirectory}"); - Console.WriteLine ($" DedupAssembly: {DedupAssembly}"); - Console.WriteLine ($" AppBundleManifestPath: {Application.InfoPListPath}"); - Console.WriteLine ($" AreAnyAssembliesTrimmed: {Application.AreAnyAssembliesTrimmed}"); - Console.WriteLine ($" AssemblyName: {Application.AssemblyName}"); - Console.WriteLine ($" CacheDirectory: {CacheDirectory}"); - Console.WriteLine ($" Debug: {Application.EnableDebug}"); - Console.WriteLine ($" Dlsym: {Application.DlsymOptions} {(Application.DlsymAssemblies is not null ? string.Join (" ", Application.DlsymAssemblies.Select (v => (v.Item2 ? "+" : "-") + v.Item1)) : string.Empty)}"); - Console.WriteLine ($" DeploymentTarget: {DeploymentTarget}"); - Console.WriteLine ($" EnableSGenConc {Application.EnableSGenConc}"); - Console.WriteLine ($" InlineDlfcnMethods: {InlineDlfcnMethods}"); - Console.WriteLine ($" IntermediateLinkDir: {IntermediateLinkDir}"); - Console.WriteLine ($" IntermediateOutputPath: {IntermediateOutputPath}"); - Console.WriteLine ($" InterpretedAssemblies: {string.Join (", ", Application.InterpretedAssemblies)}"); - Console.WriteLine ($" ItemsDirectory: {ItemsDirectory}"); - Console.WriteLine ($" {FrameworkAssemblies.Count} framework assemblies:"); + Application.Log ($"LinkerConfiguration:"); + Application.Log ($" ABI: {Abi.AsArchString ()}"); + Application.Log ($" AOTArguments: {string.Join (", ", Application.AotArguments)}"); + Application.Log ($" AOTOutputDirectory: {AOTOutputDirectory}"); + Application.Log ($" DedupAssembly: {DedupAssembly}"); + Application.Log ($" AppBundleManifestPath: {Application.InfoPListPath}"); + Application.Log ($" AreAnyAssembliesTrimmed: {Application.AreAnyAssembliesTrimmed}"); + Application.Log ($" AssemblyName: {Application.AssemblyName}"); + Application.Log ($" CacheDirectory: {CacheDirectory}"); + Application.Log ($" Debug: {Application.EnableDebug}"); + Application.Log ($" Dlsym: {Application.DlsymOptions} {(Application.DlsymAssemblies is not null ? string.Join (" ", Application.DlsymAssemblies.Select (v => (v.Item2 ? "+" : "-") + v.Item1)) : string.Empty)}"); + Application.Log ($" DeploymentTarget: {DeploymentTarget}"); + Application.Log ($" EnableSGenConc {Application.EnableSGenConc}"); + Application.Log ($" InlineDlfcnMethods: {InlineDlfcnMethods}"); + Application.Log ($" IntermediateLinkDir: {IntermediateLinkDir}"); + Application.Log ($" IntermediateOutputPath: {IntermediateOutputPath}"); + Application.Log ($" InterpretedAssemblies: {string.Join (", ", Application.InterpretedAssemblies)}"); + Application.Log ($" ItemsDirectory: {ItemsDirectory}"); + Application.Log ($" {FrameworkAssemblies.Count} framework assemblies:"); foreach (var fw in FrameworkAssemblies.OrderBy (v => v)) - Console.WriteLine ($" {fw}"); - Console.WriteLine ($" IsSimulatorBuild: {IsSimulatorBuild}"); - Console.WriteLine ($" MarshalManagedExceptions: {Application.MarshalManagedExceptions} (IsDefault: {Application.IsDefaultMarshalManagedExceptionMode})"); - Console.WriteLine ($" MarshalObjectiveCExceptions: {Application.MarshalObjectiveCExceptions}"); - Console.WriteLine ($" {Application.MonoLibraries.Count} mono libraries:"); + Application.Log ($" {fw}"); + Application.Log ($" IsSimulatorBuild: {IsSimulatorBuild}"); + Application.Log ($" MarshalManagedExceptions: {Application.MarshalManagedExceptions} (IsDefault: {Application.IsDefaultMarshalManagedExceptionMode})"); + Application.Log ($" MarshalObjectiveCExceptions: {Application.MarshalObjectiveCExceptions}"); + Application.Log ($" {Application.MonoLibraries.Count} mono libraries:"); foreach (var lib in Application.MonoLibraries.OrderBy (v => v)) - Console.WriteLine ($" {lib}"); - Console.WriteLine ($" Optimize: {user_optimize_flags} => {Application.Optimizations}"); - Console.WriteLine ($" PartialStaticRegistrarLibrary: {PartialStaticRegistrarLibrary}"); - Console.WriteLine ($" Platform: {Platform}"); - Console.WriteLine ($" PlatformAssembly: {PlatformAssembly}.dll"); - Console.WriteLine ($" RelativeAppBundlePath: {RelativeAppBundlePath}"); - Console.WriteLine ($" Registrar: {Application.Registrar} (Options: {Application.RegistrarOptions})"); - Console.WriteLine ($" RuntimeConfigurationFile: {Application.RuntimeConfigurationFile}"); - Console.WriteLine ($" RequirePInvokeWrappers: {Application.RequiresPInvokeWrappers}"); - Console.WriteLine ($" SdkDevPath: {Driver.SdkRoot}"); - Console.WriteLine ($" SdkRootDirectory: {SdkRootDirectory}"); - Console.WriteLine ($" SdkVersion: {SdkVersion}"); - Console.WriteLine ($" TypeMapAssemblyName: {Application.TypeMapAssemblyName}"); - Console.WriteLine ($" TypeMapFilePath: {TypeMapFilePath}"); - Console.WriteLine ($" TypeMapOutputDirectory: {Application.TypeMapOutputDirectory}"); - Console.WriteLine ($" UseInterpreter: {Application.UseInterpreter}"); - Console.WriteLine ($" UseLlvm: {Application.IsLLVM}"); - Console.WriteLine ($" Verbosity: {Verbosity}"); - Console.WriteLine ($" XamarinNativeLibraryDirectory: {XamarinNativeLibraryDirectory}"); - Console.WriteLine ($" XamarinRuntime: {Application.XamarinRuntime}"); + Application.Log ($" {lib}"); + Application.Log ($" Optimize: {user_optimize_flags} => {Application.Optimizations}"); + Application.Log ($" PartialStaticRegistrarLibrary: {PartialStaticRegistrarLibrary}"); + Application.Log ($" Platform: {Platform}"); + Application.Log ($" PlatformAssembly: {PlatformAssembly}.dll"); + Application.Log ($" RelativeAppBundlePath: {RelativeAppBundlePath}"); + Application.Log ($" Registrar: {Application.Registrar} (Options: {Application.RegistrarOptions})"); + Application.Log ($" RuntimeConfigurationFile: {Application.RuntimeConfigurationFile}"); + Application.Log ($" RequirePInvokeWrappers: {Application.RequiresPInvokeWrappers}"); + Application.Log ($" SdkDevPath: {Driver.SdkRoot}"); + Application.Log ($" SdkRootDirectory: {SdkRootDirectory}"); + Application.Log ($" SdkVersion: {SdkVersion}"); + Application.Log ($" TypeMapAssemblyName: {Application.TypeMapAssemblyName}"); + Application.Log ($" TypeMapFilePath: {TypeMapFilePath}"); + Application.Log ($" TypeMapOutputDirectory: {Application.TypeMapOutputDirectory}"); + Application.Log ($" UseInterpreter: {Application.UseInterpreter}"); + Application.Log ($" UseLlvm: {Application.IsLLVM}"); + Application.Log ($" Verbosity: {Verbosity}"); + Application.Log ($" XamarinNativeLibraryDirectory: {XamarinNativeLibraryDirectory}"); + Application.Log ($" XamarinRuntime: {Application.XamarinRuntime}"); } } @@ -644,7 +644,7 @@ public static void Report (LinkContext context, IList exceptions) context.LogMessage (msg); } // ErrorHelper.Show will print our errors and warnings to stderr. - ErrorHelper.Show (list); + ErrorHelper.Show (ConsoleLog.Instance, list); } public IEnumerable GetNonDeletedAssemblies (BaseStep step) diff --git a/tools/dotnet-linker/PreserveSmartEnumConversionsStep.cs b/tools/dotnet-linker/PreserveSmartEnumConversionsStep.cs index 75ebca8360dd..7dae7ba20e13 100644 --- a/tools/dotnet-linker/PreserveSmartEnumConversionsStep.cs +++ b/tools/dotnet-linker/PreserveSmartEnumConversionsStep.cs @@ -94,6 +94,7 @@ class PreserveSmartEnumConversion { Dictionary> cache = new (); public DerivedLinkContext LinkContext { get; private set; } + public Application App => LinkContext.App; Func, bool, MethodDefinition? [], bool> preserve { get; set; } @@ -122,14 +123,14 @@ public bool ProcessAttributeProvider (ICustomAttributeProvider provider, params continue; if (ca.ConstructorArguments.Count != 1) { - ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 4124, provider, Errors.MT4124_E, provider.AsString (), ca.ConstructorArguments.Count)); + ErrorHelper.Show (App, ErrorHelper.CreateWarning (LinkContext.App, 4124, provider, Errors.MT4124_E, provider.AsString (), ca.ConstructorArguments.Count)); continue; } var managedType = ca.ConstructorArguments [0].Value as TypeReference; var managedEnumType = managedType?.GetElementType ().Resolve (); if (managedEnumType is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 4124, provider, Errors.MT4124_H, provider.AsString (), managedType?.FullName ?? "(null)")); + ErrorHelper.Show (App, ErrorHelper.CreateWarning (LinkContext.App, 4124, provider, Errors.MT4124_H, provider.AsString (), managedType?.FullName ?? "(null)")); continue; } @@ -155,7 +156,7 @@ public bool ProcessAttributeProvider (ICustomAttributeProvider provider, params break; } if (extensionType is null) { - Driver.Log (1, $"Could not find a smart extension type for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum."); + App.Log (1, $"Could not find a smart extension type for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum."); continue; } @@ -184,12 +185,12 @@ public bool ProcessAttributeProvider (ICustomAttributeProvider provider, params } if (getConstant is null) { - Driver.Log (1, $"Could not find the GetConstant method on the supposedly smart extension type {extensionType.FullName} for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum."); + App.Log (1, $"Could not find the GetConstant method on the supposedly smart extension type {extensionType.FullName} for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum."); continue; } if (getValue is null) { - Driver.Log (1, $"Could not find the GetValue method on the supposedly smart extension type {extensionType.FullName} for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum."); + App.Log (1, $"Could not find the GetValue method on the supposedly smart extension type {extensionType.FullName} for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum."); continue; } diff --git a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs index 4eda1e9f18f3..25153aca76fa 100644 --- a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs +++ b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs @@ -34,6 +34,8 @@ public virtual void Initialize (LinkContext context) protected Profile Profile => Configuration.Profile; + protected Application App => Configuration.Application; + public void ProcessAssembly (AssemblyDefinition assembly) { try { diff --git a/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs b/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs index 1a48cba7b3b0..4cb66ed8fd27 100644 --- a/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs +++ b/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs @@ -61,7 +61,7 @@ protected override void TryProcess () Frameworks.TryGetFramework (App, td, out string? framework); sb.AppendLine ($"Class={info.ExportedName}|Framework={framework}|Introduced={introduced}|IsWrapper={info.IsWrapper}|IsStubClass={info.IsStubClass}"); } - Driver.WriteIfDifferent (Configuration.TypeMapFilePath, sb.ToString ()); + Driver.WriteIfDifferent (App, Configuration.TypeMapFilePath, sb.ToString ()); } base.TryProcess (); @@ -153,17 +153,17 @@ bool isOurOwnCode () var ldstr = instr.Previous; if (ldstr.OpCode != OpCodes.Ldstr) { if (!isOurOwnCode ()) - Driver.Log (3, "Unknown or unsupported pattern in call to Class.GetHandle in '{0}': {1}. The call will not be inlined.", FormatMethod (method), ldstr); + App.Log (3, "Unknown or unsupported pattern in call to Class.GetHandle in '{0}': {1}. The call will not be inlined.", FormatMethod (method), ldstr); continue; } if (ldstr.Operand is not string objectiveCClassName) { if (!isOurOwnCode ()) - Driver.Log (3, "Unknown or unsupported pattern in call to Class.GetHandle in '{0}': {1}. The call will not be inlined.", FormatMethod (method), ldstr.Operand); + App.Log (3, "Unknown or unsupported pattern in call to Class.GetHandle in '{0}': {1}. The call will not be inlined.", FormatMethod (method), ldstr.Operand); continue; } if (!objectiveCTypeMap.TryGetValue (objectiveCClassName, out var objCType)) { - Driver.Log (3, "Could not find a managed type for the Objective-C type '{1}' in the call to Class.GetHandle in '{0}', assuming the Objective-C type is available in the simulator.", FormatMethod (method), objectiveCClassName); + App.Log (3, "Could not find a managed type for the Objective-C type '{1}' in the call to Class.GetHandle in '{0}', assuming the Objective-C type is available in the simulator.", FormatMethod (method), objectiveCClassName); } if (!strictMode) { @@ -171,12 +171,12 @@ bool isOurOwnCode () // This is a call to Class.GetHandle for the same class that it's being called from, this is OK. } else if (ListExportedSymbols.TryGetRequiredObjectiveCType (DerivedLinkContext, method.DeclaringType, out var exportedName)) { if (exportedName != objectiveCClassName) { - Driver.Log (3, "The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but the declaring type's exported name is '{2}', not '{1}'. Since we're in compat mode, we're assuming the class should not be preserved.", FormatMethod (method), objectiveCClassName, exportedName); + App.Log (3, "The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but the declaring type's exported name is '{2}', not '{1}'. Since we're in compat mode, we're assuming the class should not be preserved.", FormatMethod (method), objectiveCClassName, exportedName); continue; } } else { if (App.StaticRegistrar.GetCategoryAttribute (method.DeclaringType) is not null) - Driver.Log (3, "The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but we couldn't determine whether this class should be statically preserved or not. Since we're in compat mode, we're assuming the class should not be statically preserved.", FormatMethod (method), objectiveCClassName); + App.Log (3, "The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but we couldn't determine whether this class should be statically preserved or not. Since we're in compat mode, we're assuming the class should not be statically preserved.", FormatMethod (method), objectiveCClassName); continue; } } @@ -184,20 +184,20 @@ bool isOurOwnCode () // Check if the Objective-C class is listed as a ReferenceNativeSymbol with Ignore mode, and if so, don't inline the call to Class.GetHandle (because the native symbol won't be available at link time) var existingSymbol = DerivedLinkContext.RequiredSymbols.Find (Symbol.ObjectiveCPrefix + objectiveCClassName); if (existingSymbol is not null && existingSymbol.Type == SymbolType.ObjectiveCClass && existingSymbol.Mode == SymbolMode.Ignore) { - Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the class is listed as a ReferenceNativeSymbol with Ignore mode.", objectiveCClassName, FormatMethod (method)); + App.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the class is listed as a ReferenceNativeSymbol with Ignore mode.", objectiveCClassName, FormatMethod (method)); continue; } if (objCType is not null) { if (DerivedLinkContext.App.IsSimulatorBuild) { if (DerivedLinkContext.HasAvailabilityAttributesShowingUnavailableInSimulator (objCType.Type.Resolve (), method)) { - Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the type is marked with an attribute indicating it's not available in the simulator.", objectiveCClassName, FormatMethod (method)); + App.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the type is marked with an attribute indicating it's not available in the simulator.", objectiveCClassName, FormatMethod (method)); continue; } } if (IsUnsupported (objCType.Type.Resolve ())) { - Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the type is marked with an [UnsupportedOSPlatform] attribute.", objectiveCClassName, FormatMethod (method)); + App.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the type is marked with an [UnsupportedOSPlatform] attribute.", objectiveCClassName, FormatMethod (method)); continue; } @@ -207,7 +207,7 @@ bool isOurOwnCode () } if (Frameworks.TryGetFramework (App, objCType.Type.Resolve (), out Framework? framework) && framework.IsFrameworkUnavailable (App)) { - Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the framework {2} is unavailable.", objectiveCClassName, FormatMethod (method), framework.Name); + App.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the framework {2} is unavailable.", objectiveCClassName, FormatMethod (method), framework.Name); continue; } diff --git a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs index 99b67d4a9984..95913f88ead9 100644 --- a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs +++ b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs @@ -43,7 +43,7 @@ protected override bool ProcessType (TypeDefinition type) var modified = false; if (type.HasMethods) { if (Frameworks.TryGetFramework (App, type, out Framework? framework) && framework.IsFrameworkUnavailable (App)) { - Driver.Log (3, $"Type {type.FullName} appears to be part of the '{framework.Name}' framework, which is not available in the current SDK. Skipping inlining Dlfcn calls for this type."); + App.Log (3, $"Type {type.FullName} appears to be part of the '{framework.Name}' framework, which is not available in the current SDK. Skipping inlining Dlfcn calls for this type."); return modified; } @@ -525,11 +525,11 @@ protected override bool ProcessMethod (MethodDefinition method) if (DerivedLinkContext.App.IsSimulatorBuild) { // if the method or its declaring type aren't available in the simulator, and we're building for the simulator, then don't inline. if (DerivedLinkContext.HasAvailabilityAttributesShowingUnavailableInSimulator (method, method)) { - Driver.Log (3, $"Method {method.FullName} is not available in the simulator. Skipping inlining Dlfcn calls for this method."); + App.Log (3, $"Method {method.FullName} is not available in the simulator. Skipping inlining Dlfcn calls for this method."); return modified; } if (DerivedLinkContext.HasAvailabilityAttributesShowingUnavailableInSimulator (method.DeclaringType, method)) { - Driver.Log (3, $"Type {method.DeclaringType.FullName} is not available in the simulator. Skipping inlining Dlfcn calls for this type."); + App.Log (3, $"Type {method.DeclaringType.FullName} is not available in the simulator. Skipping inlining Dlfcn calls for this type."); return modified; } } diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index a0d764222e29..68bbe468e009 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -330,7 +330,7 @@ void GenerateConstructNSObject (TypeDefinition registrarType) foreach (var type in types) { var ctorRef = FindNSObjectConstructor (type); if (ctorRef is null) { - Driver.Log (9, $"Cannot include {type.FullName} in ConstructNSObject because it doesn't have a suitable constructor"); + App.Log (9, $"Cannot include {type.FullName} in ConstructNSObject because it doesn't have a suitable constructor"); continue; } @@ -652,7 +652,7 @@ void GenerateLookupUnmanagedFunction (TypeDefinition registrar_type, IList 0) { // All the methods in a given assembly will have consecutive IDs (but might not start at 0). if (trampolineInfos.First ().Id + trampolineInfos.Count - 1 != trampolineInfos.Last ().Id) diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index cfc23620e1d7..70c4cc72a7bf 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -1315,7 +1315,7 @@ void GenerateConversionToNative (MethodDefinition method, ILProcessor il, TypeRe } else if (underlyingNativeType.Is ("Foundation", "NSString")) { if (!StaticRegistrar.IsSmartEnum (underlyingManagedType, out var getConstantMethod, out var getValueMethod)) { // method linked away!? this should already be verified - ErrorHelper.Show (ErrorHelper.CreateError (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); + ErrorHelper.Show (App, ErrorHelper.CreateError (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); return; } diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 853bba62528f..c3ff1f126681 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -71,6 +71,9 @@ external/tools/common/FileUtils.cs + + external/tools/common/IToolLog.cs + external/tools/common/LinkMode.cs diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 85cf938052a0..c33d231aeb66 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -67,28 +67,33 @@ static protected void Nop (Instruction ins) ins.Operand = null; } - internal static bool ValidateInstruction (MethodDefinition caller, Instruction ins, string operation, Code expected) + internal static bool ValidateInstruction (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, string operation, Code expected) + { + return ValidateInstruction (data.App, caller, ins, operation, expected); + } + + internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, Instruction ins, string operation, Code expected) { if (ins.OpCode.Code != expected) { - Driver.Log (1, "Could not {0} in {1} at offset {2}, expected {3} got {4}", operation, caller, ins.Offset, expected, ins); + log.Log (1, "Could not {0} in {1} at offset {2}, expected {3} got {4}", operation, caller, ins.Offset, expected, ins); return false; } return true; } - internal static bool ValidateInstruction (MethodDefinition caller, Instruction ins, string operation, params Code [] expected) + internal static bool ValidateInstruction (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, string operation, params Code [] expected) { foreach (var code in expected) { if (ins.OpCode.Code == code) return true; } - Driver.Log (1, "Could not {0} in {1} at offset {2}, expected any of [{3}] got {4}", operation, caller, ins.Offset, string.Join (", ", expected), ins); + data.App.Log (1, "Could not {0} in {1} at offset {2}, expected any of [{3}] got {4}", operation, caller, ins.Offset, string.Join (", ", expected), ins); return false; } - static int? GetConstantValue (Instruction? ins) + static int? GetConstantValue (OptimizeGeneratedCodeData data, Instruction? ins) { if (ins is null) return null; @@ -159,13 +164,13 @@ internal static bool ValidateInstruction (MethodDefinition caller, Instruction i #endif default: #if DEBUG - Driver.Log (9, "Unknown conditional instruction: {0}", ins); + data.App.Log (9, "Unknown conditional instruction: {0}", ins); #endif return null; } } - static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic.Collection instructions, bool [] reachable, int start, int end) + static bool MarkInstructions (OptimizeGeneratedCodeData data, MethodDefinition method, Mono.Collections.Generic.Collection instructions, bool [] reachable, int start, int end) { if (reachable [start]) return true; // We've already marked this section of code @@ -178,7 +183,7 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. case FlowControl.Branch: // Unconditional branch, we continue marking from the instruction that we branch to. var br_target = (Instruction) ins.Operand; - return MarkInstructions (method, instructions, reachable, instructions.IndexOf (br_target), instructions.Count); + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (br_target), instructions.Count); case FlowControl.Cond_Branch: // Conditional instruction, we need to check if we can calculate a constant value for the condition var cond_target = ins.Operand as Instruction; @@ -190,26 +195,26 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. // FIXME: calculate the potential constant branch (currently there are no optimizable methods where the switch condition is constant, so this is not needed for now) var targets = ins.Operand as Instruction []; if (targets is null) { - Driver.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); + data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); return false; } foreach (var target in targets) { // not constant, continue marking both this code sequence and the branched sequence - if (!MarkInstructions (method, instructions, reachable, instructions.IndexOf (target), end)) + if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (target), end)) return false; } - return MarkInstructions (method, instructions, reachable, instructions.IndexOf (ins.Next), end); + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (ins.Next), end); } if (cond_target is null) { - Driver.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); + data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); return false; } switch (ins.OpCode.Code) { case Code.Brtrue: case Code.Brtrue_S: { - var v = GetConstantValue (ins.Previous); + var v = GetConstantValue (data, ins.Previous); if (v.HasValue) branch = v.Value != 0; cond_instruction_count = 2; @@ -217,7 +222,7 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. } case Code.Brfalse: case Code.Brfalse_S: { - var v = GetConstantValue (ins.Previous); + var v = GetConstantValue (data, ins.Previous); if (v.HasValue) branch = v.Value == 0; cond_instruction_count = 2; @@ -225,8 +230,8 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. } case Code.Beq: case Code.Beq_S: { - var x1 = GetConstantValue (ins.Previous?.Previous); - var x2 = GetConstantValue (ins.Previous); + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); if (x1.HasValue && x2.HasValue) branch = x1.Value == x2.Value; cond_instruction_count = 3; @@ -234,8 +239,8 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. } case Code.Bne_Un: case Code.Bne_Un_S: { - var x1 = GetConstantValue (ins.Previous?.Previous); - var x2 = GetConstantValue (ins.Previous); + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); if (x1.HasValue && x2.HasValue) branch = x1.Value != x2.Value; cond_instruction_count = 3; @@ -245,8 +250,8 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. case Code.Ble_S: case Code.Ble_Un: case Code.Ble_Un_S: { - var x1 = GetConstantValue (ins.Previous?.Previous); - var x2 = GetConstantValue (ins.Previous); + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); if (x1.HasValue && x2.HasValue) branch = x1.Value <= x2.Value; cond_instruction_count = 3; @@ -256,8 +261,8 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. case Code.Blt_S: case Code.Blt_Un: case Code.Blt_Un_S: { - var x1 = GetConstantValue (ins.Previous?.Previous); - var x2 = GetConstantValue (ins.Previous); + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); if (x1.HasValue && x2.HasValue) branch = x1.Value < x2.Value; cond_instruction_count = 3; @@ -267,8 +272,8 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. case Code.Bge_S: case Code.Bge_Un: case Code.Bge_Un_S: { - var x1 = GetConstantValue (ins.Previous?.Previous); - var x2 = GetConstantValue (ins.Previous); + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); if (x1.HasValue && x2.HasValue) branch = x1.Value >= x2.Value; cond_instruction_count = 3; @@ -278,15 +283,15 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. case Code.Bgt_S: case Code.Bgt_Un: case Code.Bgt_Un_S: { - var x1 = GetConstantValue (ins.Previous?.Previous); - var x2 = GetConstantValue (ins.Previous); + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); if (x1.HasValue && x2.HasValue) branch = x1.Value > x2.Value; cond_instruction_count = 3; break; } default: - Driver.Log ("Can't optimize {0} because of unknown branch instruction: {1}", method, ins); + data.App.Log ("Can't optimize {0} because of unknown branch instruction: {1}", method, ins); break; } @@ -294,13 +299,13 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. // Make sure nothing else in the method branches into the middle of our supposedly constant condition, // bypassing our constant calculation. Note that it's not a bad to branch to the _first_ instruction in // the sequence (thus the +2 here), just into the middle of it. - if (AnyBranchTo (instructions, instructions [i - cond_instruction_count + 2], ins)) + if (AnyBranchTo (data, instructions, instructions [i - cond_instruction_count + 2], ins)) branch = null; } if (!branch.HasValue) { // not constant, continue marking both this code sequence and the branched sequence - if (!MarkInstructions (method, instructions, reachable, instructions.IndexOf (cond_target), end)) + if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end)) return false; } else { // we can remove the branch (and the code that loads the condition), so we mark those instructions as dead. @@ -310,7 +315,7 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. // Now continue marking according to whether we branched or not if (branch.Value) { // branch always taken - return MarkInstructions (method, instructions, reachable, instructions.IndexOf (cond_target), end); + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end); } else { // branch never taken // continue looping @@ -329,7 +334,7 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. case FlowControl.Meta: case FlowControl.Phi: default: - Driver.Log (4, "Can't optimize {0} because of unknown flow control for: {1}", method, ins); + data.App.Log (4, "Can't optimize {0} because of unknown flow control for: {1}", method, ins); return false; } } @@ -338,10 +343,10 @@ static bool MarkInstructions (MethodDefinition method, Mono.Collections.Generic. } // Check if there are any branches in the instructions that branch to anywhere between 'first' and 'last' instructions (both inclusive). - static bool AnyBranchTo (Mono.Collections.Generic.Collection instructions, Instruction first, Instruction last) + static bool AnyBranchTo (OptimizeGeneratedCodeData data, Mono.Collections.Generic.Collection instructions, Instruction first, Instruction last) { if (first.Offset > last.Offset) { - Driver.Log ($"Broken assumption: {first} is after {last}"); + data.App.Log ($"Broken assumption: {first} is after {last}"); return true; // This is the safe thing to do, since it will prevent inlining } @@ -373,7 +378,7 @@ static bool EliminateDeadCode (OptimizeGeneratedCodeData data, MethodDefinition // marking all reachable instructions. Any non-reachable instructions at the end // can be removed. - if (!MarkInstructions (caller, instructions, reachable, 0, instructions.Count)) + if (!MarkInstructions (data, caller, instructions, reachable, 0, instructions.Count)) return modified; // Handle exception handlers specially, they do not follow normal code flow. @@ -407,19 +412,19 @@ static bool EliminateDeadCode (OptimizeGeneratedCodeData data, MethodDefinition } } if (!allNops) { - if (!MarkInstructions (caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) + if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) return modified; } break; case ExceptionHandlerType.Finally: // finally clauses are always executed, even if the protected region is empty - if (!MarkInstructions (caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) + if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) return modified; break; case ExceptionHandlerType.Fault: case ExceptionHandlerType.Filter: // FIXME: and until fixed, exit gracefully without doing anything - Driver.Log (4, "Unhandled exception handler: {0}, skipping dead code elimination for {1}", eh.HandlerType, caller); + data.App.Log (4, "Unhandled exception handler: {0}, skipping dead code elimination for {1}", eh.HandlerType, caller); return modified; } } @@ -475,7 +480,7 @@ static bool EliminateDeadCode (OptimizeGeneratedCodeData data, MethodDefinition case FlowControl.Cond_Branch: var target = (Instruction) ins.Operand; if (target.Offset > last_reachable_offset) { - Driver.Log (4, "Can't optimize {0} because of branching beyond last instruction alive: {1}", caller, ins); + data.App.Log (4, "Can't optimize {0} because of branching beyond last instruction alive: {1}", caller, ins); return modified; } break; @@ -483,13 +488,13 @@ static bool EliminateDeadCode (OptimizeGeneratedCodeData data, MethodDefinition } } #if false - Console.WriteLine ($"{caller.FullName}:"); + data.App.Log ($"{caller.FullName}:"); for (int i = 0; i < reachable.Length; i++) { - Console.WriteLine ($"{(reachable [i] ? " " : "- ")} {instructions [i]}"); + data.App.Log ($"{(reachable [i] ? " " : "- ")} {instructions [i]}"); if (!reachable [i]) Nop (instructions [i]); } - Console.WriteLine (); + data.App.Log (""); #endif // Exterminate, exterminate, exterminate @@ -648,7 +653,7 @@ static bool ProcessEnsureUIThread (OptimizeGeneratedCodeData data, MethodDefinit // Verify a few assumptions before doing anything const string operation = "remove calls to [NS|UI]Application::EnsureUIThread"; - if (!ValidateInstruction (caller, ins, operation, Code.Call)) + if (!ValidateInstruction (data, caller, ins, operation, Code.Call)) return false; // This is simple: just remove the call @@ -680,10 +685,10 @@ static bool ProcessIsDirectBinding (OptimizeGeneratedCodeData data, MethodDefini return false; // Verify a few assumptions before doing anything - if (!ValidateInstruction (caller, ins.Previous, operation, Code.Ldarg_0)) + if (!ValidateInstruction (data, caller, ins.Previous, operation, Code.Ldarg_0)) return false; - if (!ValidateInstruction (caller, ins, operation, Code.Call)) + if (!ValidateInstruction (data, caller, ins, operation, Code.Call)) return false; // Clearing the branch succeeded, so clear the condition too @@ -742,10 +747,10 @@ static bool ProcessSetupBlock (OptimizeGeneratedCodeData data, MethodDefinition prev = prev.Previous; // Skip any nops. if (prev.OpCode.StackBehaviourPush != StackBehaviour.Push1) { //todo: localize mmp error 2106 - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); return false; } else if (prev.OpCode.StackBehaviourPop != StackBehaviour.Pop0) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); return false; } @@ -756,22 +761,22 @@ static bool ProcessSetupBlock (OptimizeGeneratedCodeData data, MethodDefinition // Then find the type of the previous instruction (the first argument to SetupBlock[Unsafe]) var trampolineDelegateType = GetPushedType (caller, loadTrampolineInstruction); if (trampolineDelegateType is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_A, caller, ins.Offset, mr.Name, loadTrampolineInstruction)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_A, caller, ins.Offset, mr.Name, loadTrampolineInstruction)); return false; } if (trampolineDelegateType.Is ("System", "Delegate") || trampolineDelegateType.Is ("System", "MulticastDelegate")) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_B, caller, trampolineDelegateType.FullName, mr.Name)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_B, caller, trampolineDelegateType.FullName, mr.Name)); return false; } if (!data.LinkContext.App.StaticRegistrar.TryComputeBlockSignature (caller, trampolineDelegateType, out var exception, out signature)) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, exception, caller, ins, Errors.MM2106_D, caller, ins.Offset, exception.Message)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, exception, caller, ins, Errors.MM2106_D, caller, ins.Offset, exception.Message)); return false; } } catch (Exception e) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); return false; } @@ -854,34 +859,34 @@ static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, Meth // Verify 'ldstr ...' var loadString = GetPreviousSkippingNops (ins); if (loadString.OpCode != OpCodes.Ldstr) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadString)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadString)); return false; } // Verify 'call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)' var callGetTypeFromHandle = GetPreviousSkippingNops (loadString); if (callGetTypeFromHandle.OpCode != OpCodes.Call || !(callGetTypeFromHandle.Operand is MethodReference methodOperand) || methodOperand.Name != "GetTypeFromHandle" || !methodOperand.DeclaringType.Is ("System", "Type")) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, callGetTypeFromHandle)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, callGetTypeFromHandle)); return false; } // Verify 'ldtoken ...' var loadType = GetPreviousSkippingNops (callGetTypeFromHandle); if (loadType.OpCode != OpCodes.Ldtoken) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType)); return false; } // Then find the type of the previous instruction var trampolineContainerTypeReference = loadType.Operand as TypeReference; if (trampolineContainerTypeReference is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType.Operand)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType.Operand)); return false; } var trampolineContainerType = trampolineContainerTypeReference.Resolve (); if (trampolineContainerType is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, trampolineContainerTypeReference)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, trampolineContainerTypeReference)); return false; } @@ -889,15 +894,15 @@ static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, Meth var trampolineMethodName = (string) loadString.Operand; var trampolineMethods = trampolineContainerType.Methods.Where (v => v.Name == trampolineMethodName).ToArray (); if (!trampolineMethods.Any ()) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E1 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because no method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E1 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because no method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); return false; } else if (trampolineMethods.Count () > 1) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E2 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because more than one method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E2 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because more than one method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); return false; } var trampolineMethod = trampolineMethods [0]; if (!trampolineMethod.HasParameters || trampolineMethod.Parameters.Count < 1) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_F /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' must have at least one parameter. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_F /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' must have at least one parameter. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); return false; } @@ -908,18 +913,18 @@ static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, Meth } else if (firstParameterType is PointerType ptrType) { var ptrTargetType = ptrType.ElementType; if (!(ptrTargetType.Is ("System", "Void") || ptrTargetType.Is ("ObjCRuntime", "BlockLiteral"))) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); return false; } // ok } else { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); return false; } // Check that the method has [UnmanagedCallersOnly] if (!trampolineMethod.HasCustomAttributes || !trampolineMethod.CustomAttributes.Any (v => v.AttributeType.Is ("System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute"))) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_H /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' does not have an [UnmanagedCallersOnly] attribute. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_H /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' does not have an [UnmanagedCallersOnly] attribute. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); return false; } @@ -933,7 +938,7 @@ static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, Meth } if (userMethod is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_D, caller, ins.Offset, "Could not find delegate invoke method")); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_D, caller, ins.Offset, "Could not find delegate invoke method")); return false; } @@ -945,7 +950,7 @@ static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, Meth sequenceStart = loadType; } catch (Exception e) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); return false; } @@ -963,7 +968,7 @@ static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, Meth // Change the call to call the ctor with the string signature parameter instead ins.Operand = GetBlockLiteralConstructor (data, caller, ins); - Driver.Log (4, "Optimized call to BlockLiteral..ctor in {0} at offset {1} with signature {2}", caller, ins.Offset, signature); + data.App.Log (4, "Optimized call to BlockLiteral..ctor in {0} at offset {1} with signature {2}", caller, ins.Offset, signature); instructionsAddedOrRemoved = instructionDiff; return true; } @@ -1004,7 +1009,7 @@ static bool ProcessIsARM64CallingConvention (OptimizeGeneratedCodeData data, Met if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) return false; - if (!ValidateInstruction (caller, ins, operation, Code.Ldsfld)) + if (!ValidateInstruction (data, caller, ins, operation, Code.Ldsfld)) return false; // We're fine, inline the Runtime.IsARM64CallingConvention value @@ -1027,7 +1032,7 @@ static bool ProcessRuntimeArch (OptimizeGeneratedCodeData data, MethodDefinition return false; // Verify a few assumptions before doing anything - if (!ValidateInstruction (caller, ins, operation, Code.Ldsfld)) + if (!ValidateInstruction (data, caller, ins, operation, Code.Ldsfld)) return false; // We're fine, inline the Runtime.Arch condition @@ -1153,52 +1158,52 @@ static bool ProcessProtocolInterfaceStaticConstructor (OptimizeGeneratedCodeData return false; if (data.Optimizations.RegisterProtocols != true) { - Driver.Log (4, "Did not optimize static constructor in the protocol interface {0}: the 'register-protocols' optimization is disabled.", method.DeclaringType.FullName); + data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: the 'register-protocols' optimization is disabled.", method.DeclaringType.FullName); return false; } if (!method.DeclaringType.HasCustomAttributes || !method.DeclaringType.CustomAttributes.Any (v => v.AttributeType.Is ("Foundation", "ProtocolAttribute"))) { - Driver.Log (4, "Did not optimize static constructor in the protocol interface {0}: no Protocol attribute found.", method.DeclaringType.FullName); + data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: no Protocol attribute found.", method.DeclaringType.FullName); return false; } var ins = SkipNops (method.Body.Instructions.First ()); if (ins is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); return false; } else if (ins.OpCode != OpCodes.Ldnull) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); return false; } ins = SkipNops (ins.Next); if (ins is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); return false; } var callGCKeepAlive = ins; if (callGCKeepAlive.OpCode != OpCodes.Call || !(callGCKeepAlive.Operand is MethodReference methodOperand) || methodOperand.Name != "KeepAlive" || !methodOperand.DeclaringType.Is ("System", "GC")) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); return false; } ins = SkipNops (ins.Next); if (ins is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); return false; } else if (ins.OpCode != OpCodes.Ret) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); return false; } ins = SkipNops (ins.Next); if (ins is not null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); return false; } // We can just remove the entire method, however that confuses the linker later on, so just empty it out and remove all the attributes. - Driver.Log (4, "Optimized static constructor in the protocol interface {0} (static constructor was cleared and custom attributes removed)", method.DeclaringType.FullName); + data.App.Log (4, "Optimized static constructor in the protocol interface {0} (static constructor was cleared and custom attributes removed)", method.DeclaringType.FullName); method.Body.Instructions.Clear (); method.Body.Instructions.Add (Instruction.Create (OpCodes.Ret)); @@ -1234,6 +1239,8 @@ public class OptimizeGeneratedCodeData { public MethodDefinition? SetupBlockImplDefinition; public MethodDefinition? BlockCtorDefinition; public bool? InlineIsArm64CallingConvention; + + public Application App => LinkContext.App; } } diff --git a/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs b/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs index b74d18cddca7..bb2193ab35ad 100644 --- a/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs +++ b/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs @@ -201,18 +201,18 @@ bool ProcessMethod (MethodDefinition method) if (Configuration.InlineClassGetHandle != InlineClassGetHandleMode.Disabled && pinfo.EntryPoint.StartsWith (InlineClassGetHandleStep.PInvokePrefix, StringComparison.Ordinal)) break; } - Driver.Log (4, "Adding native reference to {0} in {1} because it's referenced by {2} in {3}.", pinfo.EntryPoint, pinfo.Module.Name, method.FullName, method.Module.Name); + DerivedLinkContext.App.Log (4, "Adding native reference to {0} in {1} because it's referenced by {2} in {3}.", pinfo.EntryPoint, pinfo.Module.Name, method.FullName, method.Module.Name); DerivedLinkContext.RequiredSymbols.AddFunction (pinfo.EntryPoint).AddMember (method); break; default: if (!addPInvokeSymbol) - Driver.Log (4, "Did not add native reference to {0} in {1} referenced by {2} in {3}.", pinfo.EntryPoint, pinfo.Module.Name, method.FullName, method.Module.Name); + DerivedLinkContext.App.Log (4, "Did not add native reference to {0} in {1} referenced by {2} in {3}.", pinfo.EntryPoint, pinfo.Module.Name, method.FullName, method.Module.Name); break; } if (addPInvokeSymbol) { - Driver.Log (4, "Adding native reference to {0} in {1} because it's referenced by {2} in {3}.", pinfo.EntryPoint, pinfo.Module.Name, method.FullName, method.Module.Name); + DerivedLinkContext.App.Log (4, "Adding native reference to {0} in {1} because it's referenced by {2} in {3}.", pinfo.EntryPoint, pinfo.Module.Name, method.FullName, method.Module.Name); DerivedLinkContext.RequireMonoNative = true; if (DerivedLinkContext.App.Platform != ApplePlatform.MacOSX && DerivedLinkContext.App.LibMonoNativeLinkMode == AssemblyBuildTarget.StaticObject) { diff --git a/tools/linker/RegistrarRemovalTrackingStep.cs b/tools/linker/RegistrarRemovalTrackingStep.cs index f88452f3ccd0..b2cd10bc170c 100644 --- a/tools/linker/RegistrarRemovalTrackingStep.cs +++ b/tools/linker/RegistrarRemovalTrackingStep.cs @@ -153,7 +153,7 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) void Warn (AssemblyDefinition assembly, MemberReference mr) { - ErrorHelper.Warning (WarnCode, Errors.MM2107, assembly.Name.Name, mr.DeclaringType.FullName, mr.Name, string.Join (", ", ((MethodReference) mr).Parameters.Select ((v) => v.ParameterType.FullName))); + ErrorHelper.Warning (App, WarnCode, Errors.MM2107, assembly.Name.Name, mr.DeclaringType.FullName, mr.Name, string.Join (", ", ((MethodReference) mr).Parameters.Select ((v) => v.ParameterType.FullName))); } protected override void TryEndProcess () @@ -164,7 +164,7 @@ protected override void TryEndProcess () Optimizations.RemoveDynamicRegistrar = !dynamic_registration_support_required; } - Driver.Log (4, "Optimization dynamic registrar removal: {0}", Optimizations.RemoveDynamicRegistrar.Value ? "enabled" : "disabled"); + App.Log (4, "Optimization dynamic registrar removal: {0}", Optimizations.RemoveDynamicRegistrar.Value ? "enabled" : "disabled"); if (Optimizations.RemoveDynamicRegistrar.Value) { // ILLink will optimize `Runtime.Initialize` based on `DynamicRegistrationSupported` returning a constant (`true`) diff --git a/tools/mtouch/AssemblyResolver.cs b/tools/mtouch/AssemblyResolver.cs index f63d6ebebbe7..e5386afd5df5 100644 --- a/tools/mtouch/AssemblyResolver.cs +++ b/tools/mtouch/AssemblyResolver.cs @@ -22,10 +22,6 @@ #nullable enable namespace MonoTouch.Tuner { - - public partial class MonoTouchManifestResolver : MonoTouchResolver { - } - // recent cecil removed some overloads - https://github.com/mono/cecil/commit/42db79cc16f1cbe8dbab558904e188352dba2b41 public static class AssemblyResolverRocks { @@ -41,6 +37,12 @@ public static AssemblyDefinition Resolve (this IAssemblyResolver self, string fu } public class MonoTouchResolver : CoreResolver { + Application app; + + public MonoTouchResolver (Application app) + { + this.app = app; + } public IEnumerable GetAssemblies () { @@ -63,29 +65,29 @@ public void Add (AssemblyDefinition assembly) if (FrameworkDirectory is not null) { var facadeDir = Path.Combine (FrameworkDirectory, "Facades"); - assembly = SearchDirectory (aname, facadeDir); + assembly = SearchDirectory (app, aname, facadeDir); if (assembly is not null) return assembly; } if (ArchDirectory is not null) { - assembly = SearchDirectory (aname, ArchDirectory); + assembly = SearchDirectory (app, aname, ArchDirectory); if (assembly is not null) return assembly; } if (FrameworkDirectory is not null) { - assembly = SearchDirectory (aname, FrameworkDirectory); + assembly = SearchDirectory (app, aname, FrameworkDirectory); if (assembly is not null) return assembly; } if (RootDirectory is not null) { - assembly = SearchDirectory (aname, RootDirectory); + assembly = SearchDirectory (app, aname, RootDirectory); if (assembly is not null) return assembly; - assembly = SearchDirectory (aname, RootDirectory, ".exe"); + assembly = SearchDirectory (app, aname, RootDirectory, ".exe"); if (assembly is not null) return assembly; } diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index a11f77fcc353..d9ba0a1ea76e 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -171,6 +171,9 @@ external/tools/common/XamarinRuntime.cs + + external/tools/common/IToolLog.cs + From d1a9361cd94e6d2b3aab38b18c2c451c9d171bf9 Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Tue, 2 Jun 2026 12:21:12 -0400 Subject: [PATCH 56/97] [devops] yaml-templates go back to use main (#25600) --- tools/devops/automation/build-pipeline.yml | 2 +- tools/devops/automation/build-pull-request.yml | 2 +- tools/devops/automation/publish-pr-html-results.yml | 2 +- tools/devops/automation/run-nightly-codeql.yml | 2 +- .../devops/automation/templates/pipelines/api-diff-pipeline.yml | 2 +- tools/devops/automation/templates/pipelines/build-pipeline.yml | 2 +- tools/devops/automation/templates/pipelines/run-api-scan.yml | 2 +- .../automation/templates/pipelines/run-tests-pipeline.yml | 2 +- tools/devops/automation/vs-insertion.yml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/devops/automation/build-pipeline.yml b/tools/devops/automation/build-pipeline.yml index d3e3f8b93895..97824c8abe3b 100644 --- a/tools/devops/automation/build-pipeline.yml +++ b/tools/devops/automation/build-pipeline.yml @@ -45,7 +45,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main - repository: CustomPipelineTemplates type: git diff --git a/tools/devops/automation/build-pull-request.yml b/tools/devops/automation/build-pull-request.yml index b6e7f76620cf..8d71f0ad7231 100644 --- a/tools/devops/automation/build-pull-request.yml +++ b/tools/devops/automation/build-pull-request.yml @@ -36,7 +36,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main - repository: CustomPipelineTemplates type: git diff --git a/tools/devops/automation/publish-pr-html-results.yml b/tools/devops/automation/publish-pr-html-results.yml index c1b7f08c1b2b..254babc35993 100644 --- a/tools/devops/automation/publish-pr-html-results.yml +++ b/tools/devops/automation/publish-pr-html-results.yml @@ -14,7 +14,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main # we need all stages to be completed, else we do not have the test results, this trigger is just for CI, because we have # but because we have device issues, and it needs to be gree to trigger, we will deal with it later diff --git a/tools/devops/automation/run-nightly-codeql.yml b/tools/devops/automation/run-nightly-codeql.yml index 1e4144a6028e..f5375ab46ea2 100644 --- a/tools/devops/automation/run-nightly-codeql.yml +++ b/tools/devops/automation/run-nightly-codeql.yml @@ -21,7 +21,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main variables: - template: /tools/devops/automation/templates/variables/common.yml diff --git a/tools/devops/automation/templates/pipelines/api-diff-pipeline.yml b/tools/devops/automation/templates/pipelines/api-diff-pipeline.yml index 5b81d42282de..26bb8c290581 100644 --- a/tools/devops/automation/templates/pipelines/api-diff-pipeline.yml +++ b/tools/devops/automation/templates/pipelines/api-diff-pipeline.yml @@ -31,7 +31,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main variables: - template: ../variables/common.yml diff --git a/tools/devops/automation/templates/pipelines/build-pipeline.yml b/tools/devops/automation/templates/pipelines/build-pipeline.yml index 0d7af904d237..ebd3ee2f4ae7 100644 --- a/tools/devops/automation/templates/pipelines/build-pipeline.yml +++ b/tools/devops/automation/templates/pipelines/build-pipeline.yml @@ -59,7 +59,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main variables: - ${{ if eq(parameters.isPR, false) }}: diff --git a/tools/devops/automation/templates/pipelines/run-api-scan.yml b/tools/devops/automation/templates/pipelines/run-api-scan.yml index 116c6fcbeb61..44fd63925e64 100644 --- a/tools/devops/automation/templates/pipelines/run-api-scan.yml +++ b/tools/devops/automation/templates/pipelines/run-api-scan.yml @@ -25,7 +25,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main - repository: release-scripts type: github diff --git a/tools/devops/automation/templates/pipelines/run-tests-pipeline.yml b/tools/devops/automation/templates/pipelines/run-tests-pipeline.yml index a3ef20748f63..47e83ca8c65f 100644 --- a/tools/devops/automation/templates/pipelines/run-tests-pipeline.yml +++ b/tools/devops/automation/templates/pipelines/run-tests-pipeline.yml @@ -45,7 +45,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main variables: - template: ../variables/common.yml diff --git a/tools/devops/automation/vs-insertion.yml b/tools/devops/automation/vs-insertion.yml index 90eef300b8a1..f98208720aed 100644 --- a/tools/devops/automation/vs-insertion.yml +++ b/tools/devops/automation/vs-insertion.yml @@ -12,7 +12,7 @@ resources: - repository: yaml-templates type: git name: xamarin.yaml-templates - ref: e30b445c2ffcbebe75efdd69546d7196a20c5a43 + ref: refs/heads/main # we need all stages to be completed, else we do not have the test results, this trigger is just for CI, because we have # but because we have device issues, and it needs to be gree to trigger, we will deal with it later From c3564fff2e05dc3403037e2fe158d76cf309a8e4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 2 Jun 2026 19:06:45 +0200 Subject: [PATCH 57/97] [tests] Update NUnit packages to latest stable versions (#25516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the following package versions: - NUnit: 4.4.0 → 4.6.1 - NUnit3TestAdapter: 6.1.0 → 6.2.0 - NUnitAnalyzers: 4.7.0 → 4.13.0 - NUnitXmlTestLogger: 3.1.15 → 8.0.0 - NUnitLite: 3.12.0 → 4.6.1 - NUnitV2ResultWriter: 3.6.0 → 3.8.0 Also remove the tools/nunit3-console* scripts, they're no longer needed. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.props | 12 ++++++------ tests/common/shared-dotnet.csproj | 14 ++++++++++++++ tests/dotnet/UnitTests/BundleStructureTest.cs | 1 + tests/monotouch-test/AppKit/NSPasteboard.cs | 6 +++--- tests/monotouch-test/AppKit/NSView.cs | 5 +++++ tests/monotouch-test/Foundation/ObjectTest.cs | 1 - .../ObjCRuntime/DelegateAndDataSourceTest.cs | 2 ++ tests/monotouch-test/ObjCRuntime/RegistrarTest.cs | 2 +- .../Security/SecSharedCredentialTest.cs | 8 -------- .../VTFrameRateConversionConfigurationTest.cs | 6 +++--- .../VideoToolbox/VTMotionBlurConfigurationTest.cs | 8 +++++--- .../VideoToolbox/VTOpticalFlowConfigurationTest.cs | 8 +++++--- tests/nunit.framework.targets | 2 +- tools/nunit3-console-3.10.0 | 10 ---------- tools/nunit3-console-3.11.1 | 10 ---------- tools/nunit3-console-3.12.0 | 10 ---------- tools/nunit3-console-3.9.0 | 10 ---------- 17 files changed, 46 insertions(+), 69 deletions(-) delete mode 100755 tools/nunit3-console-3.10.0 delete mode 100755 tools/nunit3-console-3.11.1 delete mode 100755 tools/nunit3-console-3.12.0 delete mode 100755 tools/nunit3-console-3.9.0 diff --git a/Directory.Build.props b/Directory.Build.props index f054d696a5d3..ac90e9e40ed2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ $(MicrosoftBuildPackageVersion) $(MicrosoftBuildPackageVersion) 3.22.0 - 3.1.15 + 8.0.0 4.7.2 @@ -19,11 +19,11 @@ 21.1.8 18.0.1 0.11.6 - 6.1.0 - 4.7.0 - 4.4.0 - 3.12.0 - 3.6.0 + 6.2.0 + 4.13.0 + 4.6.1 + $(NUnitPackageVersion) + 3.8.0 latest diff --git a/tests/common/shared-dotnet.csproj b/tests/common/shared-dotnet.csproj index 8c3e83197315..27ee7da59ae3 100644 --- a/tests/common/shared-dotnet.csproj +++ b/tests/common/shared-dotnet.csproj @@ -80,6 +80,11 @@ $(DefineConstants);NATIVEAOT false + + true + false @@ -101,8 +106,12 @@ true + + true + + @@ -126,6 +135,11 @@ $(DefineConstants);NATIVEAOT false + + true + false + NU5123;$(NoWarn) From e39f97b0e4a9b419e987b86260d61ba0ac34ce73 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 3 Jun 2026 09:26:55 +0200 Subject: [PATCH 62/97] [src] Mark MKDistanceFormatter as thread-safe. Fixes #25617. (#25620) Fixes https://github.com/dotnet/macios/issues/25617. --- src/mapkit.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mapkit.cs b/src/mapkit.cs index 1b4f59ee6e06..bc577b1f9f5f 100644 --- a/src/mapkit.cs +++ b/src/mapkit.cs @@ -2067,6 +2067,7 @@ partial interface MKRouteStep { [BaseType (typeof (NSFormatter))] [MacCatalyst (13, 1)] + [ThreadSafe] partial interface MKDistanceFormatter { [Export ("stringFromDistance:")] From 621547e5569c9e8e3de28449e5761916f669cfd1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 07:33:56 +0000 Subject: [PATCH 63/97] [github] Disable signed-commit replay in Code Radiator merge PR pushes (#25577) Code Radiator failed to create the `main -> xcode26.5` merge PR because safe-outputs attempted signed commit replay, then refused unsigned fallback when a submodule update was present. This change switches the merge workflow to explicit unsigned push behavior for PR branch updates. - **Root cause addressed** - Configure safe-outputs PR write paths to avoid `pushSignedCommits` replay for merge branches that can include unsupported commit shapes (notably submodule bumps). - **Workflow source updates (`code-radiator.md`)** - Set `signed-commits: false` under: - `safe-outputs.create-pull-request` - `safe-outputs.push-to-pull-request-branch` - **Compiled workflow parity (`code-radiator.lock.yml`)** - Updated generated safe-outputs handler/config payloads to include: - `"signed_commits": false` for `create_pull_request` - `"signed_commits": false` for `push_to_pull_request_branch` ```yaml safe-outputs: create-pull-request: max: 10 signed-commits: false push-to-pull-request-branch: max: 10 signed-commits: false ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rolfbjarne <249268+rolfbjarne@users.noreply.github.com> --- .github/workflows/code-radiator.lock.yml | 34 ++++++++++++------------ .github/workflows/code-radiator.md | 2 ++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/code-radiator.lock.yml b/.github/workflows/code-radiator.lock.yml index 39492934fcb3..76736b1d9f98 100644 --- a/.github/workflows/code-radiator.lock.yml +++ b/.github/workflows/code-radiator.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"beeb14d4346fb0901da58ea798ac8ec711e3ed27c3b1143809bcf69e85a5d8f8","body_hash":"b966744ea05e67fd471e10231f2cce01d0525af33bc90ff317655344e889cc92","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1ba69a47e6cfbd91cfe78d637aac47fa364d969c56a89ac3523d5c936169f250","body_hash":"b966744ea05e67fd471e10231f2cce01d0525af33bc90ff317655344e889cc92","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -192,24 +192,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_1f8577744e570dc4_EOF' + cat << 'GH_AW_PROMPT_686c839083e52610_EOF' - GH_AW_PROMPT_1f8577744e570dc4_EOF + GH_AW_PROMPT_686c839083e52610_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_1f8577744e570dc4_EOF' + cat << 'GH_AW_PROMPT_686c839083e52610_EOF' Tools: add_comment(max:10), create_pull_request(max:10), update_pull_request(max:10), add_labels(max:10), push_to_pull_request_branch(max:10), missing_tool, missing_data, noop - GH_AW_PROMPT_1f8577744e570dc4_EOF + GH_AW_PROMPT_686c839083e52610_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_1f8577744e570dc4_EOF' + cat << 'GH_AW_PROMPT_686c839083e52610_EOF' - GH_AW_PROMPT_1f8577744e570dc4_EOF + GH_AW_PROMPT_686c839083e52610_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_1f8577744e570dc4_EOF' + cat << 'GH_AW_PROMPT_686c839083e52610_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -241,12 +241,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_1f8577744e570dc4_EOF + GH_AW_PROMPT_686c839083e52610_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_1f8577744e570dc4_EOF' + cat << 'GH_AW_PROMPT_686c839083e52610_EOF' {{#runtime-import .github/workflows/code-radiator.md}} - GH_AW_PROMPT_1f8577744e570dc4_EOF + GH_AW_PROMPT_686c839083e52610_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -461,9 +461,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_752f6925d3d27640_EOF' - {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":1000,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review"},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} - GH_AW_SAFE_OUTPUTS_CONFIG_752f6925d3d27640_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_9a64ac31bc43ee2c_EOF' + {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":1000,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","signed_commits":false},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"signed_commits":false,"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} + GH_AW_SAFE_OUTPUTS_CONFIG_9a64ac31bc43ee2c_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -806,7 +806,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d132526c2d13876f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_ecdd849dd0c87e2d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -850,7 +850,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_d132526c2d13876f_EOF + GH_AW_MCP_CONFIG_ecdd849dd0c87e2d_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1594,7 +1594,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":1000,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\"},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":1000,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"signed_commits\":false},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"signed_commits\":false,\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/code-radiator.md b/.github/workflows/code-radiator.md index 5d4d18d5a0e7..e1fe86d0d97d 100644 --- a/.github/workflows/code-radiator.md +++ b/.github/workflows/code-radiator.md @@ -28,6 +28,7 @@ safe-outputs: max-patch-files: 1000 create-pull-request: max: 10 + signed-commits: false allowed-base-branches: - "net*.0" - "xcode*" @@ -42,6 +43,7 @@ safe-outputs: max: 10 push-to-pull-request-branch: max: 10 + signed-commits: false target: "*" required-title-prefix: "🤖 Merge 'main' => '" update-pull-request: From bb479716b669b3d2c8869b666aa58d24bd37b9b3 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 3 Jun 2026 10:44:39 +0200 Subject: [PATCH 64/97] [sharpie] Fix a parallel build issue by building before packing. (#25616) --------- Co-authored-by: Rolf Bjarne Kvinge Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tools/sharpie/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sharpie/Makefile b/tools/sharpie/Makefile index 56e53de56372..a2c79f4e8727 100644 --- a/tools/sharpie/Makefile +++ b/tools/sharpie/Makefile @@ -21,7 +21,7 @@ SHARPIE_BIND_TOOL_NUPKG_NAME=Sharpie.Bind.Tool.$(SHARPIE_BIND_TOOL_NUGET_VERSION SHARPIE_BIND_TOOL_NUPKG=Sharpie.Bind.Tool/bin/Release/$(SHARPIE_BIND_TOOL_NUPKG_NAME) pack: $(SHARPIE_BIND_TOOL_NUPKG) -$(SHARPIE_BIND_TOOL_NUPKG): $(Sharpie.Bind_dependencies) +$(SHARPIE_BIND_TOOL_NUPKG): $(Sharpie.Bind_dependencies) | $(SHARPIE_BIND_TOOL_DEBUG) $(Q_BUILD) $(DOTNET) pack Sharpie.Bind.Tool/Sharpie.Bind.Tool.csproj "/p:Version=$(SHARPIE_VERSION)" "/p:CurrentBranch=$(CURRENT_BRANCH)" "/p:CurrentHash=$(CURRENT_HASH_LONG)" $(DOTNET_PACK_VERBOSITY) -bl:$@.binlog all-local:: $(DOTNET_NUPKG_DIR)/$(SHARPIE_BIND_TOOL_NUPKG_NAME) From ad81f10dde27e116916126b0438e090d1ed4ae17 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 3 Jun 2026 20:47:44 +0200 Subject: [PATCH 65/97] [apidiff] Add nullability support to mono-api-info and mono-api-html (#25603) mono-api-info now reads NullableAttribute and NullableContextAttribute metadata from assemblies and appends '?' to type names for nullable reference types in the XML output (parameters, return types, properties, fields, and events). mono-api-html now: - Strips nullability annotations when matching methods (so nullability- only changes don't cause false removed/added entries) - Detects nullability-only changes and renders them under a separate '(nullability)' subsection header to indicate they are non-breaking - Handles the '?' suffix in type name resolution (GetTypeName) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/api-tools/mono-api-html/ApiChange.cs | 127 +++++++++++++++- .../mono-api-html/ConstructorComparer.cs | 2 +- .../api-tools/mono-api-html/EventComparer.cs | 2 +- .../api-tools/mono-api-html/FieldComparer.cs | 2 +- tools/api-tools/mono-api-html/Formatter.cs | 7 + tools/api-tools/mono-api-html/Helpers.cs | 36 +++++ .../api-tools/mono-api-html/MemberComparer.cs | 23 ++- .../api-tools/mono-api-html/MethodComparer.cs | 2 +- .../mono-api-html/PropertyComparer.cs | 4 +- .../api-tools/mono-api-info/mono-api-info.cs | 142 +++++++++++++++++- 10 files changed, 330 insertions(+), 17 deletions(-) diff --git a/tools/api-tools/mono-api-html/ApiChange.cs b/tools/api-tools/mono-api-html/ApiChange.cs index 3bf895a47b00..18cc90285b5c 100644 --- a/tools/api-tools/mono-api-html/ApiChange.cs +++ b/tools/api-tools/mono-api-html/ApiChange.cs @@ -10,6 +10,7 @@ class ApiChange { public string Header = ""; public TextChunk Member = new TextChunk (); public bool AnyChange; + public bool IsNullabilityChange; public string SourceDescription; public State State; @@ -45,6 +46,76 @@ public ApiChange AppendModified (string old, string @new) AnyChange = true; return this; } + + // Renders a type change: uses inline nullability rendering if the only + // difference is '?' annotations, otherwise falls back to full modification. + public ApiChange AppendTypeModified (string old, string @new) + { + if (Helper.DiffersOnlyByNullability (old, @new)) + return AppendNullabilityModified (old, @new); + return AppendModified (old, @new); + } + + // Renders a type modification where only nullability annotations ('?') differ. + // Only the added/removed '?' characters are highlighted; the rest is plain text. + public ApiChange AppendNullabilityModified (string old, string @new) + { + int si = 0; + int ti = 0; + while (si < old.Length && ti < @new.Length) { + if (old [si] == @new [ti]) { + Member.Append (old [si]); + si++; + ti++; + } else if (old [si] == '?' && IsNullabilitySuffix (old, si)) { + State.Formatter.DiffRemoval (Member, "?"); + AnyChange = true; + si++; + } else if (@new [ti] == '?' && IsNullabilitySuffix (@new, ti)) { + State.Formatter.DiffAddition (Member, "?"); + AnyChange = true; + ti++; + } else { + // Shouldn't happen for nullability-only diffs, fall back to full modification + State.Formatter.DiffModification (Member, old.Substring (si), @new.Substring (ti)); + AnyChange = true; + return this; + } + } + // Handle remaining characters + while (si < old.Length) { + if (old [si] == '?' && IsNullabilitySuffix (old, si)) { + State.Formatter.DiffRemoval (Member, "?"); + AnyChange = true; + si++; + } else { + Member.Append (old [si]); + si++; + } + } + while (ti < @new.Length) { + if (@new [ti] == '?' && IsNullabilitySuffix (@new, ti)) { + State.Formatter.DiffAddition (Member, "?"); + AnyChange = true; + ti++; + } else { + Member.Append (@new [ti]); + ti++; + } + } + return this; + } + + static bool IsNullabilitySuffix (string text, int index) + { + // A '?' is a nullability suffix if it's at the end, or before a type separator. + // The input type names are already formatted (via GetTypeName + Formatter), so generic + // brackets appear as HTML entities (< / >). A '?' before '&' catches the > case. + if (index + 1 >= text.Length) + return true; + char next = text [index + 1]; + return next == ']' || next == ',' || next == '>' || next == '&' || next == ' '; + } } class ApiChanges : Dictionary> { @@ -61,12 +132,66 @@ public void Add (XElement source, XElement target, ApiChange change) if (!change.AnyChange) return; + // Detect if this change is nullability-only + if (DiffersOnlyByNullability (source, target)) + change.IsNullabilityChange = true; + if (!TryGetValue (change.Header, out List? list)) { list = new List (); base.Add (change.Header, list); } list.Add (change); } + + static bool DiffersOnlyByNullability (XElement source, XElement target) + { + // Compare all attributes, stripping nullability from type-related attributes + var typeAttributes = new HashSet { "returntype", "fieldtype", "ptype", "eventtype", "type" }; + + if (source.Name != target.Name) + return false; + + // Check that all non-type-related attributes are the same + var srcAttrs = source.Attributes ().ToDictionary (a => a.Name.LocalName, a => a.Value); + var tgtAttrs = target.Attributes ().ToDictionary (a => a.Name.LocalName, a => a.Value); + + if (srcAttrs.Count != tgtAttrs.Count) + return false; + + bool hasNullabilityDiff = false; + foreach (var kvp in srcAttrs) { + if (!tgtAttrs.TryGetValue (kvp.Key, out var tgtValue)) + return false; + + if (kvp.Value == tgtValue) + continue; + + if (typeAttributes.Contains (kvp.Key)) { + if (Helper.DiffersOnlyByNullability (kvp.Value, tgtValue)) + hasNullabilityDiff = true; + else + return false; + } else { + return false; + } + } + + // Always check child elements — a non-nullability child diff means this + // is not a nullability-only change, even if the parent attributes differ only by nullability. + var srcChildren = source.Elements ().ToList (); + var tgtChildren = target.Elements ().ToList (); + if (srcChildren.Count != tgtChildren.Count) + return false; + + for (int i = 0; i < srcChildren.Count; i++) { + if (XNode.DeepEquals (srcChildren [i], tgtChildren [i])) + continue; + if (!DiffersOnlyByNullability (srcChildren [i], tgtChildren [i])) + return false; + hasNullabilityDiff = true; + } + + return hasNullabilityDiff; + } } } - diff --git a/tools/api-tools/mono-api-html/ConstructorComparer.cs b/tools/api-tools/mono-api-html/ConstructorComparer.cs index e541861f2093..5ae44bf7ee6d 100644 --- a/tools/api-tools/mono-api-html/ConstructorComparer.cs +++ b/tools/api-tools/mono-api-html/ConstructorComparer.cs @@ -59,7 +59,7 @@ void RenderReturnType (XElement source, XElement target, ApiChange change) var tgtType = target.GetTypeName ("returntype", State); if (srcType != tgtType) { - change.AppendModified (srcType ?? "", tgtType ?? ""); + change.AppendTypeModified (srcType ?? "", tgtType ?? ""); change.Append (" "); } else if (srcType is not null) { // ctor don't have a return type diff --git a/tools/api-tools/mono-api-html/EventComparer.cs b/tools/api-tools/mono-api-html/EventComparer.cs index 302d483a5008..123cfe850897 100644 --- a/tools/api-tools/mono-api-html/EventComparer.cs +++ b/tools/api-tools/mono-api-html/EventComparer.cs @@ -59,7 +59,7 @@ public override bool Equals (XElement source, XElement target, ApiChanges change var tgtEventType = target.GetTypeName ("eventtype", State) ?? ""; if (srcEventType != tgtEventType) { - change.AppendModified (srcEventType, tgtEventType); + change.AppendTypeModified (srcEventType, tgtEventType); } else { change.Append (srcEventType); } diff --git a/tools/api-tools/mono-api-html/FieldComparer.cs b/tools/api-tools/mono-api-html/FieldComparer.cs index ba600ba548c2..d0b6169cbf73 100644 --- a/tools/api-tools/mono-api-html/FieldComparer.cs +++ b/tools/api-tools/mono-api-html/FieldComparer.cs @@ -146,7 +146,7 @@ public override bool Equals (XElement source, XElement target, ApiChanges change var tgtType = target.GetTypeName ("fieldtype", State) ?? ""; if (srcType != tgtType) { - change.AppendModified (srcType, tgtType); + change.AppendTypeModified (srcType, tgtType); } else { change.Append (srcType); } diff --git a/tools/api-tools/mono-api-html/Formatter.cs b/tools/api-tools/mono-api-html/Formatter.cs index a7be2f256c0b..0fce910fc46c 100644 --- a/tools/api-tools/mono-api-html/Formatter.cs +++ b/tools/api-tools/mono-api-html/Formatter.cs @@ -211,6 +211,13 @@ public void Append (string value) cachedOutput.Append (value); } + public void Append (char value) + { + foreach (var kvp in stringbuilders) + kvp.StringBuilder.Append (value); + cachedOutput.Append (value); + } + public override string ToString () { throw new InvalidOperationException (); diff --git a/tools/api-tools/mono-api-html/Helpers.cs b/tools/api-tools/mono-api-html/Helpers.cs index 975eefacd838..9a98857a0826 100644 --- a/tools/api-tools/mono-api-html/Helpers.cs +++ b/tools/api-tools/mono-api-html/Helpers.cs @@ -127,6 +127,7 @@ static bool TryGetAttributeProperty (this XElement? self, string attributeName, StringBuilder sb = null!; bool is_nullable = false; + bool is_nullable_ref = false; if (type.StartsWith ("System.Nullable`1[", StringComparison.Ordinal)) { is_nullable = true; sb = new StringBuilder (type, 18, type.Length - 19, 1024); @@ -134,6 +135,12 @@ static bool TryGetAttributeProperty (this XElement? self, string attributeName, sb = new StringBuilder (type); } + // Handle nullable reference type annotation (trailing '?' added by mono-api-info) + if (!is_nullable && sb.Length > 0 && sb [sb.Length - 1] == '?') { + is_nullable_ref = true; + sb.Remove (sb.Length - 1, 1); + } + bool is_ref = (sb [sb.Length - 1] == '&'); if (is_ref) sb.Remove (sb.Length - 1, 1); @@ -159,6 +166,8 @@ static bool TryGetAttributeProperty (this XElement? self, string attributeName, sb.Append ("[]"); if (is_nullable) sb.Append ('?'); + if (is_nullable_ref) + sb.Append ('?'); if (is_pointer) sb.Append ('*'); return sb.ToString (); @@ -245,5 +254,32 @@ public static FieldAttributes GetFieldAttributes (this XElement element) var srcAttribs = element.Attribute ("attrib"); return (FieldAttributes) (srcAttribs is not null ? Int32.Parse (srcAttribs.Value) : 0); } + + // Strips trailing '?' nullability annotations from a type name for comparison purposes. + // Handles both top-level (System.String?) and nested generics (List`1[System.String?]). + public static string? StripNullability (string? type) + { + if (type is null) + return null; + // Remove all '?' that appear before ']', at end of string, before ',', + // before '>' or '&' (HTML entities like >), or before ' ' (before param name) + var sb = new StringBuilder (type.Length); + for (int i = 0; i < type.Length; i++) { + if (type [i] == '?') { + if (i + 1 >= type.Length || type [i + 1] == ']' || type [i + 1] == ',' || type [i + 1] == '>' || type [i + 1] == '&' || type [i + 1] == ' ') + continue; + } + sb.Append (type [i]); + } + return sb.ToString (); + } + + // Returns true if two type names differ only in nullability annotations. + public static bool DiffersOnlyByNullability (string? source, string? target) + { + if (source == target) + return false; + return StripNullability (source) == StripNullability (target); + } } } diff --git a/tools/api-tools/mono-api-html/MemberComparer.cs b/tools/api-tools/mono-api-html/MemberComparer.cs index d5a0ebfa216c..f0f5915c384b 100644 --- a/tools/api-tools/mono-api-html/MemberComparer.cs +++ b/tools/api-tools/mono-api-html/MemberComparer.cs @@ -134,11 +134,24 @@ void Add (IEnumerable elements) void Modify (ApiChanges modified) { foreach (var changes in modified) { - Formatter.BeginMemberModification (changes.Key); - foreach (var element in changes.Value) { - Formatter.Diff (element); + var nonNullability = changes.Value.Where (c => !c.IsNullabilityChange).ToList (); + var nullabilityOnly = changes.Value.Where (c => c.IsNullabilityChange).ToList (); + + if (nonNullability.Count > 0) { + Formatter.BeginMemberModification (changes.Key); + foreach (var element in nonNullability) { + Formatter.Diff (element); + } + Formatter.EndMemberModification (); + } + + if (nullabilityOnly.Count > 0) { + Formatter.BeginMemberModification (changes.Key + " (nullability)"); + foreach (var element in nullabilityOnly) { + Formatter.Diff (element); + } + Formatter.EndMemberModification (); } - Formatter.EndMemberModification (); } } @@ -315,7 +328,7 @@ protected void RenderParameters (XElement source, XElement target, ApiChange cha } if (paramSourceType != paramTargetType) { - change.AppendModified (paramSourceType ?? "", paramTargetType ?? ""); + change.AppendTypeModified (paramSourceType ?? "", paramTargetType ?? ""); } else { change.Append (paramSourceType ?? ""); } diff --git a/tools/api-tools/mono-api-html/MethodComparer.cs b/tools/api-tools/mono-api-html/MethodComparer.cs index 677a5136bdf1..6846c1cf6b4f 100644 --- a/tools/api-tools/mono-api-html/MethodComparer.cs +++ b/tools/api-tools/mono-api-html/MethodComparer.cs @@ -52,7 +52,7 @@ public override bool Find (XElement e) if (e.GetAttribute ("name") != Source.GetAttribute ("name")) return false; - if (e.GetAttribute ("returntype") != Source.GetAttribute ("returntype")) + if (Helper.StripNullability (e.GetAttribute ("returntype")) != Helper.StripNullability (Source.GetAttribute ("returntype"))) return false; var eGP = e.Element ("generic-parameters"); diff --git a/tools/api-tools/mono-api-html/PropertyComparer.cs b/tools/api-tools/mono-api-html/PropertyComparer.cs index 205600a6a199..a2783f0782d0 100644 --- a/tools/api-tools/mono-api-html/PropertyComparer.cs +++ b/tools/api-tools/mono-api-html/PropertyComparer.cs @@ -103,7 +103,7 @@ void RenderPropertyType (XElement source, XElement target, ApiChange change) if (srcType == tgtType) { change.Append (tgtType); } else { - change.AppendModified (srcType, tgtType); + change.AppendTypeModified (srcType, tgtType); } change.Append (" "); } @@ -150,7 +150,7 @@ void RenderIndexers (List srcIndexers, List tgtIndexers, Api if (srcType == tgtType) { change.Append (tgtType); } else { - change.AppendModified (srcType, tgtType); + change.AppendTypeModified (srcType, tgtType); } change.Append (" "); diff --git a/tools/api-tools/mono-api-info/mono-api-info.cs b/tools/api-tools/mono-api-info/mono-api-info.cs index b0c8a4d9d542..1b22201ef597 100644 --- a/tools/api-tools/mono-api-info/mono-api-info.cs +++ b/tools/api-tools/mono-api-info/mono-api-info.cs @@ -1014,7 +1014,9 @@ protected override void AddExtraAttributes (MemberReference memberDefinition) base.AddExtraAttributes (memberDefinition); FieldDefinition field = (FieldDefinition) memberDefinition; - AddAttribute ("fieldtype", Utils.CleanupTypeName (field.FieldType)); + var fieldTypeName = Utils.CleanupTypeName (field.FieldType); + fieldTypeName = NullabilityHelper.AppendNullabilityToTypeName (fieldTypeName, field.FieldType, field, field.DeclaringType); + AddAttribute ("fieldtype", fieldTypeName); if (field.IsLiteral) { object value = field.Constant;//object value = field.GetValue (null); @@ -1083,7 +1085,9 @@ protected override void AddExtraAttributes (MemberReference memberDefinition) base.AddExtraAttributes (memberDefinition); PropertyDefinition prop = (PropertyDefinition) memberDefinition; - AddAttribute ("ptype", Utils.CleanupTypeName (prop.PropertyType)); + var ptypeName = Utils.CleanupTypeName (prop.PropertyType); + ptypeName = NullabilityHelper.AppendNullabilityToTypeName (ptypeName, prop.PropertyType, prop, prop.DeclaringType); + AddAttribute ("ptype", ptypeName); bool haveParameters; MethodDefinition []? methods = GetMethods ((PropertyDefinition) memberDefinition, out haveParameters); @@ -1149,7 +1153,9 @@ protected override void AddExtraAttributes (MemberReference memberDefinition) base.AddExtraAttributes (memberDefinition); EventDefinition evt = (EventDefinition) memberDefinition; - AddAttribute ("eventtype", Utils.CleanupTypeName (evt.EventType)); + var evtTypeName = Utils.CleanupTypeName (evt.EventType); + evtTypeName = NullabilityHelper.AppendNullabilityToTypeName (evtTypeName, evt.EventType, evt, evt.DeclaringType); + AddAttribute ("eventtype", evtTypeName); } public override string ParentTag { @@ -1217,8 +1223,10 @@ protected override void AddExtraAttributes (MemberReference memberDefinition) AddAttribute ("is-override", "true"); } string rettype = Utils.CleanupTypeName (mbase.MethodReturnType.ReturnType); - if (rettype != "System.Void" || !mbase.IsConstructor) + if (rettype != "System.Void" || !mbase.IsConstructor) { + rettype = NullabilityHelper.AppendNullabilityToTypeName (rettype, mbase.MethodReturnType.ReturnType, mbase.MethodReturnType, mbase); AddAttribute ("returntype", (rettype)); + } // // if (mbase.MethodReturnType.HasCustomAttributes) // AttributeData.OutputAttributes (writer, mbase.MethodReturnType); @@ -1302,7 +1310,7 @@ public override void DoOutput () pt = brt.ElementType; } - AddAttribute ("type", Utils.CleanupTypeName (pt)); + AddAttribute ("type", NullabilityHelper.AppendNullabilityToTypeName (Utils.CleanupTypeName (pt), pt, parameter, parameter.Method as ICustomAttributeProvider)); if (parameter.IsOptional) { AddAttribute ("optional", "true"); @@ -1456,6 +1464,130 @@ public static string GetSignature (IList infos) } + static class NullabilityHelper { + const string NullableAttributeName = "System.Runtime.CompilerServices.NullableAttribute"; + const string NullableContextAttributeName = "System.Runtime.CompilerServices.NullableContextAttribute"; + + // Returns the nullability flag for the top-level type: + // 0 = oblivious, 1 = not-null, 2 = nullable + public static byte GetTopLevelNullability (ICustomAttributeProvider provider, ICustomAttributeProvider? context) + { + // Check for NullableAttribute directly on the member/parameter/return type + var flag = GetNullableFlagFromProvider (provider); + if (flag.HasValue) + return flag.Value; + + // Fall back to NullableContextAttribute on the containing method/type + if (context is not null) { + var contextFlag = GetNullableContextFlag (context); + if (contextFlag.HasValue) + return contextFlag.Value; + } + + return 0; // oblivious + } + + // Gets the NullableContextAttribute flag from a method or type + public static byte? GetNullableContextFlag (ICustomAttributeProvider provider) + { + if (provider is null) + return null; + + if (!provider.HasCustomAttributes) + return GetNullableContextFromParent (provider); + + foreach (var attr in provider.CustomAttributes) { + if (attr.AttributeType.FullName != NullableContextAttributeName) + continue; + if (attr.ConstructorArguments.Count == 1 && attr.ConstructorArguments [0].Value is byte b) + return b; + } + + return GetNullableContextFromParent (provider); + } + + static byte? GetNullableContextFromParent (ICustomAttributeProvider? provider) + { + if (provider is MethodDefinition method) + return GetNullableContextFlag (method.DeclaringType); + if (provider is PropertyDefinition prop) + return GetNullableContextFlag (prop.DeclaringType); + if (provider is FieldDefinition field) + return GetNullableContextFlag (field.DeclaringType); + if (provider is EventDefinition evt) + return GetNullableContextFlag (evt.DeclaringType); + if (provider is TypeDefinition type) { + if (type.DeclaringType is not null) + return GetNullableContextFlag (type.DeclaringType); + // Fall back to module-level NullableContextAttribute + return GetNullableContextFlagFromAttributes (type.Module); + } + return null; + } + + static byte? GetNullableContextFlagFromAttributes (ICustomAttributeProvider? provider) + { + if (provider is null || !provider.HasCustomAttributes) + return null; + + foreach (var attr in provider.CustomAttributes) { + if (attr.AttributeType.FullName != NullableContextAttributeName) + continue; + if (attr.ConstructorArguments.Count == 1 && attr.ConstructorArguments [0].Value is byte b) + return b; + } + return null; + } + + static byte? GetNullableFlagFromProvider (ICustomAttributeProvider provider) + { + if (!provider.HasCustomAttributes) + return null; + + foreach (var attr in provider.CustomAttributes) { + if (attr.AttributeType.FullName != NullableAttributeName) + continue; + if (attr.ConstructorArguments.Count != 1) + continue; + + var arg = attr.ConstructorArguments [0]; + if (arg.Value is byte b) + return b; + if (arg.Value is CustomAttributeArgument [] arr && arr.Length > 0 && arr [0].Value is byte b2) + return b2; + } + + return null; + } + + public static bool IsNullableReferenceType (TypeReference type, ICustomAttributeProvider provider, ICustomAttributeProvider? context) + { + if (type is null) + return false; + + // Value types use Nullable for nullability, not annotations + if (type.IsValueType) + return false; + + // ByReference types (ref/out parameters): check the element type + if (type.IsByReference) { + var elementType = ((ByReferenceType) type).ElementType; + if (elementType.IsValueType) + return false; + } + + var flag = GetTopLevelNullability (provider, context); + return flag == 2; + } + + public static string AppendNullabilityToTypeName (string typeName, TypeReference type, ICustomAttributeProvider provider, ICustomAttributeProvider? context) + { + if (IsNullableReferenceType (type, provider, context)) + return typeName + "?"; + return typeName; + } + } + class TypeReferenceComparer : IComparer { public static TypeReferenceComparer Default = new TypeReferenceComparer (); From b53046a688dfc299407b9bcc607fc55188de0842 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 06:43:18 +0000 Subject: [PATCH 66/97] [.github] Bump .github/workflows/inter-branch-merge-base.yml from b36a3594d11b8d56bf7d3dbce919e0688715d5ec to b6ed8ef7f3251d0b67ac2428c06253d0ba328e97 (#25639) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/inter-branch-merge-flow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/inter-branch-merge-flow.yml b/.github/workflows/inter-branch-merge-flow.yml index 7c95ddd17a32..b456685eebb4 100644 --- a/.github/workflows/inter-branch-merge-flow.yml +++ b/.github/workflows/inter-branch-merge-flow.yml @@ -30,4 +30,4 @@ permissions: jobs: Merge: - uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@b36a3594d11b8d56bf7d3dbce919e0688715d5ec # main + uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@b6ed8ef7f3251d0b67ac2428c06253d0ba328e97 # main From 587323252d7ad75bafbeccecf340898621dc797a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 08:46:55 +0200 Subject: [PATCH 67/97] [github] Bump actions/setup-dotnet from 5.2.0 to 5.3.0 (#25638) Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.2.0 to 5.3.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 4fb4d4a234df..e1e32292ad03 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@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 with: global-json-file: ./global.json @@ -67,7 +67,7 @@ jobs: fetch-depth: 1 - name: 'Install .NET' - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 with: global-json-file: ./global.json diff --git a/.github/workflows/maestro-changelog.yml b/.github/workflows/maestro-changelog.yml index e390cd2d872c..ab0ebf924389 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@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 + - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 with: dotnet-version: '9' From 90011e1495fb12a9b960fc1d22b8bf258ac0174a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 08:55:21 +0200 Subject: [PATCH 68/97] [tools] Remove most static state from the tools. (#25619) The remaining static state is mostly caching of environment variables (which won't change during a process' lifetime). This is necessary, because soon we'll include some of this code in MSBuild tasks, which shouldn't have static state. --- .../ErrorHelper.msbuild.cs | 9 +- src/bgen/BindingTouch.cs | 1 + tests/common/ErrorHelper.tests.cs | 41 ++- tools/common/Application.cs | 61 +++- tools/common/Driver.cs | 332 +----------------- tools/common/ErrorHelper.tools.cs | 93 ++--- tools/common/FileCopier.cs | 10 - tools/common/Frameworks.cs | 2 +- tools/common/IToolLog.cs | 5 + tools/common/Optimizations.cs | 4 +- tools/common/StaticRegistrar.cs | 2 +- tools/common/StringUtils.cs | 6 +- tools/common/cache.cs | 16 +- tools/common/error.cs | 23 +- .../dotnet-linker/BackingFieldDelayHandler.cs | 8 +- tools/dotnet-linker/LinkerConfiguration.cs | 36 +- tools/dotnet-linker/SetupStep.cs | 1 - .../Steps/ConfigurationAwareStep.cs | 2 +- .../Steps/TrimmableRegistrarStep.cs | 2 +- tools/linker/CoreTypeMapStep.cs | 2 +- tools/mtouch/AssemblyResolver.cs | 2 +- 21 files changed, 196 insertions(+), 462 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs b/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs index 9827fc45e2bd..8bda5c48e690 100644 --- a/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs +++ b/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs @@ -10,10 +10,9 @@ namespace Xamarin.Bundler { public static partial class ErrorHelper { public static ApplePlatform Platform; - internal static string Prefix { - get { - return Xamarin.MacDev.Tasks.LoggingExtensions.ErrorPrefix; - } + internal static string GetPrefix (IToolLog? log) + { + return Xamarin.MacDev.Tasks.LoggingExtensions.ErrorPrefix; } public enum WarningLevel { @@ -24,7 +23,7 @@ public enum WarningLevel { static Dictionary? warning_levels; - public static WarningLevel GetWarningLevel (int code) + public static WarningLevel GetWarningLevel (IToolLog log, int code) { WarningLevel level; diff --git a/src/bgen/BindingTouch.cs b/src/bgen/BindingTouch.cs index 72aa883e0985..7b9e2d32dadb 100644 --- a/src/bgen/BindingTouch.cs +++ b/src/bgen/BindingTouch.cs @@ -46,6 +46,7 @@ public class BindingTouch : IDisposable, IToolLog { public static ApplePlatform [] AllPlatforms = new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }; public static PlatformName [] AllPlatformNames = new PlatformName [] { PlatformName.iOS, PlatformName.MacOSX, PlatformName.TvOS, PlatformName.MacCatalyst }; public PlatformName CurrentPlatform; + public ApplePlatform Platform { get => CurrentPlatform.AsApplePlatform (); } public bool BindThirdPartyLibrary = true; public string? outfile; diff --git a/tests/common/ErrorHelper.tests.cs b/tests/common/ErrorHelper.tests.cs index eb2c7d952b10..495ed26053ff 100644 --- a/tests/common/ErrorHelper.tests.cs +++ b/tests/common/ErrorHelper.tests.cs @@ -3,6 +3,7 @@ #nullable enable using System.Linq; +using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; @@ -13,10 +14,9 @@ namespace Xamarin.Bundler { public static partial class ErrorHelper { public static ApplePlatform Platform; - internal static string Prefix { - get { - return "TESTS"; - } + internal static string GetPrefix (IToolLog? log) + { + return "TESTS"; } public enum WarningLevel { @@ -25,33 +25,32 @@ public enum WarningLevel { Disable = 1, } - static Dictionary? warning_levels; + static ConditionalWeakTable> warning_levels = new (); - public static WarningLevel GetWarningLevel (int code) + public static WarningLevel GetWarningLevel (IToolLog log, int code) { - WarningLevel level; - - if (warning_levels is null) - return WarningLevel.Warning; + if (warning_levels.TryGetValue (log, out var log_warning_levels)) { + // code -1: all codes + if (log_warning_levels.TryGetValue (-1, out var level)) + return level; - // code -1: all codes - if (warning_levels.TryGetValue (-1, out level)) - return level; - - if (warning_levels.TryGetValue (code, out level)) - return level; + if (log_warning_levels.TryGetValue (code, out level)) + return level; + } return WarningLevel.Warning; } - public static void SetWarningLevel (WarningLevel level, int? code = null /* if null, apply to all warnings */) + public static void SetWarningLevel (IToolLog log, WarningLevel level, int? code = null /* if null, apply to all warnings */) { - if (warning_levels is null) - warning_levels = new Dictionary (); + if (!warning_levels.TryGetValue (log, out var log_warning_levels)) { + log_warning_levels = new Dictionary (); + warning_levels.Add (log, log_warning_levels); + } if (code.HasValue) { - warning_levels [code.Value] = level; + log_warning_levels [code.Value] = level; } else { - warning_levels [-1] = level; // code -1: all codes. + log_warning_levels [-1] = level; // code -1: all codes. } } } diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 6aad8900c70c..5405b8eb0244 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -102,7 +102,8 @@ public partial class Application : IToolLog { public HashSet WeakFrameworks = new HashSet (); public bool IsExtension; - public ApplePlatform Platform { get { return Driver.TargetFramework.Platform; } } + public TargetFramework TargetFramework { get; set; } + public ApplePlatform Platform { get { return TargetFramework.Platform; } } public List MonoLibraries = new List (); public List InterpretedAssemblies = new List (); @@ -266,7 +267,7 @@ public bool PackageManagedDebugSymbols { public Version GetMacCatalystiOSVersion (Version macOSVersion) { #if LEGACY_TOOLS - if (macOSVersion.Major >= 26 && Driver.SdkRoot is null) { + if (macOSVersion.Major >= 26 && SdkRoot is null) { // this shouldn't happen for normal builds, nor for customers, so just show an internal 99 warning. ErrorHelper.Warning (this, 99, Errors.MX0099, $"No Xcode configured, assuming the macOS version {macOSVersion} is identical to the Mac Catalyst/iOS version."); return macOSVersion; @@ -287,6 +288,7 @@ public Application (LinkerConfiguration configuration) #endif this.StaticRegistrar = new StaticRegistrar (this); this.Resolver = new PlatformResolver (this); + SetDefaultHiddenWarnings (); } #if !LEGACY_TOOLS @@ -1032,7 +1034,7 @@ public void GetAotArguments (string filename, Abi abi, string outputDir, string if (!string.IsNullOrEmpty (llvm_path)) { aotArguments.Add ($"llvm-path={llvm_path}"); } else { - aotArguments.Add ($"llvm-path={Driver.GetFrameworkCurrentDirectory (app)}/LLVM/bin/"); + aotArguments.Add ($"llvm-path={app.FrameworkCurrentDirectory}/LLVM/bin/"); } } @@ -1198,17 +1200,12 @@ public bool VerifyDynamicFramework (string framework_path) } #endif // !LEGACY_TOOLS - static Application () - { - SetDefaultHiddenWarnings (); - } - - public static void SetDefaultHiddenWarnings () + public void SetDefaultHiddenWarnings () { // People don't like these warnings (#20670), and they also complicate our tests, so ignore them. - ErrorHelper.ParseWarningLevel (ErrorHelper.WarningLevel.Disable, "4178"); // The class '{0}' will not be registered because the {1} framework has been removed from the {2} SDK. - ErrorHelper.ParseWarningLevel (ErrorHelper.WarningLevel.Disable, "4189"); // The class '{0}' will not be registered because it has been removed from the {1} SDK. - ErrorHelper.ParseWarningLevel (ErrorHelper.WarningLevel.Disable, "4190"); // The class '{0}' will not be registered because the {1} framework has been deprecated from the {2} SDK. + ErrorHelper.ParseWarningLevel (this, ErrorHelper.WarningLevel.Disable, "4178"); // The class '{0}' will not be registered because the {1} framework has been removed from the {2} SDK. + ErrorHelper.ParseWarningLevel (this, ErrorHelper.WarningLevel.Disable, "4189"); // The class '{0}' will not be registered because it has been removed from the {1} SDK. + ErrorHelper.ParseWarningLevel (this, ErrorHelper.WarningLevel.Disable, "4190"); // The class '{0}' will not be registered because the {1} framework has been deprecated from the {2} SDK. } public void Log (string message) @@ -1236,5 +1233,45 @@ public int Verbosity { get => verbosity; set => verbosity = value; } + + public string? SdkRoot { get; set; } + public string? DeveloperDirectory { get; set; } + + string? framework_dir; + public string FrameworkCurrentDirectory { + get { + if (framework_dir is null) + throw new InvalidOperationException ($"Teh current framework directory hasn't been set."); + return framework_dir; + } + set { + framework_dir = value; + } + } + + /// + /// This returns the /Applications/Xcode*.app/Contents/Developer/Platforms directory + /// + public string PlatformsDirectory { + get { + if (DeveloperDirectory is null) + throw new InvalidOperationException ("DeveloperDirectory is not set"); + return Path.Combine (DeveloperDirectory, "Platforms"); + } + } + + Version? xcode_version; + public Version XcodeVersion { + get { + if (xcode_version is null) + throw ErrorHelper.CreateError (99, Errors.MX0099, "The Xcode version has not been configured. Pass --xcode-version or configure an Xcode installation."); + return xcode_version; + } + set { + xcode_version = value; + } + } + + public string? XcodeProductVersion { get; set; } } } diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index c04f81cd0221..fe912e00e4a8 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -7,9 +7,8 @@ */ using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; +using System.Globalization; using System.Linq; using System.Text; @@ -20,8 +19,6 @@ namespace Xamarin.Bundler { public partial class Driver { - public static bool Force { get; set; } - #if LEGACY_TOOLS public static int Main (string [] args) { @@ -31,8 +28,6 @@ public static int Main (string [] args) return Main2 (args); } catch (Exception e) { ErrorHelper.Show (ConsoleLog.Instance, e); - } finally { - Watch (ConsoleLog.Instance, "Total time", 0); } return 0; } @@ -43,7 +38,7 @@ static void ParseOptions (Application app, Mono.Options.OptionSet options, strin options.Add ("v|verbose", "Specify how verbose the output should be. This can be passed multiple times to increase the verbosity.", v => app.Verbosity++); options.Add ("q|quiet", "Specify how quiet the output should be. This can be passed multiple times to increase the silence.", v => app.Verbosity--); options.Add ("reference=", "Add an assembly to be processed.", v => app.References.Add (v)); - options.Add ("sdkroot=", "Specify the location of Apple SDKs, default to 'xcode-select' value.", v => sdk_root = v); + options.Add ("sdkroot=", "Specify the location of Apple SDKs, default to 'xcode-select' value.", v => app.SdkRoot = v); options.Add ("sdk=", "Specifies the SDK version to compile against (version, for example \"10.9\"). For Mac Catalyst, this is the macOS version of the SDK.", v => { try { app.SdkVersion = StringUtils.ParseVersion (v); @@ -53,7 +48,7 @@ static void ParseOptions (Application app, Mono.Options.OptionSet options, strin } }); options.Add ("target-framework=", "Specify target framework to use. Currently supported: '" + string.Join ("', '", TargetFramework.ValidFrameworks.Select ((v) => v.ToString ())) + "'.", v => { - targetFramework = TargetFramework.Parse (v); + app.TargetFramework = TargetFramework.Parse (v); }); options.Add ("abi=", "Comma-separated list of ABIs to target.", v => app.ParseAbi (v)); options.Add ("runregistrar:", "Runs the registrar on the input assembly and outputs a corresponding native library.", @@ -72,7 +67,7 @@ static void ParseOptions (Application app, Mono.Options.OptionSet options, strin options.Add ("xcode-version=", "The Xcode version we're building with", v => { if (!Version.TryParse (v, out var xcodeVersion)) throw ErrorHelper.CreateError (26, Errors.MX0026, $"xcode-version:{v}", "Expected a valid version string."); - Driver.XcodeVersion = xcodeVersion; + app.XcodeVersion = xcodeVersion; }); try { @@ -97,13 +92,6 @@ public static int GetDefaultVerbosity () return v; } - static TargetFramework targetFramework; - - public static TargetFramework TargetFramework { - get { return targetFramework; } - set { targetFramework = value; } - } - static void FileMove (string source, string target) { File.Delete (target); @@ -186,25 +174,7 @@ internal static string GetFullPath () return System.Reflection.Assembly.GetExecutingAssembly ().Location; } - static string? xcode_product_version; - public static string? XcodeProductVersion { - get { - return xcode_product_version; - } - } - - static Version? xcode_version; - public static Version XcodeVersion { - get { - if (xcode_version is null) - throw ErrorHelper.CreateError (99, Errors.MX0099, "The Xcode version has not been configured. Pass --xcode-version or configure an Xcode installation."); - return xcode_version; - } - set { - xcode_version = value; - } - } - +#if LEGACY_TOOLS static void SetCurrentLanguage (IToolLog log) { // There's no way to change the current culture from the command-line @@ -236,6 +206,7 @@ static void SetCurrentLanguage (IToolLog log) ErrorHelper.Warning (log, 124, e, Errors.MT0124, lang, lang_variable, e.Message); } } +#endif public static void Touch (IToolLog log, IEnumerable filenames, DateTime? timestamp = null) { @@ -261,28 +232,7 @@ public static void Touch (IToolLog log, params string [] filenames) Touch (log, (IEnumerable) filenames); } - static int watch_level; - static Stopwatch? watch; - - public static int WatchLevel { - get { return watch_level; } - set { - watch_level = value; - if ((watch_level > 0) && (watch is null)) { - watch = new Stopwatch (); - watch.Start (); - } - } - } - - public static void Watch (IToolLog log, string msg, int level) - { - if ((watch is null) || (level > WatchLevel)) - return; - log.Log ($"{new string ('!', level)}Timestamp {msg}: {watch.ElapsedMilliseconds} ms"); - } - - internal static PDictionary? FromPList (string name) + internal static PDictionary FromPList (string name) { if (!File.Exists (name)) throw ErrorHelper.CreateError (24, Errors.MT0024, name); @@ -301,48 +251,6 @@ public static void Watch (IToolLog log, string msg, int level) return output.ToString ().Trim (); } - static string? sdk_root; - static string? developer_directory = null; - - public static string? SdkRoot { - get => sdk_root; - set => sdk_root = value; - } - - public static string? DeveloperDirectory { - get { - return developer_directory; - } - } - - // This returns the /Applications/Xcode*.app/Contents/Developer/Platforms directory - public static string PlatformsDirectory { - get { - if (DeveloperDirectory is null) - throw new InvalidOperationException ("DeveloperDirectory is not set"); - return Path.Combine (DeveloperDirectory, "Platforms"); - } - } - - // This returns the /Applications/Xcode*.app/Contents/Developer/Platforms/*.platform directory - public static string GetPlatformDirectory (Application app) - { - return Path.Combine (PlatformsDirectory, GetPlatform (app) + ".platform"); - } - - static string? framework_dir; - public static string GetFrameworkCurrentDirectory (Application app) - { - if (framework_dir is null) - throw new InvalidOperationException ($"Teh current framework directory hasn't been set."); - return framework_dir; - } - - public static void SetFrameworkCurrentDirectory (string value) - { - framework_dir = value; - } - // This returns the platform to use in /Applications/Xcode*.app/Contents/Developer/Platforms/*.platform public static string GetPlatform (Application app) { @@ -364,7 +272,7 @@ public static string GetFrameworkDirectory (Application app) { var platform = GetPlatform (app); var sdkVersion = app.NativeSdkVersion?.ToString () ?? ""; - return Path.Combine (PlatformsDirectory, platform + ".platform", "Developer", "SDKs", platform + sdkVersion + ".sdk"); + return Path.Combine (app.PlatformsDirectory, platform + ".platform", "Developer", "SDKs", platform + sdkVersion + ".sdk"); } public static string GetProductAssembly (Application app) @@ -385,6 +293,8 @@ public static string GetProductAssembly (Application app) public static void ValidateXcode (Application app, bool accept_any_xcode_version, bool warn_if_not_found) { + var sdk_root = app.SdkRoot; + if (sdk_root is null) { sdk_root = FindSystemXcode (app); if (sdk_root is null) { @@ -419,28 +329,28 @@ public static void ValidateXcode (Application app, bool accept_any_xcode_version // Check what kind of path we got if (File.Exists (Path.Combine (sdk_root, "Contents", "MacOS", "Xcode"))) { // path to the Xcode.app - developer_directory = Path.Combine (sdk_root, "Contents", "Developer"); + app.DeveloperDirectory = Path.Combine (sdk_root, "Contents", "Developer"); } else if (File.Exists (Path.Combine (sdk_root, "..", "MacOS", "Xcode"))) { // path to Contents/Developer - developer_directory = Path.GetFullPath (Path.Combine (sdk_root, "..", "..", "Contents", "Developer")); + app.DeveloperDirectory = Path.GetFullPath (Path.Combine (sdk_root, "..", "..", "Contents", "Developer")); } else { throw ErrorHelper.CreateError (57, Errors.MT0057, sdk_root); } - var plist_path = Path.Combine (Path.GetDirectoryName (DeveloperDirectory)!, "version.plist"); + var plist_path = Path.Combine (Path.GetDirectoryName (app.DeveloperDirectory)!, "version.plist"); if (File.Exists (plist_path)) { var plist = FromPList (plist_path); - var version = plist?.GetString ("CFBundleShortVersionString"); + var version = plist.GetString ("CFBundleShortVersionString"); if (version is null) - throw ErrorHelper.CreateError (58, Errors.MT0058, Path.GetDirectoryName (Path.GetDirectoryName (DeveloperDirectory)), plist_path); - xcode_version = new Version (version); - xcode_product_version = plist!.GetString ("ProductBuildVersion"); + throw ErrorHelper.CreateError (58, Errors.MT0058, Path.GetDirectoryName (Path.GetDirectoryName (app.DeveloperDirectory)), plist_path); + app.XcodeVersion = new Version (version); + app.XcodeProductVersion = plist.GetString ("ProductBuildVersion"); } else { - throw ErrorHelper.CreateError (58, Errors.MT0058, Path.GetDirectoryName (Path.GetDirectoryName (DeveloperDirectory)), plist_path); + throw ErrorHelper.CreateError (58, Errors.MT0058, Path.GetDirectoryName (Path.GetDirectoryName (app.DeveloperDirectory)), plist_path); } - app.Log (1, "Using Xcode {0} ({2}) found in {1}", XcodeVersion, sdk_root, XcodeProductVersion); + app.Log (1, "Using Xcode {0} ({2}) found in {1}", app.XcodeVersion, sdk_root, app.XcodeProductVersion); } internal static bool TryParseBool (string value, out bool result) @@ -476,210 +386,6 @@ internal static bool ParseBool (string value, string name, bool show_error = tru return result; } -#if !LEGACY_TOOLS - static readonly Dictionary tools = new Dictionary (); - static string FindTool (Application app, string tool) - { - lock (tools) { - if (tools.TryGetValue (tool, out var path) && path is not null) - return path; - } - - var foundPath = LocateTool (app, tool); - static string? LocateTool (Application app, string tool) - { - if (XcrunFind (app, tool, out var path)) - return path; - - if (DeveloperDirectory is null) - return null; - - // either /Developer (Xcode 4.2 and earlier), /Applications/Xcode.app/Contents/Developer (Xcode 4.3) or user override - path = Path.Combine (DeveloperDirectory, "usr", "bin", tool); - if (File.Exists (path)) - return path; - - // Xcode 4.3 (without command-line tools) also has a copy of 'strip' - path = Path.Combine (DeveloperDirectory, "Toolchains", "XcodeDefault.xctoolchain", "usr", "bin", tool); - if (File.Exists (path)) - return path; - - // Xcode "Command-Line Tools" install a copy in /usr/bin (and it can be there afterward) - path = Path.Combine ("/usr", "bin", tool); - if (File.Exists (path)) - return path; - - return null; - } - - // We can end up finding the same tool multiple times. - // That's not a problem. - lock (tools) - tools [tool] = foundPath; - - if (foundPath is null) - throw ErrorHelper.CreateError (5307, Errors.MX5307 /* Missing '{0}' tool. Please install Xcode 'Command-Line Tools' component */, tool); - - return foundPath; - } - - static bool XcrunFind (Application app, string tool, [NotNullWhen (true)] out string? path) - { - return XcrunFind (app, ApplePlatform.None, false, tool, out path); - } - - static bool XcrunFind (Application app, ApplePlatform platform, bool is_simulator, string tool, [NotNullWhen (true)] out string? path) - { - var env = new Dictionary (); - // Unset XCODE_DEVELOPER_DIR_PATH. See https://github.com/dotnet/macios/issues/3931. - env.Add ("XCODE_DEVELOPER_DIR_PATH", null); - // Set DEVELOPER_DIR if we have it - if (!string.IsNullOrEmpty (DeveloperDirectory)) - env.Add ("DEVELOPER_DIR", DeveloperDirectory); - - path = null; - - var args = new List (); - if (platform != ApplePlatform.None) { - args.Add ("-sdk"); - switch (platform) { - case ApplePlatform.iOS: - args.Add (is_simulator ? "iphonesimulator" : "iphoneos"); - break; - case ApplePlatform.MacOSX: - args.Add ("macosx"); - break; - case ApplePlatform.TVOS: - args.Add (is_simulator ? "appletvsimulator" : "appletvos"); - break; - default: - throw ErrorHelper.CreateError (71, Errors.MX0071 /* Unknown platform: {0}. This usually indicates a bug in {1}; please file a bug report at https://github.com/dotnet/macios/issues/new with a test case. */, platform.ToString (), app.ProductName); - } - } - args.Add ("-f"); - args.Add (tool); - - var stdout = new StringBuilder (); - var stderr = new StringBuilder (); - var both = new StringBuilder (); - // xcrun can write unrelated stuff to stderr even if it succeeds, so we need to separate stdout and stderr. - // We also want to print out what happened if something went wrong, and in that case we don't want stdout - // and stderr captured separately, because related lines could end up printed far from eachother in time, - // and that's confusing. So capture stdout and stderr by themselves, and also capture both together. - int ret = RunCommand (app, "xcrun", args, env, - (v) => { - lock (both) { - both.AppendLine (v); - stdout.AppendLine (v); - } - }, - (v) => { - lock (both) { - both.AppendLine (v); - stderr.AppendLine (v); - } - }); - - if (ret == 0) { - path = stdout.ToString ().Trim (); - if (!File.Exists (path)) { - ErrorHelper.Warning (app, 5315, Errors.MX5315 /* The tool xcrun failed to return a valid result (the file {0} does not exist). Check build log for details. */, tool, path); - return false; - } - } else { - app.Log (1, "Failed to locate the developer tool '{0}', 'xcrun {1}' returned with the exit code {2}:\n{3}", tool, string.Join (" ", args), ret, both.ToString ()); - } - - return ret == 0; - } - - public static void RunXcodeTool (Application app, string tool, params string [] arguments) - { - RunXcodeTool (app, tool, (IList) arguments); - } - - public static void RunXcodeTool (Application app, string tool, IList arguments) - { - var executable = FindTool (app, tool); - var rv = RunCommand (app, executable, arguments); - if (rv != 0) - throw ErrorHelper.CreateError (5309, Errors.MX5309 /* Failed to execute the tool '{0}', it failed with an error code '{1}'. Please check the build log for details. */, tool, rv); - } - - public static void RunClang (Application app, IList arguments) - { - RunXcodeTool (app, "clang", arguments); - } - - public static void RunInstallNameTool (Application app, IList arguments) - { - RunXcodeTool (app, "install_name_tool", arguments); - } - - public static void RunBitcodeStrip (Application app, IList arguments) - { - RunXcodeTool (app, "bitcode_strip", arguments); - } - - public static void RunLipo (Application app, string output, IEnumerable inputs) - { - var sb = new List (); - sb.AddRange (inputs); - sb.Add ("-create"); - sb.Add ("-output"); - sb.Add (output); - RunLipo (app, sb); - } - - public static void RunLipoAndCreateDsym (Application app, string output, IEnumerable inputs) - { - RunLipo (app, output, inputs); - - var dsymFolders = inputs.Select (input => input + ".dSYM").Where (Directory.Exists).ToArray (); - if (dsymFolders.Length > 1) { - // Lipo the dSYMs into one big happy dSYM - var dsymLibsDir = dsymFolders.Select (dsym => Path.Combine (dsym, "Contents", "Resources", "DWARF")).ToArray (); - var allLibs = dsymLibsDir.Where (Directory.Exists).SelectMany (dir => Directory.EnumerateFiles (dir)).Select (dir => Path.GetFileName (dir)).Distinct ().ToArray (); - - foreach (var lib in allLibs) { - var outputLib = Path.Combine (dsymLibsDir [0], lib); - var allDsymInputs = dsymLibsDir.Select (libDir => Path.Combine (libDir, lib)).Where (File.Exists).ToArray (); - Driver.RunLipo (app, outputLib, allDsymInputs); - } - } - - // Move the dSYM next to its executable - if (dsymFolders.Length > 0) { - var outputDsymDir = output + ".dSYM"; - if (Directory.Exists (outputDsymDir)) - Directory.Delete (outputDsymDir, true); - Directory.Move (dsymFolders [0], outputDsymDir); - RunCommand (app, "/usr/bin/mdimport", outputDsymDir); - } - } - - public static void RunLipo (Application app, IList options) - { - RunXcodeTool (app, "lipo", options); - } - - public static void CreateDsym (Application app, string output_dir, string appname, string dsym_dir) - { - RunDsymUtil (app, Path.Combine (output_dir, appname), "-num-threads", "4", "-z", "-o", dsym_dir); - RunCommand (app, "/usr/bin/mdimport", dsym_dir); - } - - public static void RunDsymUtil (Application app, params string [] options) - { - RunXcodeTool (app, "dsymutil", options); - } - - public static void RunStrip (Application app, IList options) - { - RunXcodeTool (app, "strip", options); - } -#endif // !LEGACY_TOOLS - public static string CorlibName { get { return "System.Private.CoreLib"; diff --git a/tools/common/ErrorHelper.tools.cs b/tools/common/ErrorHelper.tools.cs index 275f0d04a894..e930f95731f6 100644 --- a/tools/common/ErrorHelper.tools.cs +++ b/tools/common/ErrorHelper.tools.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; - +using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; @@ -13,22 +13,20 @@ namespace Xamarin.Bundler { public static partial class ErrorHelper { - public static ApplePlatform Platform; - - internal static string Prefix { - get { - switch (Platform) { - case ApplePlatform.iOS: - case ApplePlatform.TVOS: - case ApplePlatform.MacCatalyst: - case ApplePlatform.None: // Return "MT" by default instead of throwing an exception, because any exception here will most likely hide whatever other error we're trying to show. - return "MT"; - case ApplePlatform.MacOSX: - return "MM"; - default: - // Do not use the ErrorHandler machinery, because it will probably end up recursing and eventually throwing a StackOverflowException. - throw new InvalidOperationException ($"Unknown platform: {Platform}"); - } + internal static string GetPrefix (IToolLog? log) + { + switch (log?.Platform) { + case ApplePlatform.iOS: + case ApplePlatform.TVOS: + case ApplePlatform.MacCatalyst: + case ApplePlatform.None: // Return "MT" by default instead of throwing an exception, because any exception here will most likely hide whatever other error we're trying to show. + case null: + return "MT"; + case ApplePlatform.MacOSX: + return "MM"; + default: + // Do not use the ErrorHandler machinery, because it will probably end up recursing and eventually throwing a StackOverflowException. + throw new InvalidOperationException ($"Unknown platform: {log.Platform}"); } } @@ -38,48 +36,42 @@ public enum WarningLevel { Disable = 1, } - static Dictionary? warning_levels; - -#pragma warning disable 649 - public static Func? IsExpectedException; - public static Action? ExitCallback; -#pragma warning restore 649 + static ConditionalWeakTable> warning_levels = new (); - public static WarningLevel GetWarningLevel (int code) + public static WarningLevel GetWarningLevel (IToolLog log, int code) { - WarningLevel level; + if (warning_levels.TryGetValue (log, out var log_warning_levels)) { + // code -1: all codes + if (log_warning_levels.TryGetValue (-1, out var level)) + return level; - if (warning_levels is null) - return WarningLevel.Warning; - - // code -1: all codes - if (warning_levels.TryGetValue (-1, out level)) - return level; - - if (warning_levels.TryGetValue (code, out level)) - return level; + if (log_warning_levels.TryGetValue (code, out level)) + return level; + } return WarningLevel.Warning; } - public static void SetWarningLevel (WarningLevel level, int? code = null /* if null, apply to all warnings */) + public static void SetWarningLevel (IToolLog log, WarningLevel level, int? code = null /* if null, apply to all warnings */) { - if (warning_levels is null) - warning_levels = new Dictionary (); + if (!warning_levels.TryGetValue (log, out var log_warning_levels)) { + log_warning_levels = new Dictionary (); + warning_levels.Add (log, log_warning_levels); + } if (code.HasValue) { - warning_levels [code.Value] = level; + log_warning_levels [code.Value] = level; } else { - warning_levels [-1] = level; // code -1: all codes. + log_warning_levels [-1] = level; // code -1: all codes. } } - public static void ParseWarningLevel (WarningLevel level, string value) + public static void ParseWarningLevel (IToolLog log, WarningLevel level, string value) { if (string.IsNullOrEmpty (value)) { - SetWarningLevel (level); + SetWarningLevel (log, level); } else { foreach (var code in value.Split (new char [] { ',' }, StringSplitOptions.RemoveEmptyEntries)) - SetWarningLevel (level, int.Parse (code)); + SetWarningLevel (log, level, int.Parse (code)); } } @@ -254,7 +246,7 @@ public static ProductException Create (Application app, int code, bool error, Ex public static void Warning (IToolLog log, int code, string message, params object [] args) { - Show (log, new ProductException (code, false, message, args)); + Show (log, new ProductException (code, false, null, message, args)); } public static void Warning (IToolLog log, int code, Exception innerException, string message, params object [] args) @@ -269,7 +261,7 @@ public static void ThrowIfErrors (IToolLog log, IList exceptions) return; // Separate warnings from errors - var grouped = exceptions.GroupBy ((v) => (v as ProductException)?.Error == false); + var grouped = exceptions.GroupBy ((v) => (v as ProductException)?.IsError (log) == false); var warnings = grouped.SingleOrDefault ((v) => v.Key); if (warnings?.Any () == true) @@ -299,8 +291,6 @@ public static void Show (IToolLog log, Exception e) static void Exit (int exitCode) { - if (ExitCallback is not null) - ExitCallback (exitCode); Environment.Exit (exitCode); } @@ -310,9 +300,9 @@ static bool ShowInternal (IToolLog log, Exception e) bool error = true; if (mte is not null) { - error = mte.Error; + error = mte.IsError (log); - if (!error && GetWarningLevel (mte.Code) == WarningLevel.Disable) + if (!error && GetWarningLevel (log, mte.Code) == WarningLevel.Disable) return false; // This is an ignored warning. log.LogError (mte.ToString ()); @@ -321,14 +311,9 @@ static bool ShowInternal (IToolLog log, Exception e) if (log.Verbosity > 2 && !string.IsNullOrEmpty (e.StackTrace)) log.LogError (e.StackTrace); - } else if (IsExpectedException is null || !IsExpectedException (e)) { - log.LogError ("error " + Prefix + "0000: Unexpected error - Please file a bug report at https://github.com/dotnet/macios/issues/new"); - log.LogError (e.ToString ()); } else { + log.LogError ("error " + GetPrefix (log) + "0000: Unexpected error - Please file a bug report at https://github.com/dotnet/macios/issues/new"); log.LogError (e.ToString ()); - ShowInner (log, e); - if (log.Verbosity > 2 && !string.IsNullOrEmpty (e.StackTrace)) - log.LogError (e.StackTrace); } return error; diff --git a/tools/common/FileCopier.cs b/tools/common/FileCopier.cs index d10e93b59827..d26254c1ba17 100644 --- a/tools/common/FileCopier.cs +++ b/tools/common/FileCopier.cs @@ -241,11 +241,6 @@ static CopyFileResult CopyFileCallback (CopyFileWhat what, CopyFileStep stage, I // if it's later than the timestamp of the "target" file itself. public static bool IsUptodate (IToolLog log, string source, string target, bool check_contents = false, bool check_stamp = true) { -#if LEGACY_TOOLS || BUNDLER // msbuild does not have force - if (Driver.Force) - return false; -#endif - var tfi = new FileInfo (target); if (!tfi.Exists) { @@ -288,11 +283,6 @@ public static bool IsUptodate (IToolLog log, string source, string target, bool // if it's later than the timestamp of the "target" file itself. public static bool IsUptodate (IToolLog log, IEnumerable sources, IEnumerable targets, bool check_stamp = true) { -#if LEGACY_TOOLS || BUNDLER // msbuild does not have force - if (Driver.Force) - return false; -#endif - DateTime max_source = DateTime.MinValue; string? max_s = null; diff --git a/tools/common/Frameworks.cs b/tools/common/Frameworks.cs index 3970120f17fa..8e40602e764a 100644 --- a/tools/common/Frameworks.cs +++ b/tools/common/Frameworks.cs @@ -134,7 +134,7 @@ public void Add (string @namespace, string framework, Version version, Version? return null; } - static Version NotAvailableInSimulator = new Version (int.MaxValue, int.MaxValue); + static readonly Version NotAvailableInSimulator = new Version (int.MaxValue, int.MaxValue); static Frameworks? mac_frameworks; public static Frameworks MacFrameworks { diff --git a/tools/common/IToolLog.cs b/tools/common/IToolLog.cs index 1bc5a9c41d31..758cb136eaf4 100644 --- a/tools/common/IToolLog.cs +++ b/tools/common/IToolLog.cs @@ -1,7 +1,10 @@ +using Xamarin.Utils; + namespace Xamarin.Bundler; public interface IToolLog { int Verbosity { get; } + ApplePlatform Platform { get; } void Log (string message); void LogError (string message); // Log an error we raise ourselves (through an exception) @@ -45,6 +48,8 @@ public class ConsoleLog : IToolLog { public int Verbosity { get => verbosity; } + public ApplePlatform Platform => ApplePlatform.None; + public void Log (string message) { Console.WriteLine (message); diff --git a/tools/common/Optimizations.cs b/tools/common/Optimizations.cs index 992d36ee118a..d243a9b39415 100644 --- a/tools/common/Optimizations.cs +++ b/tools/common/Optimizations.cs @@ -9,7 +9,7 @@ namespace Xamarin.Bundler { public class Optimizations { - static string [] opt_names = + static readonly string [] opt_names = { "remove-uithread-checks", "dead-code-elimination", @@ -31,7 +31,7 @@ public class Optimizations { "redirect-class-handles", }; - static ApplePlatform [] [] valid_platforms = new ApplePlatform [] [] { + static readonly ApplePlatform [] [] valid_platforms = new ApplePlatform [] [] { /* Opt.RemoveUIThreadChecks */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.DeadCodeElimination */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, /* Opt.InlineIsDirectBinding */ new ApplePlatform [] { ApplePlatform.iOS, ApplePlatform.MacOSX, ApplePlatform.TVOS, ApplePlatform.MacCatalyst }, diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index be257833d956..f6b55ec0a015 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -2686,7 +2686,7 @@ List GetAllTypes (List exceptions) if (!string.IsNullOrEmpty (single_assembly) && single_assembly != @class.Type.Module.Assembly.Name.Name) continue; - if (Driver.XcodeVersion.Major >= 15) { + if (App.XcodeVersion.Major >= 15) { if (@class.Type.Is ("PassKit", "PKDisbursementAuthorizationControllerDelegate") || @class.Type.Is ("PassKit", "IPKDisbursementAuthorizationControllerDelegate")) { exceptions.Add (ErrorHelper.CreateWarning (4189, $"The class '{@class.Type.FullName}' will not be registered it has been removed from the {App.Platform} SDK.")); continue; diff --git a/tools/common/StringUtils.cs b/tools/common/StringUtils.cs index 01dadf374af7..d2470f08fdae 100644 --- a/tools/common/StringUtils.cs +++ b/tools/common/StringUtils.cs @@ -17,9 +17,9 @@ static StringUtils () shellQuoteChar = '\''; // !Windows } - static char shellQuoteChar; - static char [] mustQuoteCharacters = new char [] { ' ', '\'', ',', '$', '\\' }; - static char [] mustQuoteCharactersProcess = { ' ', '\\', '"', '\'' }; + static readonly char shellQuoteChar; + static readonly char [] mustQuoteCharacters = new char [] { ' ', '\'', ',', '$', '\\' }; + static readonly char [] mustQuoteCharactersProcess = { ' ', '\\', '"', '\'' }; [return: NotNullIfNotNull (nameof (array))] public static string []? Quote (params string [] array) diff --git a/tools/common/cache.cs b/tools/common/cache.cs index 1dcba0073ef9..1a7e607f1630 100644 --- a/tools/common/cache.cs +++ b/tools/common/cache.cs @@ -74,13 +74,8 @@ public void Clean (IToolLog log) Directory.CreateDirectory (location); } - public static bool CompareFiles (IToolLog log, string a, string b, bool ignore_cache = false) + public static bool CompareFiles (IToolLog log, string a, string b) { - if (Driver.Force && !ignore_cache) { - log.Log (6, "Files {0} and {1} are considered different because -f was passed to " + NAME + ".", a, b); - return false; - } - if (!File.Exists (b)) { log.Log (6, "Files {0} and {1} are considered different because the latter doesn't exist.", a, b); return false; @@ -90,20 +85,15 @@ public static bool CompareFiles (IToolLog log, string a, string b, bool ignore_c using (var bstream = new FileStream (b, FileMode.Open, FileAccess.Read, FileShare.Read)) { bool rv; log.Log (6, "Comparing files {0} and {1}...", a, b); - rv = CompareStreams (log, astream, bstream, ignore_cache); + rv = CompareStreams (log, astream, bstream); log.Log (6, " > {0}", rv ? "Identical" : "Different"); return rv; } } } - public unsafe static bool CompareStreams (IToolLog log, Stream astream, Stream bstream, bool ignore_cache = false) + public unsafe static bool CompareStreams (IToolLog log, Stream astream, Stream bstream) { - if (Driver.Force && !ignore_cache) { - log.Log (6, " > streams are considered different because -f was passed to " + NAME + "."); - return false; - } - if (astream.Length != bstream.Length) { log.Log (6, " > streams are considered different because their lengths do not match."); return false; diff --git a/tools/common/error.cs b/tools/common/error.cs index d61c1f80f6eb..626a112453e5 100644 --- a/tools/common/error.cs +++ b/tools/common/error.cs @@ -9,8 +9,6 @@ namespace Xamarin.Bundler { public class ProductException : Exception { - public static string Prefix => ErrorHelper.Prefix; - public ProductException (int code, string message) : this (code, false, message) { @@ -49,22 +47,37 @@ public ProductException (int code, bool error, Exception? innerException, string public int Code { get; private set; } - public bool Error { get; private set; } + bool Warning { get; set; } + + public bool IsError (IToolLog? log) + { + if (!Warning) + return true; + if (log is null) + return false; + return ErrorHelper.GetWarningLevel (log, Code) == ErrorHelper.WarningLevel.Error; + } void SetValues (int code, bool error) { Code = code; - Error = error || ErrorHelper.GetWarningLevel (code) == ErrorHelper.WarningLevel.Error; + Warning = !error; } // http://blogs.msdn.com/b/msbuild/archive/2006/11/03/msbuild-visual-studio-aware-error-messages-and-message-formats.aspx public override string ToString () + { + return ToString (null); + } + + // http://blogs.msdn.com/b/msbuild/archive/2006/11/03/msbuild-visual-studio-aware-error-messages-and-message-formats.aspx + public string ToString (IToolLog? log) { var sb = new StringBuilder (); if (!String.IsNullOrEmpty (FileName)) sb.Append (FileName).Append ('(').Append (LineNumber).Append ("): "); - sb.Append (Error ? "error" : "warning").Append (' ').Append (Prefix).Append (Code.ToString ("0000: ")).Append (Message); + sb.Append (IsError (log) ? "error" : "warning").Append (' ').Append (ErrorHelper.GetPrefix (log)).Append (Code.ToString ("0000: ")).Append (Message); return sb.ToString (); } diff --git a/tools/dotnet-linker/BackingFieldDelayHandler.cs b/tools/dotnet-linker/BackingFieldDelayHandler.cs index c77ad969e721..9a61786f7c0d 100644 --- a/tools/dotnet-linker/BackingFieldDelayHandler.cs +++ b/tools/dotnet-linker/BackingFieldDelayHandler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Linker; @@ -38,7 +39,7 @@ public override void Initialize (LinkContext context, MarkContext markContext) } // cache `Dispose` body of optimization NSObject subclasses - static Dictionary dispose = new (); + static ConditionalWeakTable> disposes = new (); protected override void Process (MethodDefinition method) { @@ -54,7 +55,7 @@ protected override void Process (MethodDefinition method) return; // keep original for later (if needed) - dispose.Add (method, method.Body); + disposes.GetOrCreateValue (LinkContext).Add (method, method.Body); // setting body to null will only cause it to be reloaded again // same if we don't get a new IL processor @@ -68,6 +69,9 @@ protected override void Process (MethodDefinition method) public static void ReapplyDisposedFields (DerivedLinkContext context, string operation) { // note: all methods in the dictionary are marked (since they were added from an IMarkHandler) + if (!disposes.TryGetValue (context, out var dispose)) + return; + var app = context.App; foreach ((var method, var body) in dispose) { foreach (var ins in body.Instructions) { diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index a7d9909785ed..e9375514f12c 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -276,7 +276,7 @@ public static LinkerConfiguration GetInstance (LinkContext context) break; case "NoWarn": try { - ErrorHelper.ParseWarningLevel (ErrorHelper.WarningLevel.Disable, value); + ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Disable, value); } catch (Exception ex) { throw new InvalidOperationException ($"Invalid WarnAsError '{value}' in {linker_file}", ex); } @@ -357,11 +357,11 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application.RuntimeConfigurationFile = value; break; case "SdkDevPath": - Driver.SdkRoot = value; + Application.SdkRoot = value; break; case "SdkRootDirectory": SdkRootDirectory = value; - Driver.SetFrameworkCurrentDirectory (value); + Application.FrameworkCurrentDirectory = value; break; case "SdkVersion": if (!Version.TryParse (value, out var sdk_version)) @@ -380,7 +380,7 @@ public static LinkerConfiguration GetInstance (LinkContext context) case "TargetFramework": if (!TargetFramework.TryParse (value, out var tf)) throw new InvalidOperationException ($"Invalid TargetFramework '{value}' in {linker_file}"); - Driver.TargetFramework = TargetFramework.Parse (value); + Application.TargetFramework = TargetFramework.Parse (value); break; case "TypeMapAssemblyName": Application.TypeMapAssemblyName = value; @@ -401,14 +401,14 @@ public static LinkerConfiguration GetInstance (LinkContext context) break; case "Warn": try { - ErrorHelper.ParseWarningLevel (ErrorHelper.WarningLevel.Warning, value); + ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Warning, value); } catch (Exception ex) { throw new InvalidOperationException ($"Invalid Warn '{value}' in {linker_file}", ex); } break; case "WarnAsError": try { - ErrorHelper.ParseWarningLevel (ErrorHelper.WarningLevel.Error, value); + ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Error, value); } catch (Exception ex) { throw new InvalidOperationException ($"Invalid WarnAsError '{value}' in {linker_file}", ex); } @@ -432,8 +432,6 @@ public static LinkerConfiguration GetInstance (LinkContext context) } } - ErrorHelper.Platform = Platform; - // Optimizations.Parse can only be called after setting ErrorHelper.Platform if (!StringUtils.IsNullOrEmpty (user_optimize_flags)) { var messages = new List (); @@ -468,8 +466,8 @@ public static LinkerConfiguration GetInstance (LinkContext context) break; } - if (Driver.TargetFramework.Platform != Platform) - throw ErrorHelper.CreateError (99, "Inconsistent platforms. TargetFramework={0}, Platform={1}", Driver.TargetFramework.Platform, Platform); + if (Application.TargetFramework.Platform != Platform) + throw ErrorHelper.CreateError (99, "Inconsistent platforms. TargetFramework={0}, Platform={1}", Application.TargetFramework.Platform, Platform); if (Application.XamarinRuntime != XamarinRuntime.MonoVM && Application.UseInterpreter) { Application.Log (4, "The interpreter is enabled, but the current runtime isn't MonoVM. The interpreter settings will be ignored."); @@ -574,7 +572,7 @@ public void Write () Application.Log ($" Registrar: {Application.Registrar} (Options: {Application.RegistrarOptions})"); Application.Log ($" RuntimeConfigurationFile: {Application.RuntimeConfigurationFile}"); Application.Log ($" RequirePInvokeWrappers: {Application.RequiresPInvokeWrappers}"); - Application.Log ($" SdkDevPath: {Driver.SdkRoot}"); + Application.Log ($" SdkDevPath: {Application.SdkRoot}"); Application.Log ($" SdkRootDirectory: {SdkRootDirectory}"); Application.Log ($" SdkVersion: {SdkVersion}"); Application.Log ($" TypeMapAssemblyName: {Application.TypeMapAssemblyName}"); @@ -636,15 +634,23 @@ public static void Report (LinkContext context, IList exceptions) // Since we print using a standard message format, msbuild will parse those error messages and show // them as msbuild errors. var list = ErrorHelper.CollectExceptions (exceptions); - var allWarnings = list.All (v => v is ProductException pe && !pe.Error); + if (!TryGetInstance (context, out var instance)) { + // Something went very wrong. Just dump out everything. + context.LogMessage (MessageContainer.CreateCustomErrorMessage ("No linker configuration available.", 7000)); + foreach (var exception in exceptions) { + context.LogMessage (MessageContainer.CreateCustomErrorMessage (exception.ToString (), 7000)); + } + return; + } + + var allWarnings = list.All (v => v is ProductException pe && !pe.IsError (instance.Application)); if (!allWarnings) { - TryGetInstance (context, out var instance); - var platform = (instance?.Platform)?.ToString () ?? "unknown"; + var platform = instance.Platform.ToString (); var msg = MessageContainer.CreateCustomErrorMessage (Errors.MX7000 /* An error occurred while executing the custom linker steps. Please review the build log for more information. */, 7000, platform); context.LogMessage (msg); } // ErrorHelper.Show will print our errors and warnings to stderr. - ErrorHelper.Show (ConsoleLog.Instance, list); + ErrorHelper.Show (instance.Application, list); } public IEnumerable GetNonDeletedAssemblies (BaseStep step) diff --git a/tools/dotnet-linker/SetupStep.cs b/tools/dotnet-linker/SetupStep.cs index 3d1ecf296cf3..a6fb4461aef7 100644 --- a/tools/dotnet-linker/SetupStep.cs +++ b/tools/dotnet-linker/SetupStep.cs @@ -22,7 +22,6 @@ public class SetupStep : ConfigurationAwareStep { protected override void TryProcess () { Configuration.Write (); - ErrorHelper.Platform = Configuration.Platform; Directory.CreateDirectory (Configuration.ItemsDirectory); Directory.CreateDirectory (Configuration.CacheDirectory); } diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs index 56423d905bff..7c5eb5ca3775 100644 --- a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs +++ b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs @@ -136,7 +136,7 @@ Exception [] CollectExceptions (Exception e, Func createExcept // If we're only reporting warnings, then don't add the step-specific exception at all. if (CollectProductExceptions (e, out var productExceptions)) { // don't add inner exception - if (productExceptions.Any (v => v.Error)) { + if (productExceptions.Any (v => v.IsError (App))) { var ex = createException (); // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. productExceptions.Add (ex); diff --git a/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs b/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs index cf3f544bc5b6..e494cec5a972 100644 --- a/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs @@ -47,7 +47,7 @@ AssemblyDefinition CreateTypeMapRootAssembly (ModuleParameters moduleParameters, AssemblyDefinition rootTypeMapAssembly; // .NET 10 doesn't support a separate root type map assembly, so we have to add these attributes to the entry assembly instead. - var useEntryAssemblyAsRootTypeMapAssembly = Driver.TargetFramework.Version.Major <= 10; + var useEntryAssemblyAsRootTypeMapAssembly = App.TargetFramework.Version.Major <= 10; if (useEntryAssemblyAsRootTypeMapAssembly) { rootTypeMapAssembly = Configuration.EntryAssembly; diff --git a/tools/linker/CoreTypeMapStep.cs b/tools/linker/CoreTypeMapStep.cs index 76fd37c49770..c180cf3b43fc 100644 --- a/tools/linker/CoreTypeMapStep.cs +++ b/tools/linker/CoreTypeMapStep.cs @@ -167,7 +167,7 @@ bool IsWrapperType (TypeDefinition type) // Cache the results of the IsCIFilter check in a dictionary. It makes this method slightly faster // (total time spent in IsCIFilter when linking monotouch-test went from 11 ms to 3ms). - static Dictionary ci_filter_types = new Dictionary (); + Dictionary ci_filter_types = new Dictionary (); bool IsCIFilter (TypeReference type) { if (type is null) diff --git a/tools/mtouch/AssemblyResolver.cs b/tools/mtouch/AssemblyResolver.cs index e5386afd5df5..3b2f94d2d865 100644 --- a/tools/mtouch/AssemblyResolver.cs +++ b/tools/mtouch/AssemblyResolver.cs @@ -25,7 +25,7 @@ namespace MonoTouch.Tuner { // recent cecil removed some overloads - https://github.com/mono/cecil/commit/42db79cc16f1cbe8dbab558904e188352dba2b41 public static class AssemblyResolverRocks { - static ReaderParameters defaults = new ReaderParameters (); + static readonly ReaderParameters defaults = new ReaderParameters (); public static AssemblyDefinition Resolve (this IAssemblyResolver self, string fullName) { From c102699a9c27f68d8c0fde7ad8c219958e62d86b Mon Sep 17 00:00:00 2001 From: "CSIGS@microsoft.com" Date: Thu, 4 Jun 2026 02:08:29 -0700 Subject: [PATCH 69/97] LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260603055155830 to main (#25626) LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260603055155830 to main with localized lcls --- .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ 6 files changed, 162 insertions(+) diff --git a/macios/Localize/loc/cs/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/cs/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 44c49d101321..e7c0532070d5 100644 --- a/macios/Localize/loc/cs/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/cs/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + Komponenty).]]> + + + + + + + Components).]]> + + Komponenty).]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/de/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/de/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 564e87794819..10b33895439f 100644 --- a/macios/Localize/loc/de/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/de/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + Komponenten) ausführen.]]> + + + + + + + Components).]]> + + Komponenten) ausführen.]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/es/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/es/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index b692e401e9c4..32a33885ce1a 100644 --- a/macios/Localize/loc/es/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/es/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + Componentes).]]> + + + + + + + Components).]]> + + Componentes).]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/ko/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/ko/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 8f6a9309a649..68a284939316 100644 --- a/macios/Localize/loc/ko/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/ko/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + 구성 요소)에서 'xcodebuild -downloadPlatform {0}'을 실행하여 설치합니다.]]> + + + + + + + Components).]]> + + 구성 요소)에서 'xcodebuild -downloadPlatform {0}'을 실행하여 시뮬레이터 런타임을 업데이트합니다.]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/pt-BR/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/pt-BR/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index b36703f288b6..503593408653 100644 --- a/macios/Localize/loc/pt-BR/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/pt-BR/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + Componentes).]]> + + + + + + + Components).]]> + + Componentes).]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/zh-Hant/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/zh-Hant/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 0c5e6cd287eb..0f9913fdbb09 100644 --- a/macios/Localize/loc/zh-Hant/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/zh-Hant/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + 元件]來安裝。]]> + + + + + + + Components).]]> + + 元件]來更新模擬器執行階段。]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + From e7e179532c76ce40ce1c8bcf12b1d9117cc4ab6e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 14:54:45 +0200 Subject: [PATCH 70/97] [tests] Ignore any tests using CFNetworkHandler. Fixes #25634. (#25635) There are known issues (deadlocks) with CFNetworkHandler: https://github.com/dotnet/macios/issues/25634 CFNetworkHandler is obsolete, so we won't fix any such issues, so to avoid deadlocks, just avoid testing CFNetworkHandler. Fixes https://github.com/dotnet/macios/issues/25634. --- tests/monotouch-test/HttpClient/HttpClientTest.cs | 4 +++- tests/monotouch-test/System.Net.Http/MessageHandlers.cs | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/monotouch-test/HttpClient/HttpClientTest.cs b/tests/monotouch-test/HttpClient/HttpClientTest.cs index ba17c5427721..77efe890d8fb 100644 --- a/tests/monotouch-test/HttpClient/HttpClientTest.cs +++ b/tests/monotouch-test/HttpClient/HttpClientTest.cs @@ -80,7 +80,9 @@ public static IHandlerWrapper GetWrapper (Type handlerType) } [TestCase (typeof (HttpClientHandler), 8)] - [TestCase (typeof (CFNetworkHandler), 8)] + // There are known issues (deadlocks) with CFNetworkHandler: https://github.com/dotnet/macios/issues/25634 + // CFNetworkHandler is obsolete, so we won't fix any such issues, so to avoid deadlocks, just avoid testing CFNetworkHandler. + [TestCase (typeof (CFNetworkHandler), 8, Ignore = "There are known issues (deadlocks) with CFNetworkHandler: https://github.com/dotnet/macios/issues/25634")] [TestCase (typeof (NSUrlSessionHandler), 9)] public void EnsureModifiabilityPostSend (Type handlerType, int macOSMinVersion) { diff --git a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs index b159fab8c8c5..6bf154dc0934 100644 --- a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs +++ b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs @@ -52,7 +52,9 @@ HttpMessageHandler GetHandler (Type handler_type) [Test] [TestCase (typeof (HttpClientHandler))] - [TestCase (typeof (CFNetworkHandler))] + // There are known issues (deadlocks) with CFNetworkHandler: https://github.com/dotnet/macios/issues/25634 + // CFNetworkHandler is obsolete, so we won't fix any such issues, so to avoid deadlocks, just avoid testing CFNetworkHandler. + [TestCase (typeof (CFNetworkHandler), Ignore = "There are known issues (deadlocks) with CFNetworkHandler: https://github.com/dotnet/macios/issues/25634")] [TestCase (typeof (SocketsHttpHandler))] [TestCase (typeof (NSUrlSessionHandler))] public void DnsFailure (Type handlerType) @@ -438,7 +440,9 @@ public void TestNSUrlSessionTimeoutExceptionWhileStreamingContent () // ensure that if we have a redirect, we do not have the auth headers in the following requests [TestCase (typeof (HttpClientHandler))] - [TestCase (typeof (CFNetworkHandler))] + // There are known issues (deadlocks) with CFNetworkHandler: https://github.com/dotnet/macios/issues/25634 + // CFNetworkHandler is obsolete, so we won't fix any such issues, so to avoid deadlocks, just avoid testing CFNetworkHandler. + [TestCase (typeof (CFNetworkHandler), Ignore = "There are known issues (deadlocks) with CFNetworkHandler: https://github.com/dotnet/macios/issues/25634")] [TestCase (typeof (NSUrlSessionHandler))] public void RedirectionWithAuthorizationHeaders (Type handlerType) { From 063c4ad515ec26820053d67b25ef63d26500a384 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 14:55:23 +0200 Subject: [PATCH 71/97] [tests] Don't strip symbols on any platform. (#25643) `MtouchNoSymbolStrip` doesn't do anything on macOS, so use `NoSymbolStrip` instead. --- tests/monotouch-test/dotnet/shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/monotouch-test/dotnet/shared.csproj b/tests/monotouch-test/dotnet/shared.csproj index 989a89db0bef..ecb61eeb4914 100644 --- a/tests/monotouch-test/dotnet/shared.csproj +++ b/tests/monotouch-test/dotnet/shared.csproj @@ -13,7 +13,7 @@ $(RootTestsDirectory)\monotouch-test - true + true $(DefineConstants);DEBUG From ac159468375779e75d07ca0a41eeb414b838304c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:05:37 -0400 Subject: [PATCH 72/97] [github] Increase Code Radiator safe-output patch size limit (#25642) Code Radiator failed when large merge PRs exceeded the default 1024 KB safe-output patch limit, causing PR creation to fall back to an issue. This updates the workflow to allow substantially larger generated patches. - **Workflow source** - Raise the top-level `safe-outputs.max-patch-size` in `code-radiator.md` from the default to `10240` KB. - **Compiled workflow** - Update the generated safe-output config in `code-radiator.lock.yml` so both PR creation and PR branch updates use the new `10240` KB limit. - **Result** - Large inter-branch merge patches can proceed through `create_pull_request` instead of being rejected by the safe-output size guard. ```yaml safe-outputs: max-patch-files: 1000 max-patch-size: 10240 ``` --------- Co-authored-by: rolfbjarne <249268+rolfbjarne@users.noreply.github.com> --- .github/workflows/code-radiator.lock.yml | 34 ++++++++++++------------ .github/workflows/code-radiator.md | 1 + 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/code-radiator.lock.yml b/.github/workflows/code-radiator.lock.yml index 76736b1d9f98..2f6344841183 100644 --- a/.github/workflows/code-radiator.lock.yml +++ b/.github/workflows/code-radiator.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1ba69a47e6cfbd91cfe78d637aac47fa364d969c56a89ac3523d5c936169f250","body_hash":"b966744ea05e67fd471e10231f2cce01d0525af33bc90ff317655344e889cc92","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"3f1a9ee34482a7876690cddd0a715bb51cd8db1f78e741ef5d16c383db9b0508","body_hash":"b966744ea05e67fd471e10231f2cce01d0525af33bc90ff317655344e889cc92","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -192,24 +192,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_686c839083e52610_EOF' + cat << 'GH_AW_PROMPT_78cbc6c89dc649e4_EOF' - GH_AW_PROMPT_686c839083e52610_EOF + GH_AW_PROMPT_78cbc6c89dc649e4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_686c839083e52610_EOF' + cat << 'GH_AW_PROMPT_78cbc6c89dc649e4_EOF' Tools: add_comment(max:10), create_pull_request(max:10), update_pull_request(max:10), add_labels(max:10), push_to_pull_request_branch(max:10), missing_tool, missing_data, noop - GH_AW_PROMPT_686c839083e52610_EOF + GH_AW_PROMPT_78cbc6c89dc649e4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_686c839083e52610_EOF' + cat << 'GH_AW_PROMPT_78cbc6c89dc649e4_EOF' - GH_AW_PROMPT_686c839083e52610_EOF + GH_AW_PROMPT_78cbc6c89dc649e4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_686c839083e52610_EOF' + cat << 'GH_AW_PROMPT_78cbc6c89dc649e4_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -241,12 +241,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_686c839083e52610_EOF + GH_AW_PROMPT_78cbc6c89dc649e4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_686c839083e52610_EOF' + cat << 'GH_AW_PROMPT_78cbc6c89dc649e4_EOF' {{#runtime-import .github/workflows/code-radiator.md}} - GH_AW_PROMPT_686c839083e52610_EOF + GH_AW_PROMPT_78cbc6c89dc649e4_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -461,9 +461,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_9a64ac31bc43ee2c_EOF' - {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":1000,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","signed_commits":false},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"signed_commits":false,"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} - GH_AW_SAFE_OUTPUTS_CONFIG_9a64ac31bc43ee2c_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_1667342258ba4bbd_EOF' + {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_pull_request":{"allowed_base_branches":["net*.0","xcode*","xcode*.*"],"max":10,"max_patch_files":1000,"max_patch_size":10240,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","signed_commits":false},"create_report_incomplete_issue":{},"merge_pull_request":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":10,"max_patch_size":10240,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"signed_commits":false,"target":"*","title_prefix":"🤖 Merge 'main' =\u003e '"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":true,"max":10,"update_branch":false}} + GH_AW_SAFE_OUTPUTS_CONFIG_1667342258ba4bbd_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -806,7 +806,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_ecdd849dd0c87e2d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_b4c6a644a0b8a10f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -850,7 +850,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_ecdd849dd0c87e2d_EOF + GH_AW_MCP_CONFIG_b4c6a644a0b8a10f_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1594,7 +1594,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":1000,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"signed_commits\":false},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"signed_commits\":false,\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_pull_request\":{\"allowed_base_branches\":[\"net*.0\",\"xcode*\",\"xcode*.*\"],\"max\":10,\"max_patch_files\":1000,\"max_patch_size\":10240,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"signed_commits\":false},\"create_report_incomplete_issue\":{},\"merge_pull_request\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":10,\"max_patch_size\":10240,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"signed_commits\":false,\"target\":\"*\",\"title_prefix\":\"🤖 Merge 'main' =\\u003e '\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":10,\"update_branch\":false}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/code-radiator.md b/.github/workflows/code-radiator.md index e1fe86d0d97d..89fd81f9bdef 100644 --- a/.github/workflows/code-radiator.md +++ b/.github/workflows/code-radiator.md @@ -26,6 +26,7 @@ checkout: fetch-depth: 0 safe-outputs: max-patch-files: 1000 + max-patch-size: 10240 create-pull-request: max: 10 signed-commits: false From dc9651151bc2d439dc17261589ed8274e4d1c71c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:06:18 -0400 Subject: [PATCH 73/97] [github] Disable weekly schedule trigger for CI Post-Mortem Analysis workflow (#25602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CI Post-Mortem workflow was failing on its weekly schedule because it requires authenticated Azure DevOps access that isn't available in the scheduled runner environment. Disabling the schedule so the workflow can only be triggered manually (`workflow_dispatch`). ## Changes - **`.github/workflows/ci-postmortem.md`** — removed the `schedule` trigger from the frontmatter `on:` block - **`.github/workflows/ci-postmortem.lock.yml`** — recompiled from updated source --------- Co-authored-by: rolfbjarne <249268+rolfbjarne@users.noreply.github.com> --- .github/workflows/ci-postmortem.lock.yml | 32 ++++++++++-------------- .github/workflows/ci-postmortem.md | 2 -- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci-postmortem.lock.yml b/.github/workflows/ci-postmortem.lock.yml index 58d2f385a005..61755f20a0a7 100644 --- a/.github/workflows/ci-postmortem.lock.yml +++ b/.github/workflows/ci-postmortem.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"15354af11629eb0049ecb70f03b13ed2df90af330f1d6a40d7ce9a202538bb0b","body_hash":"ad13b1075fd08c989fe77b74abdce355d88ab2ebeea692809a67d8b68cd7559b","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e7f2b78dc23c58554e0403a91ececfda7a6869936e0f622236e8b581313a38a7","body_hash":"ad13b1075fd08c989fe77b74abdce355d88ab2ebeea692809a67d8b68cd7559b","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.5"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -47,9 +47,6 @@ name: "CI Post-Mortem Analysis" on: - schedule: - - cron: "11 2 * * 0" - # Friendly format: weekly on sunday (scattered) workflow_dispatch: inputs: aw_context: @@ -186,20 +183,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_956c986f1e45d6a0_EOF' + cat << 'GH_AW_PROMPT_452cd77d855884c0_EOF' - GH_AW_PROMPT_956c986f1e45d6a0_EOF + GH_AW_PROMPT_452cd77d855884c0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_956c986f1e45d6a0_EOF' + cat << 'GH_AW_PROMPT_452cd77d855884c0_EOF' Tools: add_comment(max:20), create_issue(max:20), update_issue(max:20), missing_tool, missing_data, noop - GH_AW_PROMPT_956c986f1e45d6a0_EOF + GH_AW_PROMPT_452cd77d855884c0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_956c986f1e45d6a0_EOF' + cat << 'GH_AW_PROMPT_452cd77d855884c0_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -228,12 +225,12 @@ jobs: {{/if}} - GH_AW_PROMPT_956c986f1e45d6a0_EOF + GH_AW_PROMPT_452cd77d855884c0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_956c986f1e45d6a0_EOF' + cat << 'GH_AW_PROMPT_452cd77d855884c0_EOF' {{#runtime-import .github/workflows/ci-postmortem.md}} - GH_AW_PROMPT_956c986f1e45d6a0_EOF + GH_AW_PROMPT_452cd77d855884c0_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -316,9 +313,6 @@ jobs: permissions: contents: read issues: read - concurrency: - group: "gh-aw-copilot-${{ github.workflow }}" - queue: max env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: "" @@ -441,9 +435,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c2d474e65378a915_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_418d6ffb54599ea1_EOF' {"add_comment":{"max":20},"create_issue":{"max":20},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":20}} - GH_AW_SAFE_OUTPUTS_CONFIG_c2d474e65378a915_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_418d6ffb54599ea1_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -727,7 +721,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_7bd5cd6513b45b21_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f57e5774da7cad26_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -771,7 +765,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_7bd5cd6513b45b21_EOF + GH_AW_MCP_CONFIG_f57e5774da7cad26_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/ci-postmortem.md b/.github/workflows/ci-postmortem.md index 64065525ca6e..ae2eda3ac76c 100644 --- a/.github/workflows/ci-postmortem.md +++ b/.github/workflows/ci-postmortem.md @@ -1,7 +1,5 @@ --- on: - schedule: - - cron: "weekly on sunday" workflow_dispatch: permissions: contents: read From 2743052e0ec066d28884442dede68bfceef0076d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 18:10:18 +0200 Subject: [PATCH 74/97] [tools] Don't inline calls to Class.GetHandle for BrowserEngineKit/BrowserEngineCore classes. (#25629) --- tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs b/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs index 4cb66ed8fd27..11e8dd1996b8 100644 --- a/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs +++ b/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs @@ -215,6 +215,13 @@ bool isOurOwnCode () // UITitlebar is a weird special case, the class exists in the headers, and it's documented online, but it's not possible to link with it (not even in an Xcode project, it's not in any .tbd files). continue; } + + if (objCType.Type.Namespace == "BrowserEngineKit" || objCType.Type.Namespace == "BrowserEngineCore") { + // Most apps do not use BrowserEngineKit, and linking with it when an app is not supposed to will prevent it from getting approved in the App Store. + // So we treat these frameworks specially, where we don't link with these two frameworks by default, *even if they're detected as used*, + // which means we shouldn't inline Class.GetHandle calls for any classes in these frameworks, since native linking will fail. + continue; + } } ldstr.OpCode = OpCodes.Call; From c513ce7161f9582e050d4bc33875e8177c25b994 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 18:11:41 +0200 Subject: [PATCH 75/97] [tests] Filter out any warnings about using deprecated locations for the Xcode location. (#25628) These warnings are just noise for us at this point. Eventually we'll just stop using the deprecated locations. --------- Co-authored-by: Rolf Bjarne Kvinge --- tests/dotnet/UnitTests/BundleStructureTest.cs | 14 +++---- tests/dotnet/UnitTests/Extensions.cs | 27 +++++++++++++ tests/dotnet/UnitTests/ExtensionsTest.cs | 2 +- tests/dotnet/UnitTests/ProjectTest.cs | 38 ++++++------------- tests/dotnet/UnitTests/TemplateTest.cs | 2 +- tests/dotnet/UnitTests/TrimmerWarningsTest.cs | 6 +-- tests/dotnet/UnitTests/WindowsTest.cs | 8 ++-- tests/dotnet/UnitTests/XcodeProjectTests.cs | 2 +- 8 files changed, 52 insertions(+), 47 deletions(-) diff --git a/tests/dotnet/UnitTests/BundleStructureTest.cs b/tests/dotnet/UnitTests/BundleStructureTest.cs index d751c42dbdd6..c2cd6eab5a41 100644 --- a/tests/dotnet/UnitTests/BundleStructureTest.cs +++ b/tests/dotnet/UnitTests/BundleStructureTest.cs @@ -646,7 +646,7 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers, CodeSignat properties ["Configuration"] = configuration; var rv = DotNet.AssertBuild (project_path, properties); var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); - var warningMessages = FilterWarnings (warnings); + var warningMessages = FilterWarnings (warnings, platform); var isReleaseBuild = string.Equals (configuration, "Release", StringComparison.OrdinalIgnoreCase); var platformString = platform.AsString (); @@ -692,7 +692,7 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers, CodeSignat rv = DotNet.AssertBuild (project_path, properties); warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); - warningMessages = FilterWarnings (warnings); + warningMessages = FilterWarnings (warnings, platform); CheckAppBundleContents (platform, appPath, rids, signature, isReleaseBuild); Assert.That (warningMessages, Is.EqualTo (expectedWarnings), "Warnings Rebuild 1"); @@ -704,7 +704,7 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers, CodeSignat rv = DotNet.AssertBuild (project_path, properties); warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); - warningMessages = FilterWarnings (warnings); + warningMessages = FilterWarnings (warnings, platform); CheckAppBundleContents (platform, appPath, rids, signature, isReleaseBuild); Assert.That (warningMessages, Is.EqualTo (expectedWarnings), "Warnings Rebuild 2"); @@ -713,7 +713,7 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers, CodeSignat // a simple rebuild should succeed rv = DotNet.AssertBuild (project_path, properties); warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); - warningMessages = FilterWarnings (warnings); + warningMessages = FilterWarnings (warnings, platform); CheckAppBundleContents (platform, appPath, rids, signature, isReleaseBuild); Assert.That (warningMessages, Is.EqualTo (expectedWarnings), "Warnings Rebuild 3"); @@ -740,9 +740,9 @@ static string GetXCFrameworkArchitectures (ApplePlatform platform, string runtim } } - public static string [] FilterWarnings (IEnumerable warnings, bool canonicalizePaths = false) + public static string [] FilterWarnings (IEnumerable warnings, ApplePlatform platform, bool canonicalizePaths = false) { - return warnings + return Extensions.FilterWarnings (warnings, platform) .Select (v => v?.Message!).Where (v => !string.IsNullOrWhiteSpace (v)) // Remove warnings of the form "This call site is reachable on: '...' and later. 'TheAPI' is only supported on: '...' and later." .Where (v => !v.StartsWith ("This call site is reachable on:")) @@ -752,8 +752,6 @@ public static string [] FilterWarnings (IEnumerable warnings, boo .Where (v => !v.Contains (" is obsolete: ")) // More obsolete warnings .Where (v => !v.Contains (" overrides obsolete member ")) - // Don't care about this - .Where (v => !v.Contains ("Supported iPhone orientations have not been set")) // Canonicalize if so requested .Select (v => canonicalizePaths ? v.Replace (Path.DirectorySeparatorChar, '/') : v) // Sort the messages so that comparison against the expected array is faster diff --git a/tests/dotnet/UnitTests/Extensions.cs b/tests/dotnet/UnitTests/Extensions.cs index ba97980be9a9..249cf21297cc 100644 --- a/tests/dotnet/UnitTests/Extensions.cs +++ b/tests/dotnet/UnitTests/Extensions.cs @@ -113,6 +113,33 @@ public static void AssertWarnings (this IEnumerable actualWarning Assert.Fail ($"Missing warning: {evt.File}: {evt.Message}"); }); } + + public static IEnumerable FilterWarnings (this IEnumerable actualWarnings, ApplePlatform platform, bool filterSupportediPhoneOrientations = true, bool filterXcodeLocation = true) + { + return actualWarnings.Where (v => !IsFilteredWarning (v, platform, filterSupportediPhoneOrientations, filterXcodeLocation)); + } + + public static bool IsFilteredWarning (BuildLogEvent evt, ApplePlatform platform, bool filterSupportediPhoneOrientations = true, bool filterXcodeLocation = true) + { + var v = evt.Message?.Trim (); + + if (string.IsNullOrEmpty (v)) + return false; + + if (filterSupportediPhoneOrientations && platform == ApplePlatform.iOS && v == "Supported iPhone orientations have not been set") + return true; + + if (filterXcodeLocation) { + if (v.Contains ("The environment variable 'MD_APPLE_SDK_ROOT' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use.")) + return true; + if (v.Contains ($"The settings file '{Environment.GetEnvironmentVariable ("HOME")}/Library/Preferences/maui/Settings.plist' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use.")) + return true; + if (v.Contains ($"The settings file '{Environment.GetEnvironmentVariable ("HOME")}/Library/Preferences/Xamarin/Settings.plist' is deprecated, and will be ignored. Please use the 'DEVELOPER_DIR' environment variable or the 'XcodeLocation' MSBuild property to choose which Xcode to use.")) + return true; + } + + return false; + } } public class ExpectedBuildMessage { diff --git a/tests/dotnet/UnitTests/ExtensionsTest.cs b/tests/dotnet/UnitTests/ExtensionsTest.cs index cf50e2098e90..cba31ce61c92 100644 --- a/tests/dotnet/UnitTests/ExtensionsTest.cs +++ b/tests/dotnet/UnitTests/ExtensionsTest.cs @@ -59,7 +59,7 @@ public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeId } else { var rv = DotNet.AssertBuild (project_path, properties); var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath) - .Where (v => v?.Message?.Contains ("Supported iPhone orientations have not been set") != true) + .FilterWarnings (platform) .ToArray (); var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), $"{extensionProject}.appex"); AssertWarningMessages (warnings, [ diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index 4fe2a5d3f241..8cbe51e3d86d 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -506,7 +506,7 @@ public void IsOverrideRuntimeIdentifier (ApplePlatform platform, string runtimeI properties.Remove ("RuntimeIdentifiers"); properties ["cmdline:RuntimeIdentifier"] = "maccatalyst-x64"; var rv = DotNet.AssertBuild (project_path, properties); - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).ToArray (); Assert.That (warnings.Length, Is.EqualTo (1), "Warning Count"); Assert.That (warnings [0].Message, Is.EqualTo ("RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file."), "Warning message"); } @@ -649,7 +649,7 @@ public void FilesInAppBundle (ApplePlatform platform, string runtimeIdentifiers) // Build again - this time it'll fail var rv = DotNet.Build (project_path, properties); - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).ToArray (); Assert.That (rv.ExitCode, Is.Not.EqualTo (0), "Unexpected success"); Assert.That (warnings.Length, Is.EqualTo (1), "Warning Count"); Assert.That (warnings [0].Message, Is.EqualTo ($"Found files in the root directory of the app bundle. This will likely cause codesign to fail. Files:\nbin/Debug/{Configuration.DotNetTfm}-maccatalyst/maccatalyst-x64/MySimpleApp.app/otherfile.txt\nbin/Debug/{Configuration.DotNetTfm}-maccatalyst/maccatalyst-x64/MySimpleApp.app/otherdir\nbin/Debug/{Configuration.DotNetTfm}-maccatalyst/maccatalyst-x64/MySimpleApp.app/otherdir/otherfile.log"), "Warning"); @@ -658,7 +658,7 @@ public void FilesInAppBundle (ApplePlatform platform, string runtimeIdentifiers) var enableAutomaticCleanupProperties = new Dictionary (properties); enableAutomaticCleanupProperties ["EnableAutomaticAppBundleRootDirectoryCleanup"] = "true"; rv = DotNet.AssertBuild (project_path, enableAutomaticCleanupProperties); - warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); + warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).ToArray (); Assert.That (warnings.Length, Is.EqualTo (0), "Warning Count"); // Verify that the files were in fact removed. @@ -766,7 +766,7 @@ public void BindingWithDefaultCompileInclude (ApplePlatform platform) Assert.That (myStruct, Is.Not.Null, "MyStruct type"); Assert.That (myStruct!.IsValueType, Is.True, "MyStruct"); - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings:\n\t{string.Join ("\n\t", warnings)}"); } @@ -1583,12 +1583,7 @@ public void AppWithDuplicatedResources (ApplePlatform platform, string runtimeId throw new NotImplementedException (scenario.ToString ()); } } - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath) - .Where (evt => { - if (platform == ApplePlatform.iOS && evt.Message?.Trim () == "Supported iPhone orientations have not been set") - return false; - return true; - }); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform); warnings.AssertWarnings (expectedWarnings); if (bundleOriginalResources && expectedWarnings.Length > 0) { @@ -2530,12 +2525,10 @@ public void BuildMyNativeAotAppWithTrimAnalysisWarning (ApplePlatform platform, var rv = DotNet.AssertBuild (project_path, properties); // We expect to get a warning from the trim analzyer in Debug build - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath) + .FilterWarnings (platform) + .ToArray (); - // Ignore warnings we haven't fixed yet - if (platform == ApplePlatform.iOS) { - warnings = warnings.Where (w => w.Message?.Trim () != "Supported iPhone orientations have not been set").ToArray (); - } Assert.That (warnings.Length, Is.EqualTo (1), "Warning count"); Assert.That (warnings [0].Code, Is.EqualTo ("IL2075"), "Warning code"); @@ -2577,12 +2570,7 @@ public void PublishAot (ApplePlatform platform, string runtimeIdentifiers, strin // Verify that we have no warnings, but unfortunately we still have some we haven't fixed yet. // Ignore those, and fail the test if we stop getting them (so that we can update the test to not ignore them anymore). - rv.AssertNoWarnings ((evt) => { - if (platform == ApplePlatform.iOS && evt.Message?.Trim () == "Supported iPhone orientations have not been set") - return false; - - return true; - }); + rv.AssertNoWarnings ((evt) => !Extensions.IsFilteredWarning (evt, platform)); } [Test] @@ -2606,11 +2594,7 @@ public void PublishAotMonoTouchTest_NoIL2009 (ApplePlatform platform, string run var config = "Debug"; var runtimeIdentifierInfix = $"/{runtimeIdentifiers}/"; var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath) - .Where (evt => { - if (platform == ApplePlatform.iOS && evt.Message?.Trim () == "Supported iPhone orientations have not been set") - return false; - return true; - }); + .FilterWarnings (platform); var expectedWarnings = new ExpectedBuildMessage [] { new ExpectedBuildMessage ($"ILLINK", $"It's not safe to remove the dynamic registrar, because monotouchtest references 'ObjCRuntime.Runtime.ConnectMethod (System.Reflection.MethodInfo, ObjCRuntime.Selector)'."), new ExpectedBuildMessage ($"ILLINK", $"It's not safe to remove the dynamic registrar, because monotouchtest references 'ObjCRuntime.Runtime.ConnectMethod (System.Type, System.Reflection.MethodInfo, Foundation.ExportAttribute)'."), @@ -2731,7 +2715,7 @@ public void UnsupportedTargetPlatformVersion (ApplePlatform platform) if (IsTargetPlatformVersionCompatEnabled) { var rv = DotNet.AssertBuild (project_path, properties); - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).ToArray (); AssertWarningMessages (warnings, $"{minSupportedOSVersion} is not a valid TargetPlatformVersion for {platform.AsString ()}. This warning will become an error in future versions of the {platform.AsString ()} workload. Valid versions include:\n{string.Join ('\n', supportedApiVersions)}"); } else { var rv = DotNet.AssertBuildFailure (project_path, properties); diff --git a/tests/dotnet/UnitTests/TemplateTest.cs b/tests/dotnet/UnitTests/TemplateTest.cs index 24c9e1d54dca..00f8e790b0fe 100644 --- a/tests/dotnet/UnitTests/TemplateTest.cs +++ b/tests/dotnet/UnitTests/TemplateTest.cs @@ -220,7 +220,7 @@ public void CreateAndBuildProjectTemplate (TemplateInfo info) var proj = Path.Combine (outputDir, $"{info.Template}.{language.AsFileExtension ()}"); var properties = GetDefaultProperties (); var rv = DotNet.AssertBuild (proj, properties); - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (info.Platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings:\n\t{string.Join ("\n\t", warnings)}"); if (info.Execute) { diff --git a/tests/dotnet/UnitTests/TrimmerWarningsTest.cs b/tests/dotnet/UnitTests/TrimmerWarningsTest.cs index 719f8a8112ce..ab5b77f88d7e 100644 --- a/tests/dotnet/UnitTests/TrimmerWarningsTest.cs +++ b/tests/dotnet/UnitTests/TrimmerWarningsTest.cs @@ -128,11 +128,7 @@ void TrimmerWarnings (ApplePlatform platform, string runtimeIdentifiers, string var rv = DotNet.AssertBuild (project_path, properties); var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath) - .Where (evt => { - if (platform == ApplePlatform.iOS && evt.Message?.Trim () == "Supported iPhone orientations have not been set") - return false; - return true; - }); + .FilterWarnings (platform); warnings.AssertWarnings (expectedWarnings); } } diff --git a/tests/dotnet/UnitTests/WindowsTest.cs b/tests/dotnet/UnitTests/WindowsTest.cs index a3fe051dbd38..b7b9d9a2e25f 100644 --- a/tests/dotnet/UnitTests/WindowsTest.cs +++ b/tests/dotnet/UnitTests/WindowsTest.cs @@ -137,7 +137,7 @@ public void BundleStructureWithRemoteMac (ApplePlatform platform, string runtime var rv = DotNet.AssertBuild (project_path, properties); var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); - var warningMessages = BundleStructureTest.FilterWarnings (warnings, canonicalizePaths: true); + var warningMessages = BundleStructureTest.FilterWarnings (warnings, platform, canonicalizePaths: true); var isReleaseBuild = string.Equals (configuration, "Release", StringComparison.OrdinalIgnoreCase); var platformString = platform.AsString (); @@ -209,7 +209,7 @@ public void BundleStructureWithRemoteMac (ApplePlatform platform, string runtime rv = DotNet.AssertBuild (project_path, properties); var allTargets = BinLog.GetAllTargets (rv.BinLogPath); warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); - warningMessages = BundleStructureTest.FilterWarnings (warnings, canonicalizePaths: true); + warningMessages = BundleStructureTest.FilterWarnings (warnings, platform, canonicalizePaths: true); BundleStructureTest.CheckZippedAppBundleContents (platform, zippedAppBundlePath, rids, signature, isReleaseBuild); AssertWarningsEqual (expectedWarnings, warningMessages, "Warnings Rebuild 1"); @@ -229,7 +229,7 @@ public void BundleStructureWithRemoteMac (ApplePlatform platform, string runtime rv = DotNet.AssertBuild (project_path, properties); allTargets = BinLog.GetAllTargets (rv.BinLogPath); warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); - warningMessages = BundleStructureTest.FilterWarnings (warnings, canonicalizePaths: true); + warningMessages = BundleStructureTest.FilterWarnings (warnings, platform, canonicalizePaths: true); BundleStructureTest.CheckZippedAppBundleContents (platform, zippedAppBundlePath, rids, signature, isReleaseBuild); AssertWarningsEqual (expectedWarnings, warningMessages, "Warnings Rebuild 2"); @@ -246,7 +246,7 @@ public void BundleStructureWithRemoteMac (ApplePlatform platform, string runtime rv = DotNet.AssertBuild (project_path, properties); allTargets = BinLog.GetAllTargets (rv.BinLogPath); warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); - warningMessages = BundleStructureTest.FilterWarnings (warnings, canonicalizePaths: true); + warningMessages = BundleStructureTest.FilterWarnings (warnings, platform, canonicalizePaths: true); BundleStructureTest.CheckZippedAppBundleContents (platform, zippedAppBundlePath, rids, signature, isReleaseBuild); AssertWarningsEqual (expectedWarnings, warningMessages, "Warnings Rebuild 3"); diff --git a/tests/dotnet/UnitTests/XcodeProjectTests.cs b/tests/dotnet/UnitTests/XcodeProjectTests.cs index aab50fe70c93..dac323d491a4 100644 --- a/tests/dotnet/UnitTests/XcodeProjectTests.cs +++ b/tests/dotnet/UnitTests/XcodeProjectTests.cs @@ -149,7 +149,7 @@ public class Binding var properties = GetDefaultProperties (); var rv = DotNet.AssertBuild (proj, properties); - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings:\n\t{string.Join ("\n\t", warnings)}"); AssertXcFrameworkOutput (platform, testDir, xcodeProjName); } From 15b211351d4e1a61d904b8333928edccd6ce3831 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 18:13:52 +0200 Subject: [PATCH 76/97] [src] Remove CIFilterGenerator from iOS and Mac Catalyst. (#25630) This is a macOS-only class. But keep obsolete APIs throwing PlatformNotSupportedException until XAMCORE_5_0. --------- Co-authored-by: Rolf Bjarne Kvinge --- src/CoreImage/CIFilterGenerator.cs | 145 +++++++++++++++++++++++++ src/coreimage.cs | 12 -- src/frameworks.sources | 1 + tests/introspection/ApiSelectorTest.cs | 9 -- 4 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 src/CoreImage/CIFilterGenerator.cs diff --git a/src/CoreImage/CIFilterGenerator.cs b/src/CoreImage/CIFilterGenerator.cs new file mode 100644 index 000000000000..cdcd4d1d2646 --- /dev/null +++ b/src/CoreImage/CIFilterGenerator.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#if !XAMCORE_5_0 && (__IOS__ || __MACCATALYST__) + +using System.ComponentModel; + +namespace CoreImage; + +[Register ("CIFilterGenerator", true)] +[UnsupportedOSPlatform ("ios")] +[UnsupportedOSPlatform ("maccatalyst")] +[UnsupportedOSPlatform ("tvos")] +[SupportedOSPlatform ("macos")] +[Obsolete (Constants.TypeUnavailable, false)] +[EditorBrowsable (EditorBrowsableState.Never)] +public class CIFilterGenerator : NSObject, ICIFilterConstructor, INSCoding, INSCopying, INSSecureCoding { + [EditorBrowsable (EditorBrowsableState.Never)] + public override NativeHandle ClassHandle { get => throw new PlatformNotSupportedException (Constants.TypeUnavailable); } + + [EditorBrowsable (EditorBrowsableState.Never)] + public CIFilterGenerator (NSCoder coder) : base (NSObjectFlag.Empty) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + protected CIFilterGenerator (NSObjectFlag t) : base (t) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + protected internal CIFilterGenerator (NativeHandle handle) : base (handle) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public CIFilterGenerator (NSUrl aURL) + : base (NSObjectFlag.Empty) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual void ConnectObject (NSObject sourceObject, string? withSourceKey, NSObject targetObject, string targetKey) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual NSObject Copy (NSZone? zone) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public static CIFilterGenerator Create () + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual CIFilter CreateFilter () + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual void DisconnectObject (NSObject sourceObject, string sourceKey, NSObject targetObject, string targetKey) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual void EncodeTo (NSCoder encoder) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual void ExportKey (string key, NSObject targetObject, string? exportedKeyName) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual CIFilter? FilterWithName (string name) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public static CIFilterGenerator? FromUrl (NSUrl aURL) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual void RegisterFilterName (string name) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual void RemoveExportedKey (string exportedKeyName) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual bool Save (NSUrl toUrl, bool atomically) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual void SetAttributesforExportedKey (NSDictionary attributes, NSString exportedKey) + { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual NSDictionary ClassAttributes { + [Export ("classAttributes")] + get { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + [Export ("setClassAttributes:")] + set { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public virtual NSDictionary ExportedKeys { + [Export ("exportedKeys")] + get { + throw new PlatformNotSupportedException (Constants.TypeUnavailable); + } + } +} /* class CIFilterGenerator */ + +#endif // !XAMCORE_5_0 && (__IOS__ || __MACCATALYST__) diff --git a/src/coreimage.cs b/src/coreimage.cs index 96a7abb07525..4dc8b5ccde2c 100644 --- a/src/coreimage.cs +++ b/src/coreimage.cs @@ -2633,17 +2633,8 @@ interface CIFilterApply { NSString OptionColorSpace { get; } } -#if XAMCORE_5_0 [NoiOS] [NoMacCatalyst] -#else -#if __IOS__ || __MACCATALYST__ - [EditorBrowsable (EditorBrowsableState.Never)] - [Obsolete ("Do not use; this type does not exist on this platform.")] -#endif - [iOS (17, 0)] - [MacCatalyst (17, 0)] -#endif [NoTV] [BaseType (typeof (NSObject))] [DisableDefaultCtor] @@ -2744,21 +2735,18 @@ interface CIFilterGenerator : CIFilterConstructor, NSSecureCoding, NSCopying { /// To be added. /// To be added. /// To be added. - [NoiOS, NoMacCatalyst] [Field ("kCIFilterGeneratorExportedKey", "+CoreImage")] NSString ExportedKey { get; } /// To be added. /// To be added. /// To be added. - [NoiOS, NoMacCatalyst] [Field ("kCIFilterGeneratorExportedKeyTargetObject", "+CoreImage")] NSString ExportedKeyTargetObject { get; } /// To be added. /// To be added. /// To be added. - [NoiOS, NoMacCatalyst] [Field ("kCIFilterGeneratorExportedKeyName", "+CoreImage")] NSString ExportedKeyName { get; } } diff --git a/src/frameworks.sources b/src/frameworks.sources index c715fddb973f..0b7237d278a2 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -574,6 +574,7 @@ COREIMAGE_SOURCES = \ CoreImage/CIDetector.cs \ CoreImage/CIDetectorOptions.cs \ CoreImage/CIFilter.cs \ + CoreImage/CIFilterGenerator.cs \ CoreImage/CIImage.cs \ CoreImage/CISampler.cs \ CoreImage/CISystemToneMap.cs \ diff --git a/tests/introspection/ApiSelectorTest.cs b/tests/introspection/ApiSelectorTest.cs index d22c7b8829d2..98aa1316d090 100644 --- a/tests/introspection/ApiSelectorTest.cs +++ b/tests/introspection/ApiSelectorTest.cs @@ -242,15 +242,6 @@ protected virtual bool Skip (Type type, string selectorName) return true; } break; - case "CIFilterGenerator": - switch (selectorName) { - case "filterGenerator": - case "filterGeneratorWithContentsOfURL:": - if (TestRuntime.IsSimulatorOrDesktop) - return true; - break; - } - break; } // This ctors needs to be manually bound switch (type.Name) { From 72081d04b8e5bc539170731d61ea48247e3fe6a8 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 18:14:03 +0200 Subject: [PATCH 77/97] [msbuild] Fix getting an x64 simulator if asked to build for x64. (#25614) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GetAvailableDevices.cs | 7 +- .../TaskTests/GetAvailableDevicesTest.cs | 86 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs index 8046bf247001..3a21eb4081d6 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs @@ -100,8 +100,13 @@ public override bool Execute () continue; } // if we have multiple runtime identifiers, we're running in the simulator, and one is x64 and the other is arm64. + // if 'RuntimeIdentifier' is set on the task, set that value, otherwise // if we can run on arm64, then pick the arm64 simulator, otherwise pick the x64 simulator - d.Item.SetMetadata ("RuntimeIdentifier", d.RuntimeIdentifiers.Single (v => v.Contains ("arm64") == CanRunArm64)); + if (!string.IsNullOrEmpty (RuntimeIdentifier)) { + d.Item.SetMetadata ("RuntimeIdentifier", RuntimeIdentifier); + } else { + d.Item.SetMetadata ("RuntimeIdentifier", d.RuntimeIdentifiers.Single (v => v.Contains ("arm64") == CanRunArm64)); + } } DiscardedDevices = devices.Where (d => d.Discarded).Select (v => { diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs index 1d874fcb732c..c202f5e615aa 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs @@ -679,6 +679,30 @@ public void Ctl1_AppleTV () }); } + [Test] + [TestCase ("iossimulator-x64", "iossimulator-x64")] + [TestCase ("iossimulator-arm64", "iossimulator-arm64")] + [TestCase ("", null)] // null means it depends on CanRunArm64 + public void SimCtl_MultiArch_RuntimeIdentifier (string runtimeIdentifier, string? expectedRid) + { + var platform = ApplePlatform.iOS; + var task = CreateTask (platform, SIMCTL_JSON_MULTIARCH, ""); + task.RuntimeIdentifier = runtimeIdentifier; + Assert.That (task.Execute (), Is.True, "Task should have succeeded."); + Assert.Multiple (() => { + Assert.That (task.Devices.Count, Is.EqualTo (1), "Devices count mismatch."); + + Assert.That (task.Devices [0].ItemSpec, Is.EqualTo ("AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"), "Device 1 mismatch."); + Assert.That (task.Devices [0].GetMetadata ("Description"), Is.EqualTo ("iPhone 11 - iOS 26.1"), "Device 1 Name mismatch."); + Assert.That (task.Devices [0].GetMetadata ("OSVersion"), Is.EqualTo ("26.1"), "Device 1 OSVersion mismatch."); + Assert.That (task.Devices [0].GetMetadata ("UDID"), Is.EqualTo ("AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"), "Device 1 UDID mismatch."); + if (expectedRid is null) + expectedRid = GetAvailableDevices.CanRunArm64 ? "iossimulator-arm64" : "iossimulator-x64"; + Assert.That (task.Devices [0].GetMetadata ("RuntimeIdentifier"), Is.EqualTo (expectedRid), "Device 1 RuntimeIdentifier mismatch."); + Assert.That (task.Devices [0].GetMetadata ("DiscardedReason"), Is.Empty, "Device 1 discarded reason mismatch."); + }); + } + [Test] public void DeviceCtl2_Mac () { @@ -1108,5 +1132,67 @@ public void DeviceCtl2_Mac () } } """; + + const string SIMCTL_JSON_MULTIARCH = + """ + { + "devicetypes" : [ + { + "productFamily" : "iPhone", + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Profiles\/DeviceTypes\/iPhone 11.simdevicetype", + "maxRuntimeVersion" : 4294967295, + "maxRuntimeVersionString" : "65535.255.255", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11", + "modelIdentifier" : "iPhone12,1", + "minRuntimeVersionString" : "13.0.0", + "minRuntimeVersion" : 851968, + "name" : "iPhone 11" + } + ], + "runtimes" : [ + { + "isAvailable" : true, + "version" : "26.1", + "isInternal" : false, + "buildversion" : "23B80", + "supportedArchitectures" : [ + "arm64", + "x86_64" + ], + "supportedDeviceTypes" : [ + { + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Profiles\/DeviceTypes\/iPhone 11.simdevicetype", + "name" : "iPhone 11", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11", + "productFamily" : "iPhone" + } + ], + "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-26-1", + "platform" : "iOS", + "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Volumes\/iOS_23B80\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/iOS 26.1.simruntime", + "runtimeRoot" : "\/Library\/Developer\/CoreSimulator\/Volumes\/iOS_23B80\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/iOS 26.1.simruntime\/Contents\/Resources\/RuntimeRoot", + "name" : "iOS 26.1" + } + ], + "devices" : { + "com.apple.CoreSimulator.SimRuntime.iOS-26-1" : [ + { + "dataPath" : "\/Users\/rolf\/Library\/Developer\/CoreSimulator\/Devices\/AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE\/data", + "dataPathSize" : 2274861056, + "logPath" : "\/Users\/rolf\/Library\/Logs\/CoreSimulator\/AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE", + "udid" : "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE", + "isAvailable" : true, + "logPathSize" : 253952, + "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11", + "state" : "Shutdown", + "name" : "iPhone 11 - iOS 26.1" + } + ] + }, + "pairs" : { + + } + } + """; } } From 4d39e1b367409654f65ad7d47e26fa31840c980e Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Thu, 4 Jun 2026 17:45:49 -0400 Subject: [PATCH 78/97] [CI] Use ACES in CI builds (#25572) Move the CI build/test/API-diff flows to the ACES shared macOS pool while keeping PR validation on the existing PR pool for now. ## Scope This PR enables ACES for the CI entry points only: - `build-pipeline.yml` - `run-ci-api-diff.yml` - `run-post-ci-build-tests.yml` The PR entry points remain opt-out for now and can be flipped later once CI is stable: - `build-pull-request.yml` - `run-pr-api-diff.yml` - `run-post-pr-build-tests.yml` ## What changed - Added/used the `useACES` template parameter to route CI build, API diff, and simulator-test jobs to the ACES shared pool/image. - Kept the existing non-ACES pool and demand behavior when `useACES` is false. - Marked ACES simulator-test jobs with `VM_VENDOR=ACES` so tests that should not run on virtualized machines can opt out correctly. - Avoid deleting simulator runtimes on ACES, since that operation is not supported/reliable there. - Wait longer for non-x64 simulator cleanup to complete before continuing. - Skip x64 simulator test runs on ACES machines. - Use the Xcode selected by `xcode-select` for build configuration and pre-configure provisioning, so ACES agents with `/Applications/Xcode_26.5.app` do not require symlinks or the traditional `/Applications/Xcode_26.5.0.app` bundle name. - Harden the `System.Net.Http` monotouch tests against transient CI/httpbin network timeouts so network stalls do not hang the entire monotouch app until the harness timeout. ## Related Xcode path support This branch includes the configure support from #25622. The CI templates use the selected Xcode developer root so both classic bots and ACES images can configure/build with the Xcode that is actually installed on the agent. ## Validation notes The original green build links in this PR description became stale as the branch evolved. Recent follow-up failures were investigated and resulted in the Xcode selection and network-timeout fixes included here. The intended validation matrix is: - CI Build on ACES - CI API diff on ACES - Post-CI simulator tests on ACES - PR pipelines still using the existing PR pool --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Rolf Bjarne Kvinge --- system-dependencies.sh | 31 ++++++++++++-- tests/common/TestRuntime.cs | 23 ++++++++++- .../linker/link sdk/LinkSdkRegressionTest.cs | 19 +++++++-- .../AudioToolbox/AudioQueueTest.cs | 2 + tests/monotouch-test/Metal/MTLDeviceTests.cs | 2 + .../System.Net.Http/MessageHandlers.cs | 32 ++++++++++----- .../System.Net.Http/NetworkResources.cs | 1 + .../VideoToolbox/VTCompressionSessionTests.cs | 3 ++ .../VTMotionEstimationSessionTest.cs | 6 +++ tests/xharness/AppRunner.cs | 7 ++++ .../xharness/Jenkins/TestVariationsFactory.cs | 9 +++-- tools/devops/automation/build-pipeline.yml | 1 + .../devops/automation/build-pull-request.yml | 1 + tools/devops/automation/run-ci-api-diff.yml | 1 + .../automation/run-post-ci-build-tests.yml | 1 + .../automation/run-post-pr-build-tests.yml | 1 + tools/devops/automation/run-pr-api-diff.yml | 1 + .../automation/templates/api-diff-stage.yml | 4 ++ .../templates/build/api-diff-stage.yml | 24 +++++++---- .../templates/build/build-mac-tests-stage.yml | 27 +++++++++---- .../templates/build/build-stage.yml | 27 +++++++++---- .../automation/templates/build/build.yml | 35 +++++++++++++++- .../automation/templates/main-stage.yml | 5 +++ .../templates/pipelines/api-diff-pipeline.yml | 5 +++ .../pipelines/run-tests-pipeline.yml | 6 +++ .../automation/templates/tests-stage.yml | 6 +++ .../automation/templates/tests/build.yml | 8 +++- .../automation/templates/tests/stage.yml | 38 +++++++++++++----- .../automation/templates/variables/common.yml | 40 +++++++++++++++++++ 29 files changed, 306 insertions(+), 60 deletions(-) diff --git a/system-dependencies.sh b/system-dependencies.sh index 912f671b6c90..9393d788eec7 100755 --- a/system-dependencies.sh +++ b/system-dependencies.sh @@ -365,6 +365,20 @@ function get_non_universal_simulator_runtimes () rm -f "$TMPFILE" } +function print_non_universal_simulator_runtimes () +{ + local TMPFILE + TMPFILE=$(mktemp) + + xcrun simctl runtime list -j --json-output="$TMPFILE" + + # this json query filters the json to simulator runtimes where iOS/tvOS >= 26.0 and where x64 is *not* supported (which we need to run x64 apps in the simulator on arm64) + JQ_QUERY='map({platformIdentifier: .platformIdentifier, identifier: .identifier, version: .version, state: .state, supportedArchitectures: .supportedArchitectures | join("|"), majorVersion: .version | split(".")[0] | tonumber }) | map(select(.majorVersion>=26) ) | map(select(.supportedArchitectures | contains("x86_64") | not))' + jq "$JQ_QUERY" -r "$TMPFILE" + + rm -f "$TMPFILE" +} + function xcodebuild_download_selected_platforms () { local XCODE_DEVELOPER_ROOT @@ -422,8 +436,10 @@ function xcodebuild_download_selected_platforms () log "Looking for iOS/tvOS 26+ simulator runtimes that don't support x64..." get_non_universal_simulator_runtimes - if [[ "$SIMULATORS_WITHOUT_X64_COUNT" -gt 0 ]]; then - log "Found ${SIMULATORS_WITHOUT_X64_COUNT} simulator runtimes that don't support x64, which will now be deleted: ${SIMULATORS_WITHOUT_X64[@]}" + if [[ "$SIMULATORS_WITHOUT_X64_COUNT" -gt 0 && "$ACES" == "1" ]]; then + log "Found ${SIMULATORS_WITHOUT_X64_COUNT} simulator runtimes that don't support x64, but we're running on ACES, so we can't do anything about that." + elif [[ "$SIMULATORS_WITHOUT_X64_COUNT" -gt 0 ]]; then + log "Found ${SIMULATORS_WITHOUT_X64_COUNT} simulator runtimes that don't support x64, which will now be deleted: ${SIMULATORS_WITHOUT_X64[*]}" for sim in "${SIMULATORS_WITHOUT_X64[@]}"; do log "Executing 'xcrun simctl runtime delete $sim'" xcrun simctl runtime delete "$sim" @@ -431,17 +447,24 @@ function xcodebuild_download_selected_platforms () # sadly simulator deletion is done asynchronously, so we have to wait until they're all gone log "Waiting for the simulators to be deleted..." printf " " - for i in $(seq 1 60); do + for i in $(seq 1 300); do sleep 1 get_non_universal_simulator_runtimes if [[ "$SIMULATORS_WITHOUT_X64_COUNT" == "0" ]]; then break fi + # every 60 seconds print the simulators left to delete + if [[ $(( i % 60)) == 0 ]]; then + printf "\n" + printf " Simulators left to delete:\n" + print_non_universal_simulator_runtimes | sed 's/^/ /' + printf " " + fi printf "$SIMULATORS_WITHOUT_X64_COUNT" done printf "\n" if [[ "$SIMULATORS_WITHOUT_X64_COUNT" != "0" ]]; then - warn "Waited for 60 seconds, but there are still $SIMULATORS_WITHOUT_X64_COUNT simulators waiting to deleted." + warn "Waited for 5 minutes, but there are still $SIMULATORS_WITHOUT_X64_COUNT simulators waiting to deleted." fi else log "All installed iOS/tvOS 26+ simulators support x64" diff --git a/tests/common/TestRuntime.cs b/tests/common/TestRuntime.cs index 296c348ac2cf..f7a8394faf62 100644 --- a/tests/common/TestRuntime.cs +++ b/tests/common/TestRuntime.cs @@ -299,12 +299,10 @@ public static void AssertSimulatorOrDesktop (string message = "This test only wo public static void AssertNotVirtualMachine () { -#if MONOMAC || __MACCATALYST__ // enviroment variable set by the CI when running on a VM var vmVendor = Environment.GetEnvironmentVariable ("VM_VENDOR"); if (!string.IsNullOrEmpty (vmVendor)) NUnit.Framework.Assert.Ignore ($"This test only runs on device. Found vm vendor: {vmVendor}"); -#endif } public static bool IsVSTS => @@ -1625,6 +1623,7 @@ public static void IgnoreInCIIfBadNetwork (Exception? ex) IgnoreInCIIfDnsResolutionFailed (ex); IgnoreInCIIfSshConnectionError (ex); IgnoreInCIIfTimedOut (ex); + IgnoreInCIIfHttpClientTimedOut (ex); } public static void IgnoreInCIIfBadNetwork (NSError? error) @@ -1666,6 +1665,26 @@ public static void IgnoreInCIIfTimedOut (NSError error) IgnoreNetworkError (error, CFNetworkErrors.TimedOut); } + public static void IgnoreInCIIfHttpClientTimedOut () + { + IgnoreInCI ("Ignored due to HTTP client timeout."); + } + + public static void IgnoreInCIIfHttpClientTimedOut (Exception? ex) + { + if (ex is null) + return; + + var tce = FindInner (ex); + if (tce is null) + return; + + if (FindInner (tce) is not null || + tce.Message.Contains ("HttpClient.Timeout", StringComparison.Ordinal)) { + IgnoreInCI ($"Ignored due to HTTP client timeout: {tce.Message}"); + } + } + public static void IgnoreInCIIfTimedOut (Exception ex) { if (ex is WebException wex) { diff --git a/tests/linker/link sdk/LinkSdkRegressionTest.cs b/tests/linker/link sdk/LinkSdkRegressionTest.cs index b75d223ed24a..0f147186f702 100644 --- a/tests/linker/link sdk/LinkSdkRegressionTest.cs +++ b/tests/linker/link sdk/LinkSdkRegressionTest.cs @@ -789,11 +789,11 @@ void SpecialFolderImpl () #else var myExists = false; #endif - path = TestFolder (Environment.SpecialFolder.MyMusic, exists: myExists); + path = TestFolderIfAvailableInCI (Environment.SpecialFolder.MyMusic, myExists); - path = TestFolder (Environment.SpecialFolder.MyVideos, exists: myExists); + path = TestFolderIfAvailableInCI (Environment.SpecialFolder.MyVideos, myExists); - path = TestFolder (Environment.SpecialFolder.DesktopDirectory, exists: myExists); + path = TestFolderIfAvailableInCI (Environment.SpecialFolder.DesktopDirectory, myExists); #if __TVOS__ path = TestFolder (Environment.SpecialFolder.Fonts, exists: null, supported: true); @@ -811,7 +811,7 @@ void SpecialFolderImpl () path = TestFolder (Environment.SpecialFolder.Templates, exists: false); #endif - path = TestFolder (Environment.SpecialFolder.MyPictures, exists: myExists); + path = TestFolderIfAvailableInCI (Environment.SpecialFolder.MyPictures, myExists); #if __MACOS__ path = TestFolder (Environment.SpecialFolder.CommonTemplates, supported: false); @@ -897,6 +897,17 @@ void SpecialFolderImpl () path = TestFolder (Environment.SpecialFolder.Resources, readOnly: tvos && device); Assert.That (path.EndsWith ("/Library", StringComparison.Ordinal), Is.True, "Resources"); #endif + // Some CI VM images don't initialize all standard user directories, so keep this + // tolerance limited to CI VMs and preserve the stricter check for other runs. + string TestFolderIfAvailableInCI (Environment.SpecialFolder folder, bool exists) + { +#if __MACOS__ + var path = Environment.GetFolderPath (folder); + if (string.IsNullOrEmpty (path) && TestRuntime.IsInCI && TestRuntime.IsVM) + return path; +#endif + return TestFolder (folder, exists: exists); + } } #if !__MACOS__ diff --git a/tests/monotouch-test/AudioToolbox/AudioQueueTest.cs b/tests/monotouch-test/AudioToolbox/AudioQueueTest.cs index a5249b42ea58..abe7b02f3266 100644 --- a/tests/monotouch-test/AudioToolbox/AudioQueueTest.cs +++ b/tests/monotouch-test/AudioToolbox/AudioQueueTest.cs @@ -30,6 +30,8 @@ public void Properties () [Test] public void ChannelAssignments () { + TestRuntime.AssertNotVirtualMachine (); + var aq = new OutputAudioQueue (AudioStreamBasicDescription.CreateLinearPCM ()); var route = global::AVFoundation.AVAudioSession.SharedInstance ().CurrentRoute; diff --git a/tests/monotouch-test/Metal/MTLDeviceTests.cs b/tests/monotouch-test/Metal/MTLDeviceTests.cs index 2a6a38b51134..948737e2749b 100644 --- a/tests/monotouch-test/Metal/MTLDeviceTests.cs +++ b/tests/monotouch-test/Metal/MTLDeviceTests.cs @@ -86,6 +86,8 @@ public void ReturnReleaseTest () if (device is null) Assert.Inconclusive ("Metal is not supported"); + TestRuntime.AssertNotVirtualMachine (); + // Apple claims that "Indirect command buffers" are available with MTLGPUFamilyCommon2, but it crashes on at least one machine. // Log what the current device supports, just to have it in the log. foreach (MTLFeatureSet fs in Enum.GetValues ()) { diff --git a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs index 6bf154dc0934..2811dd23ee2c 100644 --- a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs +++ b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs @@ -454,12 +454,15 @@ public void RedirectionWithAuthorizationHeaders (Type handlerType) bool containsHeaders = false; string json = ""; + using var handler = GetHandler (handlerType); var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { - HttpClient client = new HttpClient (GetHandler (handlerType)); + using var client = new HttpClient (handler, disposeHandler: false) { + Timeout = TimeSpan.FromSeconds (20), + }; client.BaseAddress = NetworkResources.Httpbin.Uri; var byteArray = new UTF8Encoding ().GetBytes ("username:password"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue ("Basic", Convert.ToBase64String (byteArray)); - var result = await client.GetAsync (NetworkResources.Httpbin.GetRedirectUrl (3)); + using var result = await client.GetAsync (NetworkResources.Httpbin.GetRedirectUrl (3)); // get the data returned from httpbin which contains the details of the requested performed. json = await result.Content.ReadAsStringAsync (); containsAuthorizarion = json.Contains ("Authorization"); @@ -467,12 +470,15 @@ public void RedirectionWithAuthorizationHeaders (Type handlerType) }, out var ex); if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail + TestRuntime.IgnoreInCIIfHttpClientTimedOut (); Assert.Inconclusive ("Request timedout."); + } else if (ex is not null) { + TestRuntime.IgnoreInCIIfBadNetwork (ex); + Assert.That (ex, Is.Null, $"Exception {ex} for {json}"); } else if (!containsHeaders) { Assert.Inconclusive ("Response from httpbin does not contain headers, therefore we cannot ensure that if the authoriation is present."); } else { Assert.That (containsAuthorizarion, Is.False, $"Authorization header did reach the final destination. {json}"); - Assert.That (ex, Is.Null, $"Exception {ex} for {json}"); } } @@ -1357,18 +1363,22 @@ public void UpdateRequestUriAfterRedirect (Type handlerType) { // https://github.com/dotnet/macios/issues/20629 + using var handler = GetHandler (handlerType); var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { - var client = new HttpClient (GetHandler (handlerType)); + using var client = new HttpClient (handler, disposeHandler: false) { + Timeout = TimeSpan.FromSeconds (20), + }; var postRequestUri = NetworkResources.Httpbin.Url + "/"; var initialRequestUri = NetworkResources.Httpbin.GetRedirectToUrl (postRequestUri); - var request = new HttpRequestMessage (HttpMethod.Get, initialRequestUri); + using var request = new HttpRequestMessage (HttpMethod.Get, initialRequestUri); Assert.That (request.RequestUri.ToString (), Is.EqualTo (initialRequestUri), "Initial RequestUri"); - var response = await client.SendAsync (request); + using var response = await client.SendAsync (request); TestRuntime.IgnoreInCIIfBadNetwork (response.StatusCode); Assert.That (request.RequestUri.ToString (), Is.EqualTo (postRequestUri), "Post RequestUri"); }, out var ex); if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc. we do not want to fail + TestRuntime.IgnoreInCIIfHttpClientTimedOut (); Assert.Inconclusive ("Request timedout."); } else { TestRuntime.IgnoreInCIIfBadNetwork (ex); @@ -1382,17 +1392,21 @@ public void RequestUriNotUpdatedIfNotRedirect (Type handlerType) { // https://github.com/dotnet/macios/issues/20629 + using var handler = GetHandler (handlerType); var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { - var client = new HttpClient (GetHandler (handlerType)); + using var client = new HttpClient (handler, disposeHandler: false) { + Timeout = TimeSpan.FromSeconds (20), + }; var requestUri = NetworkResources.Httpbin.Uri + "?stuffHere=[]{}"; - var request = new HttpRequestMessage (HttpMethod.Get, requestUri); + using var request = new HttpRequestMessage (HttpMethod.Get, requestUri); Assert.That (request.RequestUri.ToString (), Is.EqualTo (requestUri), "Initial RequestUri"); - var response = await client.SendAsync (request); + using var response = await client.SendAsync (request); TestRuntime.IgnoreInCIIfBadNetwork (response.StatusCode); Assert.That (request.RequestUri.ToString (), Is.EqualTo (requestUri), "Post RequestUri"); }, out var ex); if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc. we do not want to fail + TestRuntime.IgnoreInCIIfHttpClientTimedOut (); Assert.Inconclusive ("Request timedout."); } else { TestRuntime.IgnoreInCIIfBadNetwork (ex); diff --git a/tests/monotouch-test/System.Net.Http/NetworkResources.cs b/tests/monotouch-test/System.Net.Http/NetworkResources.cs index 6fa21fe58507..2a887a52a3d6 100644 --- a/tests/monotouch-test/System.Net.Http/NetworkResources.cs +++ b/tests/monotouch-test/System.Net.Http/NetworkResources.cs @@ -10,6 +10,7 @@ public static class NetworkResources { public static string MicrosoftUrl => AssertNetworkConnection ("https://www.microsoft.com"); public static Uri MicrosoftUri => new Uri (MicrosoftUrl); public static string MicrosoftHttpUrl => AssertNetworkConnection ("http://www.microsoft.com"); + public static Uri MicrosoftHttpUri => new Uri (MicrosoftHttpUrl); public static string XamarinUrl => AssertNetworkConnection ("https://dotnet.microsoft.com/apps/xamarin"); public static string XamarinHttpUrl => AssertNetworkConnection ("http://dotnet.microsoft.com/apps/xamarin"); public static Uri XamarinUri => new Uri (XamarinUrl); diff --git a/tests/monotouch-test/VideoToolbox/VTCompressionSessionTests.cs b/tests/monotouch-test/VideoToolbox/VTCompressionSessionTests.cs index d72f372a878a..0c5a4f57af5f 100644 --- a/tests/monotouch-test/VideoToolbox/VTCompressionSessionTests.cs +++ b/tests/monotouch-test/VideoToolbox/VTCompressionSessionTests.cs @@ -236,6 +236,9 @@ public void TestCallbackBackground (bool stronglyTyped) public void TestMultiImage (bool stronglyTyped, bool customCallback) { TestRuntime.AssertXcodeVersion (26, 0); +#if __MACOS__ || __MACCATALYST__ + TestRuntime.AssertNotVirtualMachine (); +#endif if (!VTCompressionSession.IsStereoMvHevcEncodeSupported ()) Assert.Ignore ("Stereo MV-HEVC encoding is not supported on the current system."); diff --git a/tests/monotouch-test/VideoToolbox/VTMotionEstimationSessionTest.cs b/tests/monotouch-test/VideoToolbox/VTMotionEstimationSessionTest.cs index a8f49139e6ff..9e338d9d6ef9 100644 --- a/tests/monotouch-test/VideoToolbox/VTMotionEstimationSessionTest.cs +++ b/tests/monotouch-test/VideoToolbox/VTMotionEstimationSessionTest.cs @@ -24,6 +24,9 @@ public class VTMotionEstimationSessionTest { public void CreateTest () { TestRuntime.AssertXcodeVersion (26, 0); +#if __MACOS__ || __MACCATALYST__ + TestRuntime.AssertNotVirtualMachine (); +#endif // VTMotionEstimationSessionCreate just returns in the simulator (a single 'ret' instruction), // which means it returns with status=VTStatus.Ok, but no session actually created. So ignore // this test in the simulator. @@ -49,6 +52,9 @@ public void CreateTest () public void CreateStronglyTypedTest () { TestRuntime.AssertXcodeVersion (26, 0); +#if __MACOS__ || __MACCATALYST__ + TestRuntime.AssertNotVirtualMachine (); +#endif // VTMotionEstimationSessionCreate just returns in the simulator (a single 'ret' instruction), // which means it returns with status=VTStatus.Ok, but no session actually created. So ignore // this test in the simulator. diff --git a/tests/xharness/AppRunner.cs b/tests/xharness/AppRunner.cs index 4cc1a11b2690..8f79f259d1ae 100644 --- a/tests/xharness/AppRunner.cs +++ b/tests/xharness/AppRunner.cs @@ -225,6 +225,13 @@ public async Task RunAsync () if (harness.InCI) { // We use the 'BUILD_REVISION' variable to detect whether we're running CI or not. args.Add (new SetEnvVariableArgument ("BUILD_REVISION", Environment.GetEnvironmentVariable ("BUILD_REVISION"))); + + // Forward VM_VENDOR (set by the pipeline when running on a VM-backed + // pool such as ACES) so TestRuntime.AssertNotVirtualMachine works + // inside the iOS/tvOS simulator process. + var vmVendor = Environment.GetEnvironmentVariable ("VM_VENDOR"); + if (!string.IsNullOrEmpty (vmVendor)) + args.Add (new SetEnvVariableArgument ("VM_VENDOR", vmVendor)); } if (!harness.GetIncludeSystemPermissionTests (TestPlatform.iOS, !isSimulator)) diff --git a/tests/xharness/Jenkins/TestVariationsFactory.cs b/tests/xharness/Jenkins/TestVariationsFactory.cs index 00c05d4882c6..167d8f6247bb 100644 --- a/tests/xharness/Jenkins/TestVariationsFactory.cs +++ b/tests/xharness/Jenkins/TestVariationsFactory.cs @@ -33,6 +33,7 @@ IEnumerable GetTestData (RunTestTask test) var arm64_sim_runtime_identifier = string.Empty; var x64_sim_runtime_identifier = string.Empty; var supports_coreclr = test.Platform == TestPlatform.Mac || jenkins.Harness.DotNetVersion.Major >= 11; + var supports_x64 = string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("ACES")); // x64 is not supported on ACES machines switch (test.Platform) { case TestPlatform.Mac: @@ -111,8 +112,8 @@ IEnumerable GetTestData (RunTestTask test) yield return new TestData { Variation = "Release (managed static registrar, all optimizations)", TestVariation = "release|managed-static-registrar-all-optimizations-linkall", Ignored = ignore }; if (supports_coreclr) yield return new TestData { Variation = "Release (trimmable static registrar, all optimizations)", TestVariation = "trimmable-static-registrar-all-optimizations-linkall", Ignored = ignore }; - yield return new TestData { Variation = "Release (NativeAOT, x64)", TestVariation = "release|nativeaot", Ignored = ignore, RuntimeIdentifier = x64_sim_runtime_identifier }; - yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT, x64)", TestVariation = "trimmable-static-registrar|release|nativeaot", Ignored = ignore, RuntimeIdentifier = x64_sim_runtime_identifier }; + yield return new TestData { Variation = "Release (NativeAOT, x64)", TestVariation = "release|nativeaot", Ignored = !supports_x64 ? true : ignore, RuntimeIdentifier = x64_sim_runtime_identifier }; + yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT, x64)", TestVariation = "trimmable-static-registrar|release|nativeaot", Ignored = !supports_x64 ? true : ignore, RuntimeIdentifier = x64_sim_runtime_identifier }; if (supports_interpreter) { yield return new TestData { Variation = "Debug (interpreter)", TestVariation = "interpreter", Ignored = ignore }; yield return new TestData { Variation = "Release (interpreter)", TestVariation = "release|interpreter", Ignored = ignore }; @@ -157,12 +158,12 @@ IEnumerable GetTestData (RunTestTask test) yield return new TestData { Variation = "Release (trimmable static registrar, all optimizations)", TestVariation = "trimmable-static-registrar-all-optimizations-linkall", Ignored = ignore }; yield return new TestData { Variation = "Release (NativeAOT)", TestVariation = "release|nativeaot", Ignored = ignore }; yield return new TestData { Variation = "Release (NativeAOT, ARM64)", TestVariation = "release|nativeaot", Ignored = !mac_supports_arm64 ? true : ignore, RuntimeIdentifier = arm64_runtime_identifier }; - yield return new TestData { Variation = "Release (NativeAOT, x64)", TestVariation = "release|nativeaot", Ignored = ignore, RuntimeIdentifier = x64_runtime_identifier }; + yield return new TestData { Variation = "Release (NativeAOT, x64)", TestVariation = "release|nativeaot", Ignored = !supports_x64 ? true : ignore, RuntimeIdentifier = x64_runtime_identifier }; if (mac_supports_arm64) yield return new TestData { Variation = $"Release (NativeAOT, .NET 11 defaults)", TestVariation = "release|nativeaot-net11-defaults", Ignored = ignore }; yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT)", TestVariation = "trimmable-static-registrar|nativeaot|release", Ignored = ignore }; yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT, ARM64)", TestVariation = "trimmable-static-registrar|nativeaot|release", Ignored = !mac_supports_arm64 ? true : ignore, RuntimeIdentifier = arm64_runtime_identifier }; - yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT, x64)", TestVariation = "trimmable-static-registrar|nativeaot|release", Ignored = ignore, RuntimeIdentifier = x64_runtime_identifier }; + yield return new TestData { Variation = "Release (trimmable static registrar, NativeAOT, x64)", TestVariation = "trimmable-static-registrar|nativeaot|release", Ignored = !supports_x64 ? true : ignore, RuntimeIdentifier = x64_runtime_identifier }; yield return new TestData { Variation = "Release (static registrar)", TestVariation = "release|static-registrar", Ignored = ignore }; yield return new TestData { Variation = "Release (static registrar, all optimizations)", TestVariation = "release|static-registrar-all-optimizations-linkall", Ignored = ignore }; if (test.Platform == TestPlatform.MacCatalyst) { diff --git a/tools/devops/automation/build-pipeline.yml b/tools/devops/automation/build-pipeline.yml index 97824c8abe3b..09f588d9d0c7 100644 --- a/tools/devops/automation/build-pipeline.yml +++ b/tools/devops/automation/build-pipeline.yml @@ -132,6 +132,7 @@ extends: forceInsertion: ${{ parameters.forceInsertion }} pushNugets: ${{ parameters.pushNugets }} pushNugetsToMaestro: ${{ parameters.pushNugetsToMaestro }} + useACES: true # flip to false to opt CI out of ACES; see templates/variables/common.yml - template: localization/v1.yml@yaml-templates parameters: diff --git a/tools/devops/automation/build-pull-request.yml b/tools/devops/automation/build-pull-request.yml index 8d71f0ad7231..cc30c3acf9a0 100644 --- a/tools/devops/automation/build-pull-request.yml +++ b/tools/devops/automation/build-pull-request.yml @@ -113,3 +113,4 @@ extends: forceInsertion: ${{ parameters.forceInsertion }} pushNugets: false pushNugetsToMaestro: false + useACES: false # flip to true to opt PR into ACES; see templates/variables/common.yml diff --git a/tools/devops/automation/run-ci-api-diff.yml b/tools/devops/automation/run-ci-api-diff.yml index 8ccd511ab0c3..caf428a30870 100644 --- a/tools/devops/automation/run-ci-api-diff.yml +++ b/tools/devops/automation/run-ci-api-diff.yml @@ -17,3 +17,4 @@ extends: parameters: isPR: false pool: $(CIBuildPool) + useACES: true # flip to false to opt CI api-diff out of ACES; see templates/variables/common.yml diff --git a/tools/devops/automation/run-post-ci-build-tests.yml b/tools/devops/automation/run-post-ci-build-tests.yml index af8677d8b4e6..f09d70d3e7ba 100644 --- a/tools/devops/automation/run-post-ci-build-tests.yml +++ b/tools/devops/automation/run-post-ci-build-tests.yml @@ -17,3 +17,4 @@ extends: template: templates/pipelines/run-tests-pipeline.yml parameters: isPR: false + useACES: true # flip to false to opt CI simulator tests out of ACES; see templates/variables/common.yml diff --git a/tools/devops/automation/run-post-pr-build-tests.yml b/tools/devops/automation/run-post-pr-build-tests.yml index b83f2d8e95c3..81be17692d32 100644 --- a/tools/devops/automation/run-post-pr-build-tests.yml +++ b/tools/devops/automation/run-post-pr-build-tests.yml @@ -27,3 +27,4 @@ extends: parameters: isPR: true buildPackages: true + useACES: false # flip to true to opt PR simulator tests into ACES; see templates/variables/common.yml diff --git a/tools/devops/automation/run-pr-api-diff.yml b/tools/devops/automation/run-pr-api-diff.yml index 07401bdf077c..03f693b4b5ea 100644 --- a/tools/devops/automation/run-pr-api-diff.yml +++ b/tools/devops/automation/run-pr-api-diff.yml @@ -26,3 +26,4 @@ extends: parameters: isPR: true pool: $(PRBuildPool) + useACES: false # flip to true to opt PR api-diff into ACES; see templates/variables/common.yml diff --git a/tools/devops/automation/templates/api-diff-stage.yml b/tools/devops/automation/templates/api-diff-stage.yml index a81ac79c8bda..e13e9957a7c5 100644 --- a/tools/devops/automation/templates/api-diff-stage.yml +++ b/tools/devops/automation/templates/api-diff-stage.yml @@ -24,6 +24,9 @@ parameters: - name: macOSName type: string +- name: useACES + type: boolean + default: false stages: @@ -69,3 +72,4 @@ stages: gitHubToken: $(Github.Token) xqaCertPass: $(xqa--certificates--password) pool: ${{ parameters.pool }} + useACES: ${{ parameters.useACES }} diff --git a/tools/devops/automation/templates/build/api-diff-stage.yml b/tools/devops/automation/templates/build/api-diff-stage.yml index 03cc9d7f34d9..edac9d79ca91 100644 --- a/tools/devops/automation/templates/build/api-diff-stage.yml +++ b/tools/devops/automation/templates/build/api-diff-stage.yml @@ -34,6 +34,10 @@ parameters: - name: macOSName type: string +- name: useACES + type: boolean + default: false + jobs: # Detect changes - job: api_diff @@ -44,13 +48,19 @@ jobs: # set the branch variable name, this is required by jenkins and we have a lot of scripts that depend on it BRANCH_NAME: $[ replace(variables['Build.SourceBranch'], 'refs/heads/', '') ] XHARNESS_LABELS: $[ stageDependencies.configure_build.configure.outputs['labels.xharness_labels'] ] - pool: - name: ${{ parameters.pool }} - demands: - - Agent.OS -equals Darwin - - Agent.OSVersion -gtVersion $(minimumMacOSVersion) - - macOS.Name -equals ${{ parameters.macOSName }} - - XcodeChannel -equals ${{ parameters.xcodeChannel }} + ${{ if parameters.useACES }}: + pool: + name: $(CIBuildPoolACES) + demands: + - ImageOverride -equals $(CIBuildPoolACESImage) + ${{ else }}: + pool: + name: ${{ parameters.pool }} + demands: + - Agent.OS -equals Darwin + - Agent.OSVersion -gtVersion $(minimumMacOSVersion) + - macOS.Name -equals ${{ parameters.macOSName }} + - XcodeChannel -equals ${{ parameters.xcodeChannel }} workspace: clean: all diff --git a/tools/devops/automation/templates/build/build-mac-tests-stage.yml b/tools/devops/automation/templates/build/build-mac-tests-stage.yml index 0622c8741b5d..be7ea2efb3b1 100644 --- a/tools/devops/automation/templates/build/build-mac-tests-stage.yml +++ b/tools/devops/automation/templates/build/build-mac-tests-stage.yml @@ -34,6 +34,10 @@ parameters: - name: macOSName type: string +- name: useACES + type: boolean + default: false + jobs: # This job builds the macOS tests. @@ -49,14 +53,21 @@ jobs: BRANCH_NAME: $[ replace(variables['Build.SourceBranch'], 'refs/heads/', '') ] RUN_MAC_TESTS: $[ stageDependencies.configure_build.configure.outputs['decisions.RUN_MAC_TESTS'] ] condition: ne(stageDependencies.configure_build.configure.outputs['decisions.RUN_MAC_TESTS'],'') - pool: - os: macOS - name: ${{ parameters.pool }} - demands: - - Agent.OS -equals Darwin - - Agent.OSVersion -gtVersion $(minimumMacOSVersion) - - macOS.Name -equals ${{ parameters.macOSName }} - - XcodeChannel -equals ${{ parameters.xcodeChannel }} + ${{ if parameters.useACES }}: + pool: + os: macOS + name: $(CIBuildPoolACES) + demands: + - ImageOverride -equals $(CIBuildPoolACESImage) + ${{ else }}: + pool: + os: macOS + name: ${{ parameters.pool }} + demands: + - Agent.OS -equals Darwin + - Agent.OSVersion -gtVersion $(minimumMacOSVersion) + - macOS.Name -equals ${{ parameters.macOSName }} + - XcodeChannel -equals ${{ parameters.xcodeChannel }} steps: - template: build-mac-tests.yml diff --git a/tools/devops/automation/templates/build/build-stage.yml b/tools/devops/automation/templates/build/build-stage.yml index c03d3abe9a37..3bd8bc1f799b 100644 --- a/tools/devops/automation/templates/build/build-stage.yml +++ b/tools/devops/automation/templates/build/build-stage.yml @@ -38,6 +38,10 @@ parameters: - name: use1ES type: boolean + - name: useACES + type: boolean + default: false + jobs: # This job performs the build of the nuget pkgs and the framework pkgs. There are two interesting dependencies in this job: - job: build @@ -66,14 +70,21 @@ jobs: BRANCH_NAME: $[ replace(variables['Build.SourceBranch'], 'refs/heads/', '') ] XHARNESS_LABELS: $[ stageDependencies.configure_build.configure.outputs['labels.xharness_labels'] ] RUN_MAC_TESTS: $[ stageDependencies.configure_build.configure.outputs['decisions.RUN_MAC_TESTS'] ] - pool: - os: macOS - name: ${{ parameters.pool }} - demands: - - Agent.OS -equals Darwin - - Agent.OSVersion -gtVersion $(minimumMacOSVersion) - - macOS.Name -equals ${{ parameters.macOSName }} - - XcodeChannel -equals ${{ parameters.xcodeChannel }} + ${{ if parameters.useACES }}: + pool: + os: macOS + name: $(CIBuildPoolACES) + demands: + - ImageOverride -equals $(CIBuildPoolACESImage) + ${{ else }}: + pool: + os: macOS + name: ${{ parameters.pool }} + demands: + - Agent.OS -equals Darwin + - Agent.OSVersion -gtVersion $(minimumMacOSVersion) + - macOS.Name -equals ${{ parameters.macOSName }} + - XcodeChannel -equals ${{ parameters.xcodeChannel }} steps: - template: build-pkgs.yml diff --git a/tools/devops/automation/templates/build/build.yml b/tools/devops/automation/templates/build/build.yml index ae746a2d4776..1eff352e19b6 100644 --- a/tools/devops/automation/templates/build/build.yml +++ b/tools/devops/automation/templates/build/build.yml @@ -82,6 +82,17 @@ steps: HostedMacKeychainPassword: ${{ parameters.keyringPass }} - bash: | + set -eo pipefail + set -x + + XCODE_PATH=$(xcode-select -p 2>/dev/null || true) + if [[ "$XCODE_PATH" != */Contents/Developer || ! -d "$XCODE_PATH" ]]; then + echo "xcode-select does not point to an Xcode developer directory: '$XCODE_PATH'" + exit 1 + fi + export XCODE_DEVELOPER_ROOT="$XCODE_PATH" + export DEVELOPER_DIR="$XCODE_PATH" + make -C $(Build.SourcesDirectory)/$(BUILD_REPOSITORY_TITLE)/tools/devops provisioning displayName: 'Generate provisionator files.' @@ -93,8 +104,18 @@ steps: retryCount: ${{ parameters.retryCount }} # mono does give issues sometimes to download, we will retry - bash: | + set -eo pipefail set -x - set -e + + XCODE_PATH=$(xcode-select -p 2>/dev/null || true) + if [[ "$XCODE_PATH" != */Contents/Developer || ! -d "$XCODE_PATH" ]]; then + echo "xcode-select does not point to an Xcode developer directory: '$XCODE_PATH'" + exit 1 + fi + export XCODE_DEVELOPER_ROOT="$XCODE_PATH" + export DEVELOPER_DIR="$XCODE_PATH" + xcrun -k + ARGS=() ARGS+=(--ignore-all) ARGS+=(--provision-xcode-components) @@ -135,7 +156,17 @@ steps: displayName: "Configure install extra args" timeoutInMinutes: 5 - - bash: $(System.DefaultWorkingDirectory)/$(BUILD_REPOSITORY_TITLE)/configure + - bash: | + set -eo pipefail + set -x + + ARGS=() + XCODE_PATH=$(xcode-select -p 2>/dev/null || true) + if [[ -n "$XCODE_PATH" && -d "$XCODE_PATH" ]]; then + ARGS+=("--xcode=$XCODE_PATH") + fi + + ./configure "${ARGS[@]}" env: ${{ if eq(parameters.isPR, true) }}: IsPR: 'True' diff --git a/tools/devops/automation/templates/main-stage.yml b/tools/devops/automation/templates/main-stage.yml index c06cfb6e6828..671be873a5d1 100644 --- a/tools/devops/automation/templates/main-stage.yml +++ b/tools/devops/automation/templates/main-stage.yml @@ -56,6 +56,10 @@ parameters: type: string default: '' + - name: useACES + type: boolean + default: false + stages: - stage: configure_build @@ -103,6 +107,7 @@ stages: xqaCertPass: $(xqa--certificates--password) pool: ${{ parameters.pool }} use1ES: true + useACES: ${{ parameters.useACES }} # .NET Release Prep and VS Insertion Stages, only execute them when the build comes from an official branch and is not a schedule build from OneLoc # setting the stage at this level makes the graph of the UI look better, else the lines overlap and is not clear. diff --git a/tools/devops/automation/templates/pipelines/api-diff-pipeline.yml b/tools/devops/automation/templates/pipelines/api-diff-pipeline.yml index 26bb8c290581..f8fe235e2bb9 100644 --- a/tools/devops/automation/templates/pipelines/api-diff-pipeline.yml +++ b/tools/devops/automation/templates/pipelines/api-diff-pipeline.yml @@ -22,6 +22,10 @@ parameters: type: boolean default: false +- name: useACES + type: boolean + default: false + resources: repositories: - repository: self @@ -49,3 +53,4 @@ stages: isPR: ${{ parameters.isPR }} provisionatorChannel: ${{ parameters.provisionatorChannel }} pool: ${{ parameters.pool }} + useACES: ${{ parameters.useACES }} diff --git a/tools/devops/automation/templates/pipelines/run-tests-pipeline.yml b/tools/devops/automation/templates/pipelines/run-tests-pipeline.yml index 47e83ca8c65f..3da391226489 100644 --- a/tools/devops/automation/templates/pipelines/run-tests-pipeline.yml +++ b/tools/devops/automation/templates/pipelines/run-tests-pipeline.yml @@ -36,6 +36,11 @@ parameters: type: boolean default: false + - name: useACES + displayName: Use ACES shared pool for simulator tests + type: boolean + default: false + resources: repositories: - repository: self @@ -62,3 +67,4 @@ stages: runTests: ${{ parameters.runTests }} runWindowsIntegration: ${{ parameters.runWindowsIntegration }} buildPackages: ${{ parameters.buildPackages }} + useACES: ${{ parameters.useACES }} diff --git a/tools/devops/automation/templates/tests-stage.yml b/tools/devops/automation/templates/tests-stage.yml index 41f6fec21ddd..25bf39e80654 100644 --- a/tools/devops/automation/templates/tests-stage.yml +++ b/tools/devops/automation/templates/tests-stage.yml @@ -125,6 +125,10 @@ parameters: type: boolean default: false +- name: useACES + type: boolean + default: false + stages: - template: ./build/linux-build-verification.yml @@ -234,6 +238,7 @@ stages: xqaCertPass: $(xqa--certificates--password) condition: ${{ parameters.runTests }} postPipeline: ${{ not(parameters.buildPackages) }} + useACES: ${{ parameters.useACES }} - template: ./tests/publish-results.yml parameters: @@ -282,6 +287,7 @@ stages: gitHubToken: $(Github.Token) xqaCertPass: $(xqa--certificates--password) pool: $(PRBuildPool) + useACES: ${{ parameters.useACES }} - ${{ each config in parameters.macTestsConfigurations }}: - template: ./mac/stage.yml diff --git a/tools/devops/automation/templates/tests/build.yml b/tools/devops/automation/templates/tests/build.yml index 1eacbac94c70..4e071cd06d33 100644 --- a/tools/devops/automation/templates/tests/build.yml +++ b/tools/devops/automation/templates/tests/build.yml @@ -91,14 +91,19 @@ steps: # if we got to this point, it means that we do have at least 20 Gb to run the test, should # be more than enough, else the above script would have stopped the pipeline - bash: | + set -eo pipefail set -x - set -e cd "$BUILD_REPOSITORY_TITLE" + ARGS=() TEST_PLATFORM=$(echo "${{ parameters.testPlatform }}" | tr '[:upper:]' '[:lower:]') if test -n "$TEST_PLATFORM"; then ARGS+=("--disable-all-platforms") ARGS+=("--enable-$TEST_PLATFORM") fi + XCODE_PATH=$(xcode-select -p 2>/dev/null || true) + if [[ -n "$XCODE_PATH" && -d "$XCODE_PATH" ]]; then + ARGS+=("--xcode=$XCODE_PATH") + fi ./configure "${ARGS[@]}" displayName: 'Enable Xamarin and configure platforms' timeoutInMinutes: 1 @@ -208,6 +213,7 @@ steps: provisioning_script: $(System.DefaultWorkingDirectory)/$(BUILD_REPOSITORY_TITLE)/tools/devops/build-provisioning.csx displayName: 'Provisionator dependencies' provisionatorChannel: $(PROVISIONATOR_CHANNEL) + retryCount: 3 - bash: | set -x diff --git a/tools/devops/automation/templates/tests/stage.yml b/tools/devops/automation/templates/tests/stage.yml index 7301307f3f1f..cbe4c4c73881 100644 --- a/tools/devops/automation/templates/tests/stage.yml +++ b/tools/devops/automation/templates/tests/stage.yml @@ -66,6 +66,10 @@ parameters: type: boolean default: false +- name: useACES + type: boolean + default: false + stages: - stage: ${{ parameters.stageName }} displayName: ${{ parameters.displayName }} @@ -89,17 +93,29 @@ stages: BRANCH_NAME: $[ replace(variables['Build.SourceBranch'], 'refs/heads/', '') ] XHARNESS_LABELS: $[ stageDependencies.configure_build.configure.outputs['labels.xharness_labels'] ] DOTNET_PLATFORMS: $[ stageDependencies.configure_build.configure.outputs['configure_platforms.DOTNET_PLATFORMS'] ] - pool: - name: ${{ parameters.testPool }} - demands: - - Agent.OS -equals Darwin - - macOS.Name -equals ${{ parameters.macOSName }} - - macOS.Architecture -equals arm64 - - XcodeChannel -equals ${{ parameters.XcodeChannel }} - - ${{ each demand in parameters.extraBotDemands }}: - - demand - workspace: - clean: all + # The ACES pool is a virtual-machine pool; expose VM_VENDOR so tests + # gated by TestRuntime.AssertNotVirtualMachine get ignored there. + ${{ if parameters.useACES }}: + VM_VENDOR: ACES + ${{ if parameters.useACES }}: + pool: + name: $(CIBuildPoolACES) + demands: + - ImageOverride -equals $(CIBuildPoolACESImage) + workspace: + clean: all + ${{ else }}: + pool: + name: ${{ parameters.testPool }} + demands: + - Agent.OS -equals Darwin + - macOS.Name -equals ${{ parameters.macOSName }} + - macOS.Architecture -equals arm64 + - XcodeChannel -equals ${{ parameters.XcodeChannel }} + - ${{ each demand in parameters.extraBotDemands }}: + - demand + workspace: + clean: all strategy: matrix: $[ stageDependencies.configure_build.configure.outputs['test_matrix.SIMULATOR_TEST_MATRIX'] ] condition: ne(stageDependencies.configure_build.configure.outputs['labels.skip_all_tests'], 'True') diff --git a/tools/devops/automation/templates/variables/common.yml b/tools/devops/automation/templates/variables/common.yml index c596f2b28938..db4b945f053a 100644 --- a/tools/devops/automation/templates/variables/common.yml +++ b/tools/devops/automation/templates/variables/common.yml @@ -45,6 +45,46 @@ variables: - name: CIBuildPoolUrl value: 'https://devdiv.visualstudio.com/_settings/agentpools?poolId=367&view=agents' +# ACES shared pool wiring. +# +# The infrastructure for routing build / api-diff / simulator-test jobs onto +# the ACES shared pool ($(CIBuildPoolACES), image $(CIBuildPoolACESImage)) +# instead of the traditional Redmond Mac build pools ($(CIBuildPool) / +# $(PRBuildPool)) is plumbed through the `useACES` template parameter, which +# flows from each entry pipeline down through main-stage.yml / +# build-stage.yml, api-diff-stage.yml and tests/stage.yml. When `useACES` is +# true, those job-level templates emit: +# pool: +# name: $(CIBuildPoolACES) +# demands: ImageOverride -equals $(CIBuildPoolACESImage) +# Otherwise they emit the original pool + Darwin / macOS.Name / XcodeChannel +# demands. +# +# The switch MUST be a literal in the entry pipeline. Azure Pipelines only +# resolves `${{ }}` expressions at template-expansion time, and variables +# brought in via `- template:` (like this file) are not visible at that +# point — they are runtime-only. So there is no working "single global +# variable" we can read here; the literal lives in each entry pipeline: +# +# CI (uses CIBuildPool / ACES): +# - build-pipeline.yml -> useACES: true +# - run-ci-api-diff.yml -> useACES: true +# - run-post-ci-build-tests.yml -> useACES: true +# PR (uses PRBuildPool): +# - build-pull-request.yml -> useACES: false +# - run-pr-api-diff.yml -> useACES: false +# - run-post-pr-build-tests.yml -> useACES: false +# +# Flip the literal in the three files on the side you want to move (e.g. when +# the ACES pool is unavailable, or when you need an Xcode/macOS beta that is +# only present on the traditional pools) and queue a new run. +- name: CIBuildPoolACES + value: 'AcesShared' +- name: CIBuildPoolACESUrl + value: 'https://dev.azure.com/devdiv/DevDiv/_settings/agentqueues?queueId=8259&view=jobs' +- name: CIBuildPoolACESImage + value: 'ACES_VM_SharedPool_Tahoe' + # override the default build revision - name: BUILD_REVISION value: azure-devops-$(Build.SourceVersion) From ff0ff627ededc3f2c016efda9214f75f5eba3d4d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 09:36:08 +0200 Subject: [PATCH 79/97] [tests] Add a few missing warning filters. --- tests/dotnet/UnitTests/TemplateTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/dotnet/UnitTests/TemplateTest.cs b/tests/dotnet/UnitTests/TemplateTest.cs index 2b7667a909b0..6cb5304d136b 100644 --- a/tests/dotnet/UnitTests/TemplateTest.cs +++ b/tests/dotnet/UnitTests/TemplateTest.cs @@ -240,7 +240,7 @@ public void CreateAndBuildProjectTemplate (TemplateInfo info) rv = DotNet.AssertBuild (proj, properties); // There should still not be any warnings - warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (info.Platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings (2):\n\t{string.Join ("\n\t", warnings)}"); var appPath = GetAppPath (proj, platform, runtimeIdentifiers); @@ -284,7 +284,7 @@ bool IsMatching (TemplateLanguage lang, TemplateLanguage? value) var proj = Path.Combine (outputDir, $"{info.Template}.{language.AsFileExtension ()}"); var properties = GetDefaultProperties (); var rv = DotNet.AssertBuild (proj, properties); - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings:\n\t{string.Join ("\n\t", warnings)}"); if (info.Execute) { @@ -299,7 +299,7 @@ bool IsMatching (TemplateLanguage lang, TemplateLanguage? value) rv = DotNet.AssertBuild (proj, properties); // There should still not be any warnings - warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings (2):\n\t{string.Join ("\n\t", warnings)}"); var appPath = GetAppPath (proj, platform, runtimeIdentifiers); From cb474e4ca053c6f37d8301bf0d1d473ba2bf71c5 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 09:51:36 +0200 Subject: [PATCH 80/97] [monotouch-test] Add timeouts to all Thread.Join calls (#25644) Replace all Thread.Join() calls (no timeout) with Thread.Join(TimeSpan) + Assert.That to fail the test if the thread doesn't complete within a reasonable time. Timeouts are chosen based on the work each thread does: - 5s for simple/fast operations (object creation, property checks) - 10s for I/O or RunLoop-based threads (timers, network listeners) - 30s for heavy workloads (10k iterations, GPU image processing) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/monotouch-test/CoreAnimation/LayerTest.cs | 8 +++++--- tests/monotouch-test/CoreImage/CIKernelTests.cs | 6 ++++-- tests/monotouch-test/Foundation/NSStreamTest.cs | 6 ++++-- .../monotouch-test/Foundation/NotificationCenter.cs | 2 +- tests/monotouch-test/Foundation/ThreadTest.cs | 2 +- tests/monotouch-test/Foundation/TimerTest.cs | 6 +++--- tests/monotouch-test/ObjCRuntime/RegistrarTest.cs | 6 ++++-- tests/monotouch-test/ObjCRuntime/RuntimeTest.cs | 10 ++++++---- .../SystemConfiguration/NetworkReachabilityTest.cs | 12 ++++++++---- 9 files changed, 36 insertions(+), 22 deletions(-) diff --git a/tests/monotouch-test/CoreAnimation/LayerTest.cs b/tests/monotouch-test/CoreAnimation/LayerTest.cs index 5cf0be59a801..8c689285b031 100644 --- a/tests/monotouch-test/CoreAnimation/LayerTest.cs +++ b/tests/monotouch-test/CoreAnimation/LayerTest.cs @@ -131,9 +131,11 @@ public void TestBug26532 () } catch (Exception e) { ex = e; } - }); + }) { + IsBackground = true, + }; thread.Start (); - thread.Join (); + Assert.That (thread.Join (TimeSpan.FromSeconds (10)), Is.True, "Thread.Join timed out"); var watch = new Stopwatch (); watch.Start (); @@ -181,7 +183,7 @@ public void TestCALayerDelegateDispose () IsBackground = true, }; t.Start (); - t.Join (); + Assert.That (t.Join (TimeSpan.FromSeconds (5)), Is.True, "Thread.Join timed out"); GC.Collect (); NSRunLoop.Main.RunUntil (NSDate.Now.AddSeconds (0.1)); diff --git a/tests/monotouch-test/CoreImage/CIKernelTests.cs b/tests/monotouch-test/CoreImage/CIKernelTests.cs index 87cf3717ede0..3376f816d7e0 100644 --- a/tests/monotouch-test/CoreImage/CIKernelTests.cs +++ b/tests/monotouch-test/CoreImage/CIKernelTests.cs @@ -153,9 +153,11 @@ public void CIKernel_BasicTest () } catch (Exception ex2) { ex = ex2; } - }); + }) { + IsBackground = true, + }; t.Start (); - t.Join (); + Assert.That (t.Join (TimeSpan.FromSeconds (30)), Is.True, "Thread.Join timed out"); if (ex is not null) throw ex; } diff --git a/tests/monotouch-test/Foundation/NSStreamTest.cs b/tests/monotouch-test/Foundation/NSStreamTest.cs index d6c441e6403a..ce64ceed03cd 100644 --- a/tests/monotouch-test/Foundation/NSStreamTest.cs +++ b/tests/monotouch-test/Foundation/NSStreamTest.cs @@ -63,7 +63,9 @@ public void ConnectToHost () return; } - var listenThread = new Thread (new ParameterizedThreadStart (DebugListener)); + var listenThread = new Thread (new ParameterizedThreadStart (DebugListener)) { + IsBackground = true, + }; listenThread.Start (listener); NSStream.CreatePairWithSocketToHost (new IPEndPoint (IPAddress.Loopback, port), out read, out write); read.Open (); @@ -74,7 +76,7 @@ public void ConnectToHost () Assert.That (read.Read (result, 5), Is.EqualTo ((nint) 5)); for (int i = 0; i < 5; i++) Assert.That (result [i], Is.EqualTo (send [i] * 10)); - listenThread.Join (); + Assert.That (listenThread.Join (TimeSpan.FromSeconds (10)), Is.True, "listenThread.Join timed out"); listener.Stop (); read.Close (); write.Close (); diff --git a/tests/monotouch-test/Foundation/NotificationCenter.cs b/tests/monotouch-test/Foundation/NotificationCenter.cs index cab2406ee854..adea6d9968d4 100644 --- a/tests/monotouch-test/Foundation/NotificationCenter.cs +++ b/tests/monotouch-test/Foundation/NotificationCenter.cs @@ -140,7 +140,7 @@ public void ThreadSafe () // OK, we're done now, time to stop. stopSignal.Set (); for (var i = 0; i < threadCount; i++) { - threads [i].Join (); + Assert.That (threads [i].Join (TimeSpan.FromSeconds (5)), Is.True, $"Thread {i} failed to join within 5 seconds"); } Assert.That (ex, Is.Null, "Exception"); } diff --git a/tests/monotouch-test/Foundation/ThreadTest.cs b/tests/monotouch-test/Foundation/ThreadTest.cs index 120d545ebc14..93028e29763b 100644 --- a/tests/monotouch-test/Foundation/ThreadTest.cs +++ b/tests/monotouch-test/Foundation/ThreadTest.cs @@ -40,7 +40,7 @@ public void GetEntryAssemblyReturnsOk () IsBackground = true, }; t.Start (); - t.Join (); + Assert.That (t.Join (TimeSpan.FromSeconds (5)), Is.True, "Thread.Join timed out"); Assert.That (rv, Is.EqualTo (0)); } diff --git a/tests/monotouch-test/Foundation/TimerTest.cs b/tests/monotouch-test/Foundation/TimerTest.cs index 7e7ad52f4db4..be39f8e5be9b 100644 --- a/tests/monotouch-test/Foundation/TimerTest.cs +++ b/tests/monotouch-test/Foundation/TimerTest.cs @@ -42,7 +42,7 @@ public void Bug17793 () thread.Start (); Assert.That (evt.Wait (TimeSpan.FromSeconds (5)), Is.True, "Not signalled twice in 5s"); - thread.Join (); + Assert.That (thread.Join (TimeSpan.FromSeconds (10)), Is.True, "Thread.Join timed out"); } } @@ -70,7 +70,7 @@ public void Bug2443 () thread.Start (); Assert.That (evt.Wait (TimeSpan.FromSeconds (5)), Is.True, "Not signalled twice in 5s"); - thread.Join (); + Assert.That (thread.Join (TimeSpan.FromSeconds (10)), Is.True, "Thread.Join timed out"); } } @@ -101,7 +101,7 @@ public void CreateTimer_NewSignature () Assert.That (evt.WaitOne (TimeSpan.FromSeconds (5)), Is.True, "WaitOne"); Assert.That (result, Is.True, "result"); - thread.Join (); + Assert.That (thread.Join (TimeSpan.FromSeconds (10)), Is.True, "Thread.Join timed out"); } } } diff --git a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs index b1f2ef35a054..c72e97e531d4 100644 --- a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs @@ -2122,9 +2122,11 @@ public void BlockCollection () } catch (Exception e) { ex = e; } - }); + }) { + IsBackground = true, + }; thread.Start (); - thread.Join (); + Assert.That (thread.Join (TimeSpan.FromSeconds (30)), Is.True, "Thread.Join timed out"); GC.Collect (); GC.WaitForPendingFinalizers (); TestRuntime.RunAsync (TimeSpan.FromSeconds (30), () => { }, () => ObjCBlockTester.FreedBlockCount > initialFreedCount); diff --git a/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs b/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs index 0fc4318e3168..10a84df16946 100644 --- a/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs @@ -198,7 +198,7 @@ public bool UsableUntilDeadImpl () IsBackground = true, }; t.Start (); - t.Join (); + Assert.That (t.Join (TimeSpan.FromSeconds (5)), Is.True, "Thread.Join timed out"); // Now we have a handle to an object that may be garbage collected at any time. int counter = 0; @@ -297,7 +297,7 @@ public void FinalizationRaceCondition () IsBackground = true }; thread.Start (); - thread.Join (); + Assert.That (thread.Join (TimeSpan.FromSeconds (5)), Is.True, "Thread.Join timed out"); var getter1 = new Func ((key) => dict [key] as NSString); var getter2 = new Func ((key) => dict [key] as NSString); @@ -555,9 +555,11 @@ public void ResurrectedObjectsDisposedTest (Type type) var t1 = new Thread (() => { for (int i = 0; i < objects.Length; i++) Messaging.bool_objc_msgSend_IntPtr_int (invokerClassHandle, Selector.GetHandle ("invokeMe:wait:"), objects [i], 0); - }); + }) { + IsBackground = true, + }; t1.Start (); - t1.Join (); + Assert.That (t1.Join (TimeSpan.FromSeconds (5)), Is.True, "Thread.Join timed out"); // Collect all those managed wrappers, and make sure their finalizers are executed. GC.Collect (); diff --git a/tests/monotouch-test/SystemConfiguration/NetworkReachabilityTest.cs b/tests/monotouch-test/SystemConfiguration/NetworkReachabilityTest.cs index 0c57a89ef086..d1ceeeb70e39 100644 --- a/tests/monotouch-test/SystemConfiguration/NetworkReachabilityTest.cs +++ b/tests/monotouch-test/SystemConfiguration/NetworkReachabilityTest.cs @@ -220,10 +220,12 @@ public void SetNotification_GCHandleFreed () // Dispose to ensure GCHandle is freed reachability.Dispose (); } - }); + }) { + IsBackground = true, + }; thread.Start (); - thread.Join (); + Assert.That (thread.Join (TimeSpan.FromSeconds (5)), Is.True, "Thread.Join timed out"); // Force garbage collection GC.Collect (); @@ -263,10 +265,12 @@ public void SetNotification_GCHandleFreedWithNull () // Store weak reference to track if object is collected weakRefs [i] = new WeakReference (reachability); } - }); + }) { + IsBackground = true, + }; thread.Start (); - thread.Join (); + Assert.That (thread.Join (TimeSpan.FromSeconds (5)), Is.True, "Thread.Join timed out"); // Force garbage collection GC.Collect (); From 656beee71675a093433ed56c8f0069ee92f7c5ec Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 Jan 2026 11:43:43 +0100 Subject: [PATCH 81/97] [tools] Move RegistrarMode to its own file. --- tools/common/Application.cs | 9 --------- tools/common/RegistrarMode.cs | 13 +++++++++++++ tools/dotnet-linker/dotnet-linker.csproj | 3 +++ tools/mtouch/mtouch.csproj | 3 +++ 4 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 tools/common/RegistrarMode.cs diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 5405b8eb0244..9d2115d4ea3e 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -59,15 +59,6 @@ public enum RegistrarOptions { Trace = 1, } - public enum RegistrarMode { - Default, - Dynamic, - PartialStatic, - Static, - ManagedStatic, - TrimmableStatic, - } - public partial class Application : IToolLog { public Cache? Cache; public string AppDirectory = "."; diff --git a/tools/common/RegistrarMode.cs b/tools/common/RegistrarMode.cs new file mode 100644 index 000000000000..9876623b0c9b --- /dev/null +++ b/tools/common/RegistrarMode.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Xamarin.Bundler; + +public enum RegistrarMode { + Default, + Dynamic, + PartialStatic, + Static, + ManagedStatic, + TrimmableStatic, +} diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index c3ff1f126681..456e1905a013 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -110,6 +110,9 @@ external/tools/common/CSToObjCMap.cs + + tools\common\RegistrarMode.cs + external/tools/common/Rewriter.cs diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index d9ba0a1ea76e..88f535184fca 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -39,6 +39,9 @@ external/tools/common/cache.cs + + tools\common\RegistrarMode.cs + external/tools/linker/MonoTouch.Tuner/Extensions.cs From 08b67346ab4329a6986dd37eeb87343d75f957ed Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 Jan 2026 18:12:34 +0100 Subject: [PATCH 82/97] [tools] Move NormalizedStringComparer into its own file. --- tools/common/Assembly.cs | 27 ------------------- tools/common/NormalizedStringComparer.cs | 33 ++++++++++++++++++++++++ tools/dotnet-linker/dotnet-linker.csproj | 3 +++ tools/mtouch/mtouch.csproj | 3 +++ 4 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 tools/common/NormalizedStringComparer.cs diff --git a/tools/common/Assembly.cs b/tools/common/Assembly.cs index 6ad8d9141958..8e100d6d9609 100644 --- a/tools/common/Assembly.cs +++ b/tools/common/Assembly.cs @@ -600,33 +600,6 @@ public bool IsAOTCompiled { } } - public sealed class NormalizedStringComparer : IEqualityComparer { - public static readonly NormalizedStringComparer OrdinalIgnoreCase = new NormalizedStringComparer (StringComparer.OrdinalIgnoreCase); - - StringComparer comparer; - - public NormalizedStringComparer (StringComparer comparer) - { - this.comparer = comparer; - } - - public bool Equals (string? x, string? y) - { - // From what I gather it doesn't matter which normalization form - // is used, but I chose Form D because HFS normalizes to Form D. - if (x is not null) - x = x.Normalize (System.Text.NormalizationForm.FormD); - if (y is not null) - y = y.Normalize (System.Text.NormalizationForm.FormD); - return comparer.Equals (x, y); - } - - public int GetHashCode (string? obj) - { - return comparer.GetHashCode (obj?.Normalize (System.Text.NormalizationForm.FormD) ?? ""); - } - } - public class AssemblyCollection : IEnumerable { Dictionary HashedAssemblies = new Dictionary (NormalizedStringComparer.OrdinalIgnoreCase); diff --git a/tools/common/NormalizedStringComparer.cs b/tools/common/NormalizedStringComparer.cs new file mode 100644 index 000000000000..c6e1d884ffa5 --- /dev/null +++ b/tools/common/NormalizedStringComparer.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Xamarin.Bundler; + +#nullable enable + +public sealed class NormalizedStringComparer : IEqualityComparer { + public static readonly NormalizedStringComparer OrdinalIgnoreCase = new NormalizedStringComparer (StringComparer.OrdinalIgnoreCase); + + StringComparer comparer; + + public NormalizedStringComparer (StringComparer comparer) + { + this.comparer = comparer; + } + + public bool Equals (string? x, string? y) + { + // From what I gather it doesn't matter which normalization form + // is used, but I chose Form D because HFS normalizes to Form D. + if (x is not null) + x = x.Normalize (System.Text.NormalizationForm.FormD); + if (y is not null) + y = y.Normalize (System.Text.NormalizationForm.FormD); + return comparer.Equals (x, y); + } + + public int GetHashCode (string obj) + { + return comparer.GetHashCode (obj.Normalize (System.Text.NormalizationForm.FormD)); + } +} diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 456e1905a013..8c9fec13a15f 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -77,6 +77,9 @@ external/tools/common/LinkMode.cs + + tools\common\NormalizedStringComparer.cs + external/tools/common/SdkVersions.cs diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index 88f535184fca..e0ebeca5ccf1 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -33,6 +33,9 @@ external/tools/common/AssemblyBuildTarget.cs + + tools\common\NormalizedStringComparer.cs + external/tools/common/PListExtensions.cs From a29a1ac57bf629de803745bba06b0e2a2ee45f2e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 Jan 2026 20:06:54 +0100 Subject: [PATCH 83/97] [tools] Simplify a little bit of code. --- tools/common/Application.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 9d2115d4ea3e..03f0d58bc48c 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -948,7 +948,6 @@ public void GetAotArguments (string filename, Abi abi, string outputDir, string bool enable_debug_symbols = app.PackageManagedDebugSymbols; bool interp = app.IsInterpreted (Assembly.GetIdentity (filename)) && !(isDedupAssembly.HasValue && isDedupAssembly.Value); bool interp_full = !interp && app.UseInterpreter; - bool is32bit = (abi & Abi.Arch32Mask) > 0; string arch = abi.AsArchString (); processArguments.Add ("--debug"); From 542e99d976f7c8cf417b6e6fbe89c46046bf9911 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 12:49:18 +0200 Subject: [PATCH 84/97] [tests] Clean up Configure.cs a bit. --- tests/Makefile | 8 +-- tests/common/Configuration.cs | 55 +++++++++---------- tests/dotnet/UnitTests/AssetsTest.cs | 4 +- .../AssemblySetup.cs | 2 +- .../TaskTests/ACToolTaskTest.cs | 2 +- .../TaskTests/GetAvailableDevicesTest.cs | 2 +- .../TaskTests/IBToolTaskTests.cs | 2 +- .../TaskTests/MergeAppBundleTaskTest.cs | 2 +- .../TestHelpers/TestBase.cs | 2 +- tests/mtouch/MLaunchTool.cs | 2 +- 10 files changed, 36 insertions(+), 45 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index 6a0802b2f561..34087555249a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -52,9 +52,7 @@ test.config: Makefile $(TOP)/Make.config $(TOP)/eng/Version.Details.xml @echo "JENKINS_RESULTS_DIRECTORY=$(abspath $(JENKINS_RESULTS_DIRECTORY))" >> $@ @echo "XCODE_DEVELOPER_ROOT=$(XCODE_DEVELOPER_ROOT)" >> $@ @echo "DOTNET=$(DOTNET)" >> $@ - @echo "IOS_SDK_VERSION=$(IOS_SDK_VERSION)" >> $@ - @echo "TVOS_SDK_VERSION=$(TVOS_SDK_VERSION)" >> $@ - @echo "MACOS_SDK_VERSION=$(MACOS_SDK_VERSION)" >> $@ + @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),$(platform)_SDK_VERSION=$($(platform)_SDK_VERSION)\\n)" | sed 's/^ //' >> $@ @echo "DOTNET_BCL_DIR=$(DOTNET_BCL_DIR)" >> $@ @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),DOTNET_$(platform)_RUNTIME_IDENTIFIERS_NO_ARCH='$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS_NO_ARCH)'\\n)" | sed 's/^ //' >> $@ @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),DOTNET_$(platform)_RUNTIME_IDENTIFIERS='$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS)'\\n)" | sed 's/^ //' >> $@ @@ -83,9 +81,7 @@ test-system.config: Makefile $(TOP)/Make.config $(TOP)/eng/Version.Details.xml @rm -f $@ @echo "JENKINS_RESULTS_DIRECTORY=$(abspath $(JENKINS_RESULTS_DIRECTORY))" >> $@ @echo "DOTNET=$(DOTNET)" >> $@ - @echo "IOS_SDK_VERSION=$(IOS_SDK_VERSION)" >> $@ - @echo "TVOS_SDK_VERSION=$(TVOS_SDK_VERSION)" >> $@ - @echo "MACOS_SDK_VERSION=$(MACOS_SDK_VERSION)" >> $@ + @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),$(platform)_SDK_VERSION=$($(platform)_SDK_VERSION)\\n)" | sed 's/^ //' >> $@ @echo "DOTNET_TFM=$(DOTNET_TFM)" >> $@ @echo "DOTNET_BCL_DIR=$(DOTNET_BCL_DIR)" >> $@ @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),DOTNET_$(platform)_RUNTIME_IDENTIFIERS='$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS)'\\n)" | sed 's/^ //' >> $@ diff --git a/tests/common/Configuration.cs b/tests/common/Configuration.cs index 8154d4e9b100..1735ac8df815 100644 --- a/tests/common/Configuration.cs +++ b/tests/common/Configuration.cs @@ -10,25 +10,17 @@ namespace Xamarin.Tests { static partial class Configuration { - public const string XI_ProductName = "MonoTouch"; - public const string XM_ProductName = "Xamarin.Mac"; - public static string DotNetBclDir = ""; public static string DotNetCscCommand = ""; public static string DotNetExecutable; public static string DotNetTfm; - public static string? mt_src_root; - public static string sdk_version; + public static string ios_sdk_version; public static string tvos_sdk_version; public static string macos_sdk_version; - public static string xcode_root; + public static string maccatalyst_sdk_version; + static string xcode_root; public static string? XcodeVersionString; - public static string xcode83_root; - public static string xcode94_root; -#if MONOMAC - public static string mac_xcode_root; -#endif - public static Dictionary make_config = new Dictionary (); + static Dictionary make_config = new Dictionary (); public static bool include_ios; public static bool include_mac; @@ -286,12 +278,11 @@ static Configuration () { ParseConfigFiles (); - sdk_version = GetVariable ("IOS_SDK_VERSION", "8.0"); + ios_sdk_version = GetVariable ("IOS_SDK_VERSION", "8.0"); tvos_sdk_version = GetVariable ("TVOS_SDK_VERSION", "9.0"); macos_sdk_version = GetVariable ("MACOS_SDK_VERSION", "10.12"); + maccatalyst_sdk_version = GetVariable ("MACCATALYST_SDK_VERSION", "14.0"); xcode_root = GetVariable ("XCODE_DEVELOPER_ROOT", "/Applications/Xcode.app/Contents/Developer"); - xcode83_root = GetVariable ("XCODE83_DEVELOPER_ROOT", "/Applications/Xcode83.app/Contents/Developer"); - xcode94_root = GetVariable ("XCODE94_DEVELOPER_ROOT", "/Applications/Xcode94.app/Contents/Developer"); include_ios = !string.IsNullOrEmpty (GetVariable ("INCLUDE_IOS", "")); include_mac = !string.IsNullOrEmpty (GetVariable ("INCLUDE_MAC", "")); include_tvos = !string.IsNullOrEmpty (GetVariable ("INCLUDE_TVOS", "")); @@ -305,16 +296,10 @@ static Configuration () DOTNET_DIR = GetVariable ("DOTNET_DIR", ""); XcodeVersionString = GetVariable ("XCODE_VERSION", GetXcodeVersion (xcode_root)); -#if MONOMAC - mac_xcode_root = xcode_root; -#endif Console.WriteLine ("Test configuration:"); - Console.WriteLine (" SDK_VERSION={0}", sdk_version); Console.WriteLine (" XCODE_ROOT={0}", xcode_root); -#if MONOMAC - Console.WriteLine (" MAC_XCODE_ROOT={0}", mac_xcode_root); -#endif + Console.WriteLine (" XCODE_VERSION={0}", XcodeVersionString); Console.WriteLine (" INCLUDE_IOS={0}", include_ios); Console.WriteLine (" INCLUDE_MAC={0}", include_mac); Console.WriteLine (" INCLUDE_TVOS={0}", include_tvos); @@ -371,13 +356,7 @@ public static bool TryGetRootPath ([NotNullWhen (true)] out string? rootPath) } } - public static string SourceRoot { - get { - if (mt_src_root is null) - mt_src_root = RootPath; - return mt_src_root; - } - } + public static string SourceRoot => RootPath; public static string TestProjectsDirectory { get { @@ -694,7 +673,7 @@ public static void SetBuildVariables (ApplePlatform platform, [NotNullIfNotNull if (environment is null) environment = new Dictionary (); - environment ["DEVELOPER_DIR"] = Path.GetDirectoryName (Path.GetDirectoryName (xcode_root)!)!; + environment ["DEVELOPER_DIR"] = Path.GetDirectoryName (Path.GetDirectoryName (XcodeLocation)!)!; // This is set by `dotnet test` and can cause building legacy projects to fail to build with: // Microsoft.NET.Build.Extensions.ConflictResolution.targets(30,5): @@ -732,6 +711,22 @@ public static string GetTestLibraryDirectory (ApplePlatform platform, bool? simu return Path.Combine (SourceRoot, "tests", "test-libraries", ".libs", dir); } + public static string GetSdkVersion (ApplePlatform platform) + { + switch (platform) { + case ApplePlatform.iOS: + return ios_sdk_version; + case ApplePlatform.MacOSX: + return macos_sdk_version; + case ApplePlatform.TVOS: + return tvos_sdk_version; + case ApplePlatform.MacCatalyst: + return maccatalyst_sdk_version; + default: + throw new NotImplementedException ($"Unknown platform: {platform}"); + } + } + // This implementation of Touch is to update a timestamp (not to make sure a certain file exists). public static void Touch (string file) { diff --git a/tests/dotnet/UnitTests/AssetsTest.cs b/tests/dotnet/UnitTests/AssetsTest.cs index 3466457299e9..c980bbf977a3 100644 --- a/tests/dotnet/UnitTests/AssetsTest.cs +++ b/tests/dotnet/UnitTests/AssetsTest.cs @@ -136,9 +136,9 @@ public static string GetFullSdkVersion (ApplePlatform platform, string runtimeId switch (platform) { case ApplePlatform.iOS: if (runtimeIdentifiers.Contains ("simulator")) { - return $"iphonesimulator{Configuration.sdk_version}"; + return $"iphonesimulator{Configuration.ios_sdk_version}"; } else { - return $"iphoneos{Configuration.sdk_version}"; + return $"iphoneos{Configuration.ios_sdk_version}"; } case ApplePlatform.TVOS: if (runtimeIdentifiers.Contains ("simulator")) { diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs index 5059aab86236..28575a0412a9 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs @@ -17,7 +17,7 @@ public void AssemblyInitialization () const string msbuild_exe_path = "/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/msbuild/15.0/bin/MSBuild.dll"; if (is_in_vsmac) { var env = new Dictionary { - { "DEVELOPER_DIR", Path.GetDirectoryName (Path.GetDirectoryName (Configuration.xcode_root)) ?? string.Empty }, + { "DEVELOPER_DIR", Path.GetDirectoryName (Path.GetDirectoryName (Configuration.XcodeLocation)) ?? string.Empty }, { "MSBUILD_EXE_PATH", msbuild_exe_path }, }; diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs index 218b3cf4fa9e..958fcdb63f8b 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs @@ -60,7 +60,7 @@ ACTool CreateACToolTask (ApplePlatform platform, string projectDir, out string i task.MinimumOSVersion = Xamarin.SdkVersions.GetMinVersion (platform).ToString (); task.OutputPath = Path.Combine (intermediateOutputPath, "OutputPath"); task.ProjectDir = projectDir; - task.SdkDevPath = Configuration.xcode_root; + task.SdkDevPath = Configuration.XcodeLocation; task.SdkPlatform = sdkPlatform; task.SdkVersion = version.ToString (); task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform).ToString (); diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs index c202f5e615aa..dc6ab813dde8 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs @@ -31,7 +31,7 @@ GetAvailableDevicesTaskWrapper CreateTask (ApplePlatform platform, string simctl SimCtlJson = simctlJson, DeviceCtlJson = devicectlJson, }; - task.SdkDevPath = Configuration.xcode_root; + task.SdkDevPath = Configuration.XcodeLocation; task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform).ToString (); if (!string.IsNullOrEmpty (appManifest)) { diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs index 2b66ae4a5f93..bc6fdbf5f6a0 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs @@ -44,7 +44,7 @@ IBTool CreateIBToolTask (ApplePlatform framework, string projectDir, string inte task.MinimumOSVersion = PDictionary.OpenFile (Path.Combine (projectDir, "Info.plist")).GetMinimumOSVersion (); task.ResourcePrefix = "Resources"; task.ProjectDir = projectDir; - task.SdkDevPath = Configuration.xcode_root; + task.SdkDevPath = Configuration.XcodeLocation; task.SdkPlatform = platform; task.SdkVersion = version.ToString (); task.TargetFrameworkMoniker = TargetFramework.DotNet_iOS_String; diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs index 309d9aa597f9..afe1a7eaa7b6 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -60,7 +60,7 @@ MergeAppBundles CreateTask (string outputBundle, params string [] inputBundles) var task = CreateTask (); task.InputAppBundles = inputItems.ToArray (); task.OutputAppBundle = outputBundle; - task.SdkDevPath = Configuration.xcode_root; + task.SdkDevPath = Configuration.XcodeLocation; return task; } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestHelpers/TestBase.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestHelpers/TestBase.cs index 05b391b10fbf..8f0e1ba36e6d 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestHelpers/TestBase.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestHelpers/TestBase.cs @@ -54,7 +54,7 @@ public virtual void Setup () var t = new T (); t.BuildEngine = Engine; if (t is XamarinTask xt) - xt.SdkDevPath = Configuration.xcode_root; + xt.SdkDevPath = Configuration.XcodeLocation; return t; } diff --git a/tests/mtouch/MLaunchTool.cs b/tests/mtouch/MLaunchTool.cs index 5185e8f6cfd2..789d29a1dd4c 100644 --- a/tests/mtouch/MLaunchTool.cs +++ b/tests/mtouch/MLaunchTool.cs @@ -97,7 +97,7 @@ IList BuildArguments () } if (!string.IsNullOrEmpty (platformName) && !string.IsNullOrEmpty (simType)) { - var device = string.Format (":v2:runtime=com.apple.CoreSimulator.SimRuntime.{0}-{1},devicetype=com.apple.CoreSimulator.SimDeviceType.{2}", platformName, Configuration.sdk_version.Replace ('.', '-'), simType); + var device = string.Format (":v2:runtime=com.apple.CoreSimulator.SimRuntime.{0}-{1},devicetype=com.apple.CoreSimulator.SimDeviceType.{2}", platformName, Configuration.ios_sdk_version.Replace ('.', '-'), simType); sb.Add ($"--device:{device}"); } From 2d9925c267622b01dec379d793e749a43bedef9d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 11:26:27 +0200 Subject: [PATCH 85/97] [xharness] Run the new assembly processing tests. --- tests/xharness/Harness.cs | 7 +++++++ tests/xharness/TestLabel.cs | 3 ++- tools/devops/automation/templates/common/configure.yml | 5 +++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/xharness/Harness.cs b/tests/xharness/Harness.cs index 33cf6b5ffb93..a8490b532f08 100644 --- a/tests/xharness/Harness.cs +++ b/tests/xharness/Harness.cs @@ -355,6 +355,13 @@ void PopulateUnitTestProjects () Timeout = (TimeSpan?) TimeSpan.FromMinutes (10), Filter = "", }, + new { + Label = TestLabel.AssemblyProcessing, + ProjectPath = Path.GetFullPath (Path.Combine (HarnessConfiguration.RootDirectory, "assembly-preparer", "assembly-preparer-tests.csproj")), + Name = "Assembly processing tests", + Timeout = (TimeSpan?) TimeSpan.FromMinutes (10), + Filter = "", + }, new { Label = TestLabel.Generator, ProjectPath = Path.GetFullPath (Path.Combine (HarnessConfiguration.RootDirectory, "bgen", "bgen-tests.csproj")), diff --git a/tests/xharness/TestLabel.cs b/tests/xharness/TestLabel.cs index 9085a1eb815f..0bc5e9d78910 100644 --- a/tests/xharness/TestLabel.cs +++ b/tests/xharness/TestLabel.cs @@ -44,7 +44,8 @@ public enum TestLabel : Int64 { Generator = 1 << 11, [Label ("interdependent-binding-projects")] InterdependentBindingProjects = 1 << 12, - // 1 << 13 is unused + [Label ("assembly-processing")] + AssemblyProcessing = 1 << 13, [Label ("introspection")] Introspection = 1 << 14, [Label ("linker")] diff --git a/tools/devops/automation/templates/common/configure.yml b/tools/devops/automation/templates/common/configure.yml index a7dd354368e7..377d7d4cde15 100644 --- a/tools/devops/automation/templates/common/configure.yml +++ b/tools/devops/automation/templates/common/configure.yml @@ -31,6 +31,11 @@ parameters: testPrefix: 'windows_integration', testStage: 'windows_integration' }, + { + label: assembly-processing, + splitByPlatforms: false, + testPrefix: 'simulator_tests', + }, { label: cecil, splitByPlatforms: false, From 9cac08cdb2b3ac9582182d67a4fca686138dd3b0 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:17:18 +0200 Subject: [PATCH 86/97] [tools] Remove some dead code --- tools/dotnet-linker/LinkerConfiguration.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index e9375514f12c..c0152c375d5b 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -126,7 +126,6 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application = new Application (this); CompilerFlags = new CompilerFlags (Application); - var use_llvm = false; var lines = File.ReadAllLines (linker_file); var significantLines = new List (); // This is the input the cache uses to verify if the cache is still valid for (var i = 0; i < lines.Length; i++) { @@ -439,10 +438,6 @@ public static LinkerConfiguration GetInstance (LinkContext context) ErrorHelper.Show (Application, messages); } - if (use_llvm) { - Abi |= Abi.LLVM; - } - Application.CreateCache (significantLines.ToArray ()); if (Application.Cache is not null) Application.Cache.SetLocation (Application, CacheDirectory); From 00274d7d2baab947ccfbc7be58ca956cc6b66df5 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:08:12 +0200 Subject: [PATCH 87/97] [tools] Extract OptimizeGeneratedCode into its own class. Extract the static optimization methods from OptimizeGeneratedCodeHandler into a new standalone OptimizeGeneratedCode class. The handler class now only contains the linker-specific scaffolding and delegates to the new class. This makes the optimization logic reusable without depending on the linker's ExceptionalMarkHandler base class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../dotnet-linker/BackingFieldDelayHandler.cs | 6 +- .../OptimizeGeneratedCodeStep.cs | 4 +- tools/dotnet-linker/dotnet-linker.csproj | 3 + tools/linker/CoreOptimizeGeneratedCode.cs | 1207 +--------------- tools/linker/OptimizeGeneratedCode.cs | 1214 +++++++++++++++++ tools/linker/RegistrarRemovalTrackingStep.cs | 2 +- 6 files changed, 1225 insertions(+), 1211 deletions(-) create mode 100644 tools/linker/OptimizeGeneratedCode.cs diff --git a/tools/dotnet-linker/BackingFieldDelayHandler.cs b/tools/dotnet-linker/BackingFieldDelayHandler.cs index 9a61786f7c0d..970f5d4893d6 100644 --- a/tools/dotnet-linker/BackingFieldDelayHandler.cs +++ b/tools/dotnet-linker/BackingFieldDelayHandler.cs @@ -84,9 +84,9 @@ public static void ReapplyDisposedFields (DerivedLinkContext context, string ope var store_field = ins; var load_null = ins.Previous; var load_this = ins.Previous.Previous; - if (OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, store_field, operation, Code.Stfld) && - OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, load_null, operation, Code.Ldnull) && - OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, load_this, operation, Code.Ldarg_0)) { + if (OptimizeGeneratedCode.ValidateInstruction (app, method, store_field, operation, Code.Stfld) && + OptimizeGeneratedCode.ValidateInstruction (app, method, load_null, operation, Code.Ldnull) && + OptimizeGeneratedCode.ValidateInstruction (app, method, load_this, operation, Code.Ldarg_0)) { store_field.OpCode = OpCodes.Nop; load_null.OpCode = OpCodes.Nop; load_this.OpCode = OpCodes.Nop; diff --git a/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs b/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs index d785f16c94af..a7b57042609f 100644 --- a/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs +++ b/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs @@ -14,7 +14,7 @@ public class OptimizeGeneratedCodeStep : AssemblyModifierStep { protected override bool IsActiveFor (AssemblyDefinition assembly) { - return OptimizeGeneratedCodeHandler.IsActiveFor (assembly, Configuration.Profile, DerivedLinkContext.Annotations); + return OptimizeGeneratedCode.IsActiveFor (assembly, Configuration.Profile, DerivedLinkContext.Annotations); } protected override bool ProcessType (TypeDefinition type) @@ -32,7 +32,7 @@ protected override bool ProcessMethod (MethodDefinition method) Device = App.IsDeviceBuild, }; } - return OptimizeGeneratedCodeHandler.OptimizeMethod (data, method); + return OptimizeGeneratedCode.OptimizeMethod (data, method); } } } diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 8c9fec13a15f..9a5d2966bc7b 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -140,6 +140,9 @@ external/tools/linker/CoreTypeMapStep.cs + + external/tools/linker/OptimizeGeneratedCode.cs + external/src/ObjCRuntime/Registrar.cs diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index c33d231aeb66..de6fabbec099 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -1,18 +1,8 @@ // Copyright 2012-2013, 2016 Xamarin Inc. All rights reserved. -using System; -using System.Collections.Generic; -using System.Linq; - -using ObjCRuntime; using Mono.Cecil; -using Mono.Cecil.Cil; using Mono.Linker; using Mono.Linker.Steps; -using Mono.Tuner; -using MonoTouch.Tuner; - -using Xamarin.Bundler; #nullable enable @@ -31,504 +21,7 @@ public override void Initialize (LinkContext context, MarkContext markContext) bool IsActiveFor (AssemblyDefinition assembly) { - return IsActiveFor (assembly, Profile, Annotations); - } - - public static bool IsActiveFor (AssemblyDefinition assembly, Profile profile, AnnotationStore annotations) - { - // Unless an assembly is or references our platform assembly, then it won't have anything we need to register - if (!profile.IsOrReferencesProductAssembly (assembly)) - return false; - - // We only care about assemblies that are being linked. - if (annotations.GetAction (assembly) != AssemblyAction.Link) - return false; - - return true; - } - - // [GeneratedCode] is not enough - e.g. it's used for anonymous delegates even if the - // code itself is not tool/compiler generated - static protected bool IsExport (ICustomAttributeProvider provider) - { - return provider.HasCustomAttribute (Namespaces.Foundation, "ExportAttribute"); - } - - // less risky to nop-ify if branches are pointing to this instruction - static protected void Nop (Instruction ins) - { - // Leave 'leave' instructions in place, they might be required for the resulting IL to be correct/verifiable. - switch (ins.OpCode.Code) { - case Code.Leave: - case Code.Leave_S: - return; - } - ins.OpCode = OpCodes.Nop; - ins.Operand = null; - } - - internal static bool ValidateInstruction (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, string operation, Code expected) - { - return ValidateInstruction (data.App, caller, ins, operation, expected); - } - - internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, Instruction ins, string operation, Code expected) - { - if (ins.OpCode.Code != expected) { - log.Log (1, "Could not {0} in {1} at offset {2}, expected {3} got {4}", operation, caller, ins.Offset, expected, ins); - return false; - } - - return true; - } - - internal static bool ValidateInstruction (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, string operation, params Code [] expected) - { - foreach (var code in expected) { - if (ins.OpCode.Code == code) - return true; - } - - data.App.Log (1, "Could not {0} in {1} at offset {2}, expected any of [{3}] got {4}", operation, caller, ins.Offset, string.Join (", ", expected), ins); - return false; - } - - static int? GetConstantValue (OptimizeGeneratedCodeData data, Instruction? ins) - { - if (ins is null) - return null; - - switch (ins.OpCode.Code) { - case Code.Ldc_I4_0: - return 0; - case Code.Ldc_I4_1: - return 1; - case Code.Ldc_I4_2: - return 2; - case Code.Ldc_I4_3: - return 3; - case Code.Ldc_I4_4: - return 4; - case Code.Ldc_I4_5: - return 5; - case Code.Ldc_I4_6: - return 6; - case Code.Ldc_I4_7: - return 7; - case Code.Ldc_I4_8: - return 8; - case Code.Ldc_I4: - case Code.Ldc_I4_S: - if (ins.Operand is int) - return (int) ins.Operand; - if (ins.Operand is sbyte) - return (sbyte) ins.Operand; - return null; -#if DEBUG - case Code.Isinst: // We might be able to calculate a constant value here in the future - case Code.Ldloc: - case Code.Ldloca: - case Code.Ldloc_0: - case Code.Ldloc_1: - case Code.Ldloc_2: - case Code.Ldloc_3: - case Code.Ldloc_S: - case Code.Ldloca_S: - case Code.Ldarg: - case Code.Ldarg_0: - case Code.Ldarg_1: - case Code.Ldarg_2: - case Code.Ldarg_3: - case Code.Ldarg_S: - case Code.Ldarga: - case Code.Call: - case Code.Calli: - case Code.Callvirt: - case Code.Box: - case Code.Ldsfld: - case Code.Dup: // You might think we could get the constant of the previous instruction, but this instruction might be the target of a branch, in which case the question becomes: which instruction was the previous instruction? And that's not a question easily answered without a much more thorough analysis of the code. - case Code.Ldlen: - case Code.Ldind_U1: - case Code.Ldind_U2: - case Code.Ldind_U4: - case Code.Ldind_Ref: - case Code.Conv_I: - case Code.Conv_I1: - case Code.Conv_I2: - case Code.Conv_I4: - case Code.Conv_U: - case Code.Sizeof: - case Code.Ldfld: - case Code.Ldflda: - return null; // just to not hit the CWL below -#endif - default: -#if DEBUG - data.App.Log (9, "Unknown conditional instruction: {0}", ins); -#endif - return null; - } - } - - static bool MarkInstructions (OptimizeGeneratedCodeData data, MethodDefinition method, Mono.Collections.Generic.Collection instructions, bool [] reachable, int start, int end) - { - if (reachable [start]) - return true; // We've already marked this section of code - - for (int i = start; i < end; i++) { - reachable [i] = true; - - var ins = instructions [i]; - switch (ins.OpCode.FlowControl) { - case FlowControl.Branch: - // Unconditional branch, we continue marking from the instruction that we branch to. - var br_target = (Instruction) ins.Operand; - return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (br_target), instructions.Count); - case FlowControl.Cond_Branch: - // Conditional instruction, we need to check if we can calculate a constant value for the condition - var cond_target = ins.Operand as Instruction; - bool? branch = null; // did we get a constant value for the condition, and if so, did we branch or not? - var cond_instruction_count = 0; // The number of instructions that compose the condition - - if (ins.OpCode.Code == Code.Switch) { - // Treat all branches of the switch statement as reachable. - // FIXME: calculate the potential constant branch (currently there are no optimizable methods where the switch condition is constant, so this is not needed for now) - var targets = ins.Operand as Instruction []; - if (targets is null) { - data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); - return false; - } - foreach (var target in targets) { - // not constant, continue marking both this code sequence and the branched sequence - if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (target), end)) - return false; - } - return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (ins.Next), end); - } - - if (cond_target is null) { - data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); - return false; - } - - switch (ins.OpCode.Code) { - case Code.Brtrue: - case Code.Brtrue_S: { - var v = GetConstantValue (data, ins.Previous); - if (v.HasValue) - branch = v.Value != 0; - cond_instruction_count = 2; - break; - } - case Code.Brfalse: - case Code.Brfalse_S: { - var v = GetConstantValue (data, ins.Previous); - if (v.HasValue) - branch = v.Value == 0; - cond_instruction_count = 2; - break; - } - case Code.Beq: - case Code.Beq_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value == x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Bne_Un: - case Code.Bne_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value != x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Ble: - case Code.Ble_S: - case Code.Ble_Un: - case Code.Ble_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value <= x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Blt: - case Code.Blt_S: - case Code.Blt_Un: - case Code.Blt_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value < x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Bge: - case Code.Bge_S: - case Code.Bge_Un: - case Code.Bge_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value >= x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Bgt: - case Code.Bgt_S: - case Code.Bgt_Un: - case Code.Bgt_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value > x2.Value; - cond_instruction_count = 3; - break; - } - default: - data.App.Log ("Can't optimize {0} because of unknown branch instruction: {1}", method, ins); - break; - } - - if (branch.HasValue) { - // Make sure nothing else in the method branches into the middle of our supposedly constant condition, - // bypassing our constant calculation. Note that it's not a bad to branch to the _first_ instruction in - // the sequence (thus the +2 here), just into the middle of it. - if (AnyBranchTo (data, instructions, instructions [i - cond_instruction_count + 2], ins)) - branch = null; - } - - if (!branch.HasValue) { - // not constant, continue marking both this code sequence and the branched sequence - if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end)) - return false; - } else { - // we can remove the branch (and the code that loads the condition), so we mark those instructions as dead. - for (int a = 0; a < cond_instruction_count; a++) - reachable [i - a] = false; - - // Now continue marking according to whether we branched or not - if (branch.Value) { - // branch always taken - return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end); - } else { - // branch never taken - // continue looping - } - } - break; - case FlowControl.Call: - case FlowControl.Next: - // Nothing special, continue marking - break; - case FlowControl.Return: - case FlowControl.Throw: - // Control flow returns here, so stop marking - return true; - case FlowControl.Break: - case FlowControl.Meta: - case FlowControl.Phi: - default: - data.App.Log (4, "Can't optimize {0} because of unknown flow control for: {1}", method, ins); - return false; - } - } - - return true; - } - - // Check if there are any branches in the instructions that branch to anywhere between 'first' and 'last' instructions (both inclusive). - static bool AnyBranchTo (OptimizeGeneratedCodeData data, Mono.Collections.Generic.Collection instructions, Instruction first, Instruction last) - { - if (first.Offset > last.Offset) { - data.App.Log ($"Broken assumption: {first} is after {last}"); - return true; // This is the safe thing to do, since it will prevent inlining - } - - for (int i = 0; i < instructions.Count; i++) { - var ins = instructions [i]; - switch (ins.OpCode.FlowControl) { - case FlowControl.Branch: - case FlowControl.Cond_Branch: - var target = ins.Operand as Instruction; - if (target is not null && target.Offset >= first.Offset && target.Offset <= last.Offset) - return true; - break; - } - } - - return false; - } - - static bool EliminateDeadCode (OptimizeGeneratedCodeData data, MethodDefinition caller) - { - var modified = false; - if (data.Optimizations.DeadCodeElimination != true) - return modified; - - var instructions = caller.Body.Instructions; - var reachable = new bool [instructions.Count]; - - // We walk the instructions in the method, starting with the first instruction, - // marking all reachable instructions. Any non-reachable instructions at the end - // can be removed. - - if (!MarkInstructions (data, caller, instructions, reachable, 0, instructions.Count)) - return modified; - - // Handle exception handlers specially, they do not follow normal code flow. - bool []? reachableExceptionHandlers = null; - if (caller.Body.HasExceptionHandlers) { - reachableExceptionHandlers = new bool [caller.Body.ExceptionHandlers.Count]; - for (var e = 0; e < reachableExceptionHandlers.Length; e++) { - var eh = caller.Body.ExceptionHandlers [e]; - - // First check if the protected region is reachable - var startI = instructions.IndexOf (eh.TryStart); - var endI = instructions.IndexOf (eh.TryEnd); - for (int i = startI; i < endI; i++) { - if (reachable [i]) { - reachableExceptionHandlers [e] = true; - break; - } - } - // The protected code isn't reachable, none of the handlers will be executed - if (!reachableExceptionHandlers [e]) - continue; - - switch (eh.HandlerType) { - case ExceptionHandlerType.Catch: - // We don't need catch handlers the reachable instructions are all nops - var allNops = true; - for (int i = startI; i < endI; i++) { - if (instructions [i].OpCode.Code != Code.Nop) { - allNops = false; - break; - } - } - if (!allNops) { - if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) - return modified; - } - break; - case ExceptionHandlerType.Finally: - // finally clauses are always executed, even if the protected region is empty - if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) - return modified; - break; - case ExceptionHandlerType.Fault: - case ExceptionHandlerType.Filter: - // FIXME: and until fixed, exit gracefully without doing anything - data.App.Log (4, "Unhandled exception handler: {0}, skipping dead code elimination for {1}", eh.HandlerType, caller); - return modified; - } - } - } - - if (Array.IndexOf (reachable, false) == -1) - return modified; // entire method is reachable - - // Kill branch instructions when there are only dead instructions between the branch instruction and the target of the branch - for (int i = 0; i < instructions.Count; i++) { - if (!reachable [i]) - continue; - - var ins = instructions [i]; - if (ins.OpCode.Code != Code.Br && ins.OpCode.Code != Code.Br_S) - continue; - var target = ins.Operand as Instruction; - if (target is null) - continue; - if (target.Offset < ins.Offset) - continue; // backwards branch, keep those - - var start = i + 1; - var end = instructions.IndexOf (target); - var any_reachable = false; - for (int k = start; k < end; k++) { - if (reachable [k]) { - any_reachable = true; - break; - } - } - if (any_reachable) - continue; - - // The branch instruction just branches over unreachable instructions, so it can be considered unreachable too. - reachable [i] = false; - } - - // Check if there are unreachable instructions at the end. - var last_reachable = Array.LastIndexOf (reachable, true); - if (last_reachable < reachable.Length - 1) { - // There are unreachable instructions at the end. - // We must verify that there are no branches into these instructions. - // In theory there shouldn't be any (if there are branches into these instructions, - // they're reachable), but let's still verify just in case. - var last_reachable_offset = instructions [last_reachable].Offset; - for (int i = 0; i < last_reachable; i++) { - if (!reachable [i]) - continue; // Unreachable instructions don't branch anywhere, because they'll be removed. - var ins = instructions [i]; - switch (ins.OpCode.FlowControl) { - case FlowControl.Break: - case FlowControl.Cond_Branch: - var target = (Instruction) ins.Operand; - if (target.Offset > last_reachable_offset) { - data.App.Log (4, "Can't optimize {0} because of branching beyond last instruction alive: {1}", caller, ins); - return modified; - } - break; - } - } - } -#if false - data.App.Log ($"{caller.FullName}:"); - for (int i = 0; i < reachable.Length; i++) { - data.App.Log ($"{(reachable [i] ? " " : "- ")} {instructions [i]}"); - if (!reachable [i]) - Nop (instructions [i]); - } - data.App.Log (""); -#endif - - // Exterminate, exterminate, exterminate - for (int i = 0; i < reachable.Length; i++) { - if (!reachable [i]) { - Nop (instructions [i]); - modified = true; - } - } - - // Remove exception handlers - if (reachableExceptionHandlers is not null) { - for (int i = reachableExceptionHandlers.Length - 1; i >= 0; i--) { - if (reachableExceptionHandlers [i]) - continue; - caller.Body.ExceptionHandlers.RemoveAt (i); - modified = true; - } - } - - // Remove unreachable instructions (nops) at the end, because the last instruction can only be ret/throw/backwards branch. - for (int i = last_reachable + 1; i < reachable.Length; i++) { - instructions.RemoveAt (last_reachable + 1); - modified = true; - } - - return modified; - } - - static bool GetIsExtensionType (TypeDefinition type) - { - // if 'type' inherits from NSObject inside an assembly that has [GeneratedCode] - // or for static types used for optional members (using extensions methods), they can be optimized too - return type.IsSealed && type.IsAbstract && type.Name.EndsWith ("_Extensions", StringComparison.Ordinal); + return OptimizeGeneratedCode.IsActiveFor (assembly, Profile, Annotations); } protected override void Process (MethodDefinition method) @@ -544,703 +37,7 @@ protected override void Process (MethodDefinition method) Device = LinkContext.App.IsDeviceBuild, }; } - OptimizeMethod (data, method); - } - - public static bool OptimizeMethod (OptimizeGeneratedCodeData data, MethodDefinition method) - { - var modified = false; - - if (!method.HasBody) - return modified; - - if (method.IsBindingImplOptimizableCode (data.LinkContext)) { - // We optimize all methods that have the [BindingImpl (BindingImplAttributes.Optimizable)] attribute. - } else if (method.IsGeneratedCode (data.LinkContext) && ( - GetIsExtensionType (method.DeclaringType) - || IsExport (method))) { - // We optimize methods that have the [GeneratedCodeAttribute] and is either an extension type or an exported method - } else { - // but it would be too risky to apply on user-generated code - return modified; - } - - if (data.Optimizations.InlineIsARM64CallingConvention == true && data.InlineIsArm64CallingConvention.HasValue && method.Name == "GetIsARM64CallingConvention" && method.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) { - // Rewrite to return the constant value - var instr = method.Body.Instructions; - instr.Clear (); - instr.Add (Instruction.Create (data.InlineIsArm64CallingConvention.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); - instr.Add (Instruction.Create (OpCodes.Ret)); - return true; // nothing else to do here. - } - - if (ProcessProtocolInterfaceStaticConstructor (data, method)) - return true; - - var instructions = method.Body.Instructions; - for (int i = 0; i < instructions.Count; i++) { - var ins = instructions [i]; - switch (ins.OpCode.Code) { - case Code.Newobj: - case Code.Call: - modified |= ProcessCalls (data, method, ins, out var instructionsAddedOrRemoved); - i += instructionsAddedOrRemoved; - break; - case Code.Ldsfld: - modified |= ProcessLoadStaticField (data, method, ins); - break; - } - } - - modified |= EliminateDeadCode (data, method); - return modified; - } - - // Returns the number of instructions added (or removed) in the 'instructionsAddedOrRemoved' parameter. - // Returns true if any modifications were done. - static bool ProcessCalls (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) - { - var modified = false; - instructionsAddedOrRemoved = 0; - var mr = ins.Operand as MethodReference; - switch (mr?.Name) { - case "EnsureUIThread": - modified |= ProcessEnsureUIThread (data, caller, ins); - break; - case "get_IsDirectBinding": - modified |= ProcessIsDirectBinding (data, caller, ins); - break; - case "SetupBlock": - case "SetupBlockUnsafe": - modified |= ProcessSetupBlock (data, caller, ins, out instructionsAddedOrRemoved); - break; - case ".ctor": - if (!mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) - break; - return ProcessBlockLiteralConstructor (data, caller, ins, out instructionsAddedOrRemoved); - } - - return modified; - } - - static bool ProcessLoadStaticField (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - var modified = false; - var fr = ins.Operand as FieldReference; - switch (fr?.Name) { - case "IsARM64CallingConvention": - modified |= ProcessIsARM64CallingConvention (data, caller, ins); - break; - case "Arch": - // https://app.asana.com/0/77259014252/77812690163 - modified |= ProcessRuntimeArch (data, caller, ins); - break; - } - return modified; - } - - static bool ProcessEnsureUIThread (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - if (data.Optimizations.RemoveUIThreadChecks != true) - return false; - - // Verify we're checking the right get_EnsureUIThread call - var declaringTypeNamespace = data.LinkContext.App.Platform == Utils.ApplePlatform.MacOSX ? Namespaces.AppKit : Namespaces.UIKit; - var declaringTypeName = data.LinkContext.App.Platform == Utils.ApplePlatform.MacOSX ? "NSApplication" : "UIApplication"; - var mr = ins.Operand as MethodReference; - if (mr is null || !mr.DeclaringType.Is (declaringTypeNamespace, declaringTypeName)) - return false; - - // Verify a few assumptions before doing anything - const string operation = "remove calls to [NS|UI]Application::EnsureUIThread"; - if (!ValidateInstruction (data, caller, ins, operation, Code.Call)) - return false; - - // This is simple: just remove the call - Nop (ins); // call void UIKit.UIApplication::EnsureUIThread() - return true; - } - - static bool? IsDirectBindingConstant (OptimizeGeneratedCodeData data, TypeDefinition type) - { - return type.IsNSObject (data.LinkContext) ? type.GetIsDirectBindingConstant (data.LinkContext) : null; - } - - static bool ProcessIsDirectBinding (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - const string operation = "inline IsDirectBinding"; - - if (data.Optimizations.InlineIsDirectBinding != true) - return false; - - bool? isdirectbinding_constant = IsDirectBindingConstant (data, caller.DeclaringType); - - // If we don't know the constant isdirectbinding value, then we can't inline anything - if (!isdirectbinding_constant.HasValue) - return false; - - // Verify we're checking the right get_IsDirectBinding call - var mr = ins.Operand as MethodReference; - if (mr is null || !mr.DeclaringType.Is (Namespaces.Foundation, "NSObject")) - return false; - - // Verify a few assumptions before doing anything - if (!ValidateInstruction (data, caller, ins.Previous, operation, Code.Ldarg_0)) - return false; - - if (!ValidateInstruction (data, caller, ins, operation, Code.Call)) - return false; - - // Clearing the branch succeeded, so clear the condition too - // ldarg.0 - Nop (ins.Previous); - // call System.Boolean Foundation.NSObject::get_IsDirectBinding() - ins.OpCode = isdirectbinding_constant.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; - ins.Operand = null; - return true; - } - - static bool ProcessSetupBlock (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) - { - instructionsAddedOrRemoved = 0; - if (data.Optimizations.OptimizeBlockLiteralSetupBlock != true) - return false; - - // This will optimize calls to SetupBlock and SetupBlockUnsafe by calculating the signature for the block - // (which both SetupBlock and SetupBlockUnsafe do), and then rewrite the code to call SetupBlockImpl instead - // (which takes the block signature as an argument instead of calculating it). This is required to - // remove the dynamic registrar, because calculating the block signature is done in the dynamic registrar. - // - // This code is a mirror of the code in BlockLiteral.SetupBlock (to calculate the block signature). - var mr = ins.Operand as MethodReference; - if (mr is null || !mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) - return false; - - if (caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral")) { - switch (caller.Name) { - case "GetBlockForDelegate": - case "CreateBlockForDelegate": - // These methods contain a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about. - return false; - } - } - - string? signature = null; - try { - // We need to figure out the type of the first argument to the call to SetupBlock[Impl]. - // - // Example sequence: - // - // ldsfld ObjCRuntime.Trampolines/DJSContextExceptionHandler ObjCRuntime.Trampolines/SDJSContextExceptionHandler::Handler - // ldarg.1 - // call System.Void ObjCRuntime.BlockLiteral::SetupBlockUnsafe(System.Delegate, System.Delegate) - // - - // Locating the instruction that loads the first argument can be complicated, so we simplify by making a few assumptions: - // 1. The instruction immediately before the call instruction (which would load the last argument) is a Push1/Pop0 instruction. - // This avoids running into trouble when the instruction does something else (it could be a any other instruction, which would throw off the next calculations) - // 2. We have a approved list of instructions we know how to calculate the type for, and which we use on the second to last instruction before the call instruction - - // First verify the Push1/Pop0 behavior in point 1. - var prev = ins.Previous; - while (prev.OpCode.Code == Code.Nop) - prev = prev.Previous; // Skip any nops. - if (prev.OpCode.StackBehaviourPush != StackBehaviour.Push1) { - //todo: localize mmp error 2106 - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); - return false; - } else if (prev.OpCode.StackBehaviourPop != StackBehaviour.Pop0) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); - return false; - } - - var loadTrampolineInstruction = prev.Previous; - while (loadTrampolineInstruction.OpCode.Code == Code.Nop) - loadTrampolineInstruction = loadTrampolineInstruction.Previous; // Skip any nops. - - // Then find the type of the previous instruction (the first argument to SetupBlock[Unsafe]) - var trampolineDelegateType = GetPushedType (caller, loadTrampolineInstruction); - if (trampolineDelegateType is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_A, caller, ins.Offset, mr.Name, loadTrampolineInstruction)); - return false; - } - - if (trampolineDelegateType.Is ("System", "Delegate") || trampolineDelegateType.Is ("System", "MulticastDelegate")) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_B, caller, trampolineDelegateType.FullName, mr.Name)); - return false; - } - - if (!data.LinkContext.App.StaticRegistrar.TryComputeBlockSignature (caller, trampolineDelegateType, out var exception, out signature)) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, exception, caller, ins, Errors.MM2106_D, caller, ins.Offset, exception.Message)); - return false; - - } - } catch (Exception e) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); - return false; - } - - // We got the information we need: rewrite the IL. - var instructions = caller.Body.Instructions; - var index = instructions.IndexOf (ins); - // Inject the extra arguments - instructions.Insert (index, Instruction.Create (OpCodes.Ldstr, signature)); - instructions.Insert (index, Instruction.Create (mr.Name == "SetupBlockUnsafe" ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1)); - // Change the call to call SetupBlockImpl instead - ins.Operand = GetBlockSetupImpl (data, caller, ins); - - //Driver.Log (4, "Optimized call to BlockLiteral.SetupBlock in {0} at offset {1} with delegate type {2} and signature {3}", caller, ins.Offset, delegateType.FullName, signature); - instructionsAddedOrRemoved = 2; - return true; - } - - internal static bool IsBlockLiteralCtor_Type_String (MethodDefinition md) - { - if (!md.HasParameters) - return false; - - if (md.Parameters.Count != 4) - return false; - - if (!(md.Parameters [0].ParameterType is PointerType pt) || !pt.ElementType.Is ("System", "Void")) - return false; - - if (!md.Parameters [1].ParameterType.Is ("System", "Object")) - return false; - - if (!md.Parameters [2].ParameterType.Is ("System", "Type")) - return false; - - if (!md.Parameters [3].ParameterType.Is ("System", "String")) - return false; - - return true; - } - - static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) - { - instructionsAddedOrRemoved = 0; - - if (data.Optimizations.OptimizeBlockLiteralSetupBlock != true) - return false; - - // This will optimize calls to this BlockLiteral constructor: - // (void* ptr, object context, Type trampolineType, string trampolineMethod) - // by calculating the signature for the block using the last two arguments, - // and then rewrite the code to call this constructor overload instead: - // (void* ptr, object context, string signature) - // This is required to remove the dynamic registrar, because calculating the block signature - // is done in the dynamic registrar. - // - // This code is a mirror of the code in BlockLiteral.SetupBlock (to calculate the block signature). - var mr = ins.Operand as MethodReference; - if (mr is null || !mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) - return false; - - var md = mr.Resolve (); - if (md is null || !IsBlockLiteralCtor_Type_String (md)) - return false; - - string? signature = null; - Instruction? sequenceStart; - try { - // We need to figure out the last argument to the call to the ctor - // - // Example sequence: - // - // ldarg.0 - // ldarg.1 - // ldtoken ... - // call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle) - // ldstr ... - // newobj BlockLiteral (void*, System.Object, System.Type, System.String) - // - - // Verify 'ldstr ...' - var loadString = GetPreviousSkippingNops (ins); - if (loadString.OpCode != OpCodes.Ldstr) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadString)); - return false; - } - - // Verify 'call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)' - var callGetTypeFromHandle = GetPreviousSkippingNops (loadString); - if (callGetTypeFromHandle.OpCode != OpCodes.Call || !(callGetTypeFromHandle.Operand is MethodReference methodOperand) || methodOperand.Name != "GetTypeFromHandle" || !methodOperand.DeclaringType.Is ("System", "Type")) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, callGetTypeFromHandle)); - return false; - } - - // Verify 'ldtoken ...' - var loadType = GetPreviousSkippingNops (callGetTypeFromHandle); - if (loadType.OpCode != OpCodes.Ldtoken) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType)); - return false; - } - - // Then find the type of the previous instruction - var trampolineContainerTypeReference = loadType.Operand as TypeReference; - if (trampolineContainerTypeReference is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType.Operand)); - return false; - } - - var trampolineContainerType = trampolineContainerTypeReference.Resolve (); - if (trampolineContainerType is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, trampolineContainerTypeReference)); - return false; - } - - // Find the trampoline method - var trampolineMethodName = (string) loadString.Operand; - var trampolineMethods = trampolineContainerType.Methods.Where (v => v.Name == trampolineMethodName).ToArray (); - if (!trampolineMethods.Any ()) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E1 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because no method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); - return false; - } else if (trampolineMethods.Count () > 1) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E2 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because more than one method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); - return false; - } - var trampolineMethod = trampolineMethods [0]; - if (!trampolineMethod.HasParameters || trampolineMethod.Parameters.Count < 1) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_F /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' must have at least one parameter. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); - return false; - } - - // Check that the method's first parameter is either IntPtr, void* or BlockLiteral* - var firstParameterType = trampolineMethod.Parameters [0].ParameterType; - if (firstParameterType.Is ("System", "IntPtr")) { - // ok - } else if (firstParameterType is PointerType ptrType) { - var ptrTargetType = ptrType.ElementType; - if (!(ptrTargetType.Is ("System", "Void") || ptrTargetType.Is ("ObjCRuntime", "BlockLiteral"))) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); - return false; - } - // ok - } else { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); - return false; - } - - // Check that the method has [UnmanagedCallersOnly] - if (!trampolineMethod.HasCustomAttributes || !trampolineMethod.CustomAttributes.Any (v => v.AttributeType.Is ("System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute"))) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_H /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' does not have an [UnmanagedCallersOnly] attribute. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); - return false; - } - - var userDelegateType = data.LinkContext.App.StaticRegistrar.GetUserDelegateType (trampolineMethod); - MethodReference? userMethod = null; - var blockSignature = true; - if (userDelegateType is not null) { - userMethod = data.LinkContext.App.StaticRegistrar.GetDelegateInvoke (userDelegateType); - } else { - userMethod = trampolineMethod; - } - - if (userMethod is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_D, caller, ins.Offset, "Could not find delegate invoke method")); - return false; - } - - // Calculate the block signature. - var parameters = new TypeReference [userMethod.Parameters.Count]; - for (int p = 0; p < parameters.Length; p++) - parameters [p] = userMethod.Parameters [p].ParameterType; - signature = data.LinkContext.App.StaticRegistrar.ComputeSignature (userMethod.DeclaringType, false, userMethod.ReturnType, parameters, userMethod.Resolve (), isBlockSignature: blockSignature); - - sequenceStart = loadType; - } catch (Exception e) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); - return false; - } - - // We got the information we need: rewrite the IL. - var instructions = caller.Body.Instructions; - var index = instructions.IndexOf (sequenceStart); - int instructionDiff = 0; - while (instructions [index] != ins) { - instructions.RemoveAt (index); - instructionDiff--; - } - // Inject the extra arguments - instructions.Insert (index, Instruction.Create (OpCodes.Ldstr, signature)); - instructionDiff++; - // Change the call to call the ctor with the string signature parameter instead - ins.Operand = GetBlockLiteralConstructor (data, caller, ins); - - data.App.Log (4, "Optimized call to BlockLiteral..ctor in {0} at offset {1} with signature {2}", caller, ins.Offset, signature); - instructionsAddedOrRemoved = instructionDiff; - return true; - } - - static Instruction GetPreviousSkippingNops (Instruction ins) - { - do { - ins = ins.Previous; - } while (ins.OpCode == OpCodes.Nop); - return ins; - } - - static Instruction? SkipNops (Instruction? ins) - { - if (ins is null) - return null; - - while (ins.OpCode == OpCodes.Nop) { - if (ins.Next is null) - return null; - ins = ins.Next; - } - return ins; - } - - static bool ProcessIsARM64CallingConvention (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - const string operation = "inline Runtime.IsARM64CallingConvention"; - - if (data.Optimizations.InlineIsARM64CallingConvention != true) - return false; - - if (!data.InlineIsArm64CallingConvention.HasValue) - return false; - - // Verify we're checking the right IsARM64CallingConvention field - var fr = ins.Operand as FieldReference; - if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) - return false; - - if (!ValidateInstruction (data, caller, ins, operation, Code.Ldsfld)) - return false; - - // We're fine, inline the Runtime.IsARM64CallingConvention value - ins.OpCode = data.InlineIsArm64CallingConvention.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; - ins.Operand = null; - - return true; - } - - static bool ProcessRuntimeArch (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - const string operation = "inline Runtime.Arch"; - - if (data.Optimizations.InlineRuntimeArch != true) - return false; - - // Verify we're checking the right Arch field - var fr = ins.Operand as FieldReference; - if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) - return false; - - // Verify a few assumptions before doing anything - if (!ValidateInstruction (data, caller, ins, operation, Code.Ldsfld)) - return false; - - // We're fine, inline the Runtime.Arch condition - // The enum values are Runtime.DEVICE = 0 and Runtime.SIMULATOR = 1, - ins.OpCode = data.Device ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1; - ins.Operand = null; - return true; - } - - // Returns the type of the value pushed on the stack by the given instruction. - // Returns null for unknown instructions, or for instructions that don't push anything on the stack. - static TypeReference? GetPushedType (MethodDefinition method, Instruction ins) - { - var index = 0; - switch (ins.OpCode.Code) { - case Code.Ldloc_0: - case Code.Ldarg_0: - index = 0; - break; - case Code.Ldloc_1: - case Code.Ldarg_1: - index = 1; - break; - case Code.Ldloc_2: - case Code.Ldarg_2: - index = 2; - break; - case Code.Ldloc_3: - case Code.Ldarg_3: - index = 3; - break; - case Code.Ldloc: - case Code.Ldloc_S: - return ((VariableDefinition) ins.Operand).VariableType; - case Code.Ldarg: - case Code.Ldarg_S: - return ((ParameterDefinition) ins.Operand).ParameterType; - case Code.Ldfld: - case Code.Ldsfld: - return ((FieldReference) ins.Operand).FieldType; - case Code.Call: - case Code.Calli: - case Code.Callvirt: - return ((MethodReference) ins.Operand).ReturnType; - default: - return null; - } - - switch (ins.OpCode.Code) { - case Code.Ldloc: - case Code.Ldloc_0: - case Code.Ldloc_1: - case Code.Ldloc_2: - case Code.Ldloc_3: - return method.Body.Variables [index].VariableType; - case Code.Ldarg: - case Code.Ldarg_0: - case Code.Ldarg_1: - case Code.Ldarg_2: - case Code.Ldarg_3: - if (method.IsStatic) { - return method.Parameters [index].ParameterType; - } else if (index == 0) { - return method.DeclaringType; - } else { - return method.Parameters [index - 1].ParameterType; - } - default: - return null; - } - } - - static MethodReference GetBlockSetupImpl (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - if (data.SetupBlockImplDefinition is null) { - var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); - foreach (var method in type.Methods) { - if (method.Name != "SetupBlockImpl") - continue; - data.SetupBlockImplDefinition = method; - data.SetupBlockImplDefinition.IsPublic = true; // Make sure the method is callable from the optimized code. - break; - } - if (data.SetupBlockImplDefinition is null) - throw ErrorHelper.CreateError (data.LinkContext.App, 99, caller, ins, Errors.MX0099, $"could not find the method {Namespaces.ObjCRuntime}.BlockLiteral.SetupBlockImpl"); - } - return caller.Module.ImportReference (data.SetupBlockImplDefinition); - } - - static MethodReference GetBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - if (data.BlockCtorDefinition is null) { - var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); - foreach (var method in type.Methods) { - if (!method.IsConstructor) - continue; - if (method.IsStatic) - continue; - if (!method.HasParameters || method.Parameters.Count != 3) - continue; - if (!(method.Parameters [0].ParameterType is PointerType pt) || !pt.ElementType.Is ("System", "Void")) - continue; - if (!method.Parameters [1].ParameterType.Is ("System", "Object")) - continue; - if (!method.Parameters [2].ParameterType.Is ("System", "String")) - continue; - data.BlockCtorDefinition = method; - break; - } - if (data.BlockCtorDefinition is null) - throw ErrorHelper.CreateError (data.LinkContext.App, 99, caller, ins, Errors.MX0099, $"could not find the constructor ObjCRuntime.BlockLiteral (void*, object, string)"); - } - return caller.Module.ImportReference (data.BlockCtorDefinition); - } - - static bool ProcessProtocolInterfaceStaticConstructor (OptimizeGeneratedCodeData data, MethodDefinition method) - { - // The static cctor in protocol interfaces exists only to preserve the protocol's members, for inspection by the registrar(s). - // If we're registering protocols, then we don't need to preserve protocol members, because the registrar - // already knows everything about it => we can remove the static cctor. - - if (!(method.DeclaringType.IsInterface && method.IsStatic && method.IsConstructor && method.HasBody)) - return false; - - if (data.Optimizations.RegisterProtocols != true) { - data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: the 'register-protocols' optimization is disabled.", method.DeclaringType.FullName); - return false; - } - - if (!method.DeclaringType.HasCustomAttributes || !method.DeclaringType.CustomAttributes.Any (v => v.AttributeType.Is ("Foundation", "ProtocolAttribute"))) { - data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: no Protocol attribute found.", method.DeclaringType.FullName); - return false; - } - - var ins = SkipNops (method.Body.Instructions.First ()); - if (ins is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); - return false; - } else if (ins.OpCode != OpCodes.Ldnull) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); - return false; - } - - ins = SkipNops (ins.Next); - if (ins is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); - return false; - } - var callGCKeepAlive = ins; - if (callGCKeepAlive.OpCode != OpCodes.Call || !(callGCKeepAlive.Operand is MethodReference methodOperand) || methodOperand.Name != "KeepAlive" || !methodOperand.DeclaringType.Is ("System", "GC")) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); - return false; - } - - ins = SkipNops (ins.Next); - if (ins is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); - return false; - } else if (ins.OpCode != OpCodes.Ret) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); - return false; - } - - ins = SkipNops (ins.Next); - if (ins is not null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); - return false; - } - - // We can just remove the entire method, however that confuses the linker later on, so just empty it out and remove all the attributes. - data.App.Log (4, "Optimized static constructor in the protocol interface {0} (static constructor was cleared and custom attributes removed)", method.DeclaringType.FullName); - method.Body.Instructions.Clear (); - method.Body.Instructions.Add (Instruction.Create (OpCodes.Ret)); - - // Only remove DynamicDependency attributes that takes a single string argument. - // The generator generates other DynamicDependency attributes, and we don't want to remove those. - for (var i = method.CustomAttributes.Count - 1; i >= 0; i--) { - var ca = method.CustomAttributes [i]; - - if (!ca.AttributeType.Is ("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute")) - continue; - - if (!ca.HasConstructorArguments) - continue; - - if (ca.ConstructorArguments.Count != 1) - continue; - - if (!ca.ConstructorArguments [0].Type.Is ("System", "String")) - continue; - - method.CustomAttributes.RemoveAt (i); - } - - return true; + OptimizeGeneratedCode.OptimizeMethod (data, method); } } - - public class OptimizeGeneratedCodeData { - public required Xamarin.Tuner.DerivedLinkContext LinkContext; - public required Optimizations Optimizations; - public required bool Device; - - public MethodDefinition? SetupBlockImplDefinition; - public MethodDefinition? BlockCtorDefinition; - public bool? InlineIsArm64CallingConvention; - - public Application App => LinkContext.App; - } - } diff --git a/tools/linker/OptimizeGeneratedCode.cs b/tools/linker/OptimizeGeneratedCode.cs new file mode 100644 index 000000000000..46af059779e8 --- /dev/null +++ b/tools/linker/OptimizeGeneratedCode.cs @@ -0,0 +1,1214 @@ +// Copyright 2012-2013, 2016 Xamarin Inc. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; + +using ObjCRuntime; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Linker.Steps; +using Mono.Tuner; +using MonoTouch.Tuner; + +using Xamarin.Bundler; + +#nullable enable + +namespace Xamarin.Linker { + public class OptimizeGeneratedCode { + public static bool IsActiveFor (AssemblyDefinition assembly, Profile profile, AnnotationStore annotations) + { + // Unless an assembly is or references our platform assembly, then it won't have anything we need to register + if (!profile.IsOrReferencesProductAssembly (assembly)) + return false; + + // We only care about assemblies that are being linked. + if (annotations.GetAction (assembly) != AssemblyAction.Link) + return false; + + return true; + } + + // [GeneratedCode] is not enough - e.g. it's used for anonymous delegates even if the + // code itself is not tool/compiler generated + static protected bool IsExport (ICustomAttributeProvider provider) + { + return provider.HasCustomAttribute (Namespaces.Foundation, "ExportAttribute"); + } + + // less risky to nop-ify if branches are pointing to this instruction + static protected void Nop (Instruction ins) + { + // Leave 'leave' instructions in place, they might be required for the resulting IL to be correct/verifiable. + switch (ins.OpCode.Code) { + case Code.Leave: + case Code.Leave_S: + return; + } + ins.OpCode = OpCodes.Nop; + ins.Operand = null; + } + + internal static bool ValidateInstruction (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, string operation, Code expected) + { + return ValidateInstruction (data.App, caller, ins, operation, expected); + } + + internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, Instruction ins, string operation, Code expected) + { + if (ins.OpCode.Code != expected) { + log.Log (1, "Could not {0} in {1} at offset {2}, expected {3} got {4}", operation, caller, ins.Offset, expected, ins); + return false; + } + + return true; + } + + internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, Instruction ins, string operation, params Code [] expected) + { + foreach (var code in expected) { + if (ins.OpCode.Code == code) + return true; + } + + log.Log (1, "Could not {0} in {1} at offset {2}, expected any of [{3}] got {4}", operation, caller, ins.Offset, string.Join (", ", expected), ins); + return false; + } + + static int? GetConstantValue (OptimizeGeneratedCodeData data, Instruction? ins) + { + if (ins is null) + return null; + + switch (ins.OpCode.Code) { + case Code.Ldc_I4_0: + return 0; + case Code.Ldc_I4_1: + return 1; + case Code.Ldc_I4_2: + return 2; + case Code.Ldc_I4_3: + return 3; + case Code.Ldc_I4_4: + return 4; + case Code.Ldc_I4_5: + return 5; + case Code.Ldc_I4_6: + return 6; + case Code.Ldc_I4_7: + return 7; + case Code.Ldc_I4_8: + return 8; + case Code.Ldc_I4: + case Code.Ldc_I4_S: + if (ins.Operand is int) + return (int) ins.Operand; + if (ins.Operand is sbyte) + return (sbyte) ins.Operand; + return null; +#if DEBUG + case Code.Isinst: // We might be able to calculate a constant value here in the future + case Code.Ldloc: + case Code.Ldloca: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloc_S: + case Code.Ldloca_S: + case Code.Ldarg: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + case Code.Ldarg_S: + case Code.Ldarga: + case Code.Call: + case Code.Calli: + case Code.Callvirt: + case Code.Box: + case Code.Ldsfld: + case Code.Dup: // You might think we could get the constant of the previous instruction, but this instruction might be the target of a branch, in which case the question becomes: which instruction was the previous instruction? And that's not a question easily answered without a much more thorough analysis of the code. + case Code.Ldlen: + case Code.Ldind_U1: + case Code.Ldind_U2: + case Code.Ldind_U4: + case Code.Ldind_Ref: + case Code.Conv_I: + case Code.Conv_I1: + case Code.Conv_I2: + case Code.Conv_I4: + case Code.Conv_U: + case Code.Sizeof: + case Code.Ldfld: + case Code.Ldflda: + return null; // just to not hit the CWL below +#endif + default: +#if DEBUG + data.App.Log (9, "Unknown conditional instruction: {0}", ins); +#endif + return null; + } + } + + static bool MarkInstructions (OptimizeGeneratedCodeData data, MethodDefinition method, Mono.Collections.Generic.Collection instructions, bool [] reachable, int start, int end) + { + if (reachable [start]) + return true; // We've already marked this section of code + + for (int i = start; i < end; i++) { + reachable [i] = true; + + var ins = instructions [i]; + switch (ins.OpCode.FlowControl) { + case FlowControl.Branch: + // Unconditional branch, we continue marking from the instruction that we branch to. + var br_target = (Instruction) ins.Operand; + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (br_target), instructions.Count); + case FlowControl.Cond_Branch: + // Conditional instruction, we need to check if we can calculate a constant value for the condition + var cond_target = ins.Operand as Instruction; + bool? branch = null; // did we get a constant value for the condition, and if so, did we branch or not? + var cond_instruction_count = 0; // The number of instructions that compose the condition + + if (ins.OpCode.Code == Code.Switch) { + // Treat all branches of the switch statement as reachable. + // FIXME: calculate the potential constant branch (currently there are no optimizable methods where the switch condition is constant, so this is not needed for now) + var targets = ins.Operand as Instruction []; + if (targets is null) { + data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); + return false; + } + foreach (var target in targets) { + // not constant, continue marking both this code sequence and the branched sequence + if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (target), end)) + return false; + } + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (ins.Next), end); + } + + if (cond_target is null) { + data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); + return false; + } + + switch (ins.OpCode.Code) { + case Code.Brtrue: + case Code.Brtrue_S: { + var v = GetConstantValue (data, ins.Previous); + if (v.HasValue) + branch = v.Value != 0; + cond_instruction_count = 2; + break; + } + case Code.Brfalse: + case Code.Brfalse_S: { + var v = GetConstantValue (data, ins.Previous); + if (v.HasValue) + branch = v.Value == 0; + cond_instruction_count = 2; + break; + } + case Code.Beq: + case Code.Beq_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value == x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Bne_Un: + case Code.Bne_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value != x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Ble: + case Code.Ble_S: + case Code.Ble_Un: + case Code.Ble_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value <= x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Blt: + case Code.Blt_S: + case Code.Blt_Un: + case Code.Blt_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value < x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Bge: + case Code.Bge_S: + case Code.Bge_Un: + case Code.Bge_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value >= x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Bgt: + case Code.Bgt_S: + case Code.Bgt_Un: + case Code.Bgt_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value > x2.Value; + cond_instruction_count = 3; + break; + } + default: + data.App.Log ("Can't optimize {0} because of unknown branch instruction: {1}", method, ins); + break; + } + + if (branch.HasValue) { + // Make sure nothing else in the method branches into the middle of our supposedly constant condition, + // bypassing our constant calculation. Note that it's not a bad to branch to the _first_ instruction in + // the sequence (thus the +2 here), just into the middle of it. + if (AnyBranchTo (data, instructions, instructions [i - cond_instruction_count + 2], ins)) + branch = null; + } + + if (!branch.HasValue) { + // not constant, continue marking both this code sequence and the branched sequence + if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end)) + return false; + } else { + // we can remove the branch (and the code that loads the condition), so we mark those instructions as dead. + for (int a = 0; a < cond_instruction_count; a++) + reachable [i - a] = false; + + // Now continue marking according to whether we branched or not + if (branch.Value) { + // branch always taken + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end); + } else { + // branch never taken + // continue looping + } + } + break; + case FlowControl.Call: + case FlowControl.Next: + // Nothing special, continue marking + break; + case FlowControl.Return: + case FlowControl.Throw: + // Control flow returns here, so stop marking + return true; + case FlowControl.Break: + case FlowControl.Meta: + case FlowControl.Phi: + default: + data.App.Log (4, "Can't optimize {0} because of unknown flow control for: {1}", method, ins); + return false; + } + } + + return true; + } + + // Check if there are any branches in the instructions that branch to anywhere between 'first' and 'last' instructions (both inclusive). + static bool AnyBranchTo (OptimizeGeneratedCodeData data, Mono.Collections.Generic.Collection instructions, Instruction first, Instruction last) + { + if (first.Offset > last.Offset) { + data.App.Log ($"Broken assumption: {first} is after {last}"); + return true; // This is the safe thing to do, since it will prevent inlining + } + + for (int i = 0; i < instructions.Count; i++) { + var ins = instructions [i]; + switch (ins.OpCode.FlowControl) { + case FlowControl.Branch: + case FlowControl.Cond_Branch: + var target = ins.Operand as Instruction; + if (target is not null && target.Offset >= first.Offset && target.Offset <= last.Offset) + return true; + break; + } + } + + return false; + } + + static bool EliminateDeadCode (OptimizeGeneratedCodeData data, MethodDefinition caller) + { + var modified = false; + if (data.Optimizations.DeadCodeElimination != true) + return modified; + + var instructions = caller.Body.Instructions; + var reachable = new bool [instructions.Count]; + + // We walk the instructions in the method, starting with the first instruction, + // marking all reachable instructions. Any non-reachable instructions at the end + // can be removed. + + if (!MarkInstructions (data, caller, instructions, reachable, 0, instructions.Count)) + return modified; + + // Handle exception handlers specially, they do not follow normal code flow. + bool []? reachableExceptionHandlers = null; + if (caller.Body.HasExceptionHandlers) { + reachableExceptionHandlers = new bool [caller.Body.ExceptionHandlers.Count]; + for (var e = 0; e < reachableExceptionHandlers.Length; e++) { + var eh = caller.Body.ExceptionHandlers [e]; + + // First check if the protected region is reachable + var startI = instructions.IndexOf (eh.TryStart); + var endI = instructions.IndexOf (eh.TryEnd); + for (int i = startI; i < endI; i++) { + if (reachable [i]) { + reachableExceptionHandlers [e] = true; + break; + } + } + // The protected code isn't reachable, none of the handlers will be executed + if (!reachableExceptionHandlers [e]) + continue; + + switch (eh.HandlerType) { + case ExceptionHandlerType.Catch: + // We don't need catch handlers the reachable instructions are all nops + var allNops = true; + for (int i = startI; i < endI; i++) { + if (instructions [i].OpCode.Code != Code.Nop) { + allNops = false; + break; + } + } + if (!allNops) { + if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) + return modified; + } + break; + case ExceptionHandlerType.Finally: + // finally clauses are always executed, even if the protected region is empty + if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) + return modified; + break; + case ExceptionHandlerType.Fault: + case ExceptionHandlerType.Filter: + // FIXME: and until fixed, exit gracefully without doing anything + data.App.Log (4, "Unhandled exception handler: {0}, skipping dead code elimination for {1}", eh.HandlerType, caller); + return modified; + } + } + } + + if (Array.IndexOf (reachable, false) == -1) + return modified; // entire method is reachable + + // Kill branch instructions when there are only dead instructions between the branch instruction and the target of the branch + for (int i = 0; i < instructions.Count; i++) { + if (!reachable [i]) + continue; + + var ins = instructions [i]; + if (ins.OpCode.Code != Code.Br && ins.OpCode.Code != Code.Br_S) + continue; + var target = ins.Operand as Instruction; + if (target is null) + continue; + if (target.Offset < ins.Offset) + continue; // backwards branch, keep those + + var start = i + 1; + var end = instructions.IndexOf (target); + var any_reachable = false; + for (int k = start; k < end; k++) { + if (reachable [k]) { + any_reachable = true; + break; + } + } + if (any_reachable) + continue; + + // The branch instruction just branches over unreachable instructions, so it can be considered unreachable too. + reachable [i] = false; + } + + // Check if there are unreachable instructions at the end. + var last_reachable = Array.LastIndexOf (reachable, true); + if (last_reachable < reachable.Length - 1) { + // There are unreachable instructions at the end. + // We must verify that there are no branches into these instructions. + // In theory there shouldn't be any (if there are branches into these instructions, + // they're reachable), but let's still verify just in case. + var last_reachable_offset = instructions [last_reachable].Offset; + for (int i = 0; i < last_reachable; i++) { + if (!reachable [i]) + continue; // Unreachable instructions don't branch anywhere, because they'll be removed. + var ins = instructions [i]; + switch (ins.OpCode.FlowControl) { + case FlowControl.Break: + case FlowControl.Cond_Branch: + var target = (Instruction) ins.Operand; + if (target.Offset > last_reachable_offset) { + data.App.Log (4, "Can't optimize {0} because of branching beyond last instruction alive: {1}", caller, ins); + return modified; + } + break; + } + } + } +#if false + Console.WriteLine ($"{caller.FullName}:"); + for (int i = 0; i < reachable.Length; i++) { + Console.WriteLine ($"{(reachable [i] ? " " : "- ")} {instructions [i]}"); + if (!reachable [i]) + Nop (instructions [i]); + } + Console.WriteLine (); +#endif + + // Exterminate, exterminate, exterminate + for (int i = 0; i < reachable.Length; i++) { + if (!reachable [i]) { + Nop (instructions [i]); + modified = true; + } + } + + // Remove exception handlers + if (reachableExceptionHandlers is not null) { + for (int i = reachableExceptionHandlers.Length - 1; i >= 0; i--) { + if (reachableExceptionHandlers [i]) + continue; + caller.Body.ExceptionHandlers.RemoveAt (i); + modified = true; + } + } + + // Remove unreachable instructions (nops) at the end, because the last instruction can only be ret/throw/backwards branch. + for (int i = last_reachable + 1; i < reachable.Length; i++) { + instructions.RemoveAt (last_reachable + 1); + modified = true; + } + + return modified; + } + + static bool GetIsExtensionType (TypeDefinition type) + { + // if 'type' inherits from NSObject inside an assembly that has [GeneratedCode] + // or for static types used for optional members (using extensions methods), they can be optimized too + return type.IsSealed && type.IsAbstract && type.Name.EndsWith ("_Extensions", StringComparison.Ordinal); + } + + public static bool OptimizeMethod (OptimizeGeneratedCodeData data, MethodDefinition method) + { + var modified = false; + + if (!method.HasBody) + return modified; + + if (method.IsBindingImplOptimizableCode (data.LinkContext)) { + // We optimize all methods that have the [BindingImpl (BindingImplAttributes.Optimizable)] attribute. + } else if (method.IsGeneratedCode (data.LinkContext) && ( + GetIsExtensionType (method.DeclaringType) + || IsExport (method))) { + // We optimize methods that have the [GeneratedCodeAttribute] and is either an extension type or an exported method + } else { + // but it would be too risky to apply on user-generated code + return modified; + } + + if (data.Optimizations.InlineIsARM64CallingConvention == true && data.InlineIsArm64CallingConvention.HasValue && method.Name == "GetIsARM64CallingConvention" && method.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) { + // Rewrite to return the constant value + var instr = method.Body.Instructions; + instr.Clear (); + instr.Add (Instruction.Create (data.InlineIsArm64CallingConvention.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); + instr.Add (Instruction.Create (OpCodes.Ret)); + return true; // nothing else to do here. + } + + if (ProcessProtocolInterfaceStaticConstructor (data, method)) + return true; + + var instructions = method.Body.Instructions; + for (int i = 0; i < instructions.Count; i++) { + var ins = instructions [i]; + switch (ins.OpCode.Code) { + case Code.Newobj: + case Code.Call: + modified |= ProcessCalls (data, method, ins, out var instructionsAddedOrRemoved); + i += instructionsAddedOrRemoved; + break; + case Code.Ldsfld: + modified |= ProcessLoadStaticField (data, method, ins); + break; + } + } + + modified |= EliminateDeadCode (data, method); + return modified; + } + + // Returns the number of instructions added (or removed) in the 'instructionsAddedOrRemoved' parameter. + // Returns true if any modifications were done. + static bool ProcessCalls (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) + { + var modified = false; + instructionsAddedOrRemoved = 0; + var mr = ins.Operand as MethodReference; + switch (mr?.Name) { + case "EnsureUIThread": + modified |= ProcessEnsureUIThread (data, caller, ins); + break; + case "get_IsDirectBinding": + modified |= ProcessIsDirectBinding (data, caller, ins); + break; + case "SetupBlock": + case "SetupBlockUnsafe": + modified |= ProcessSetupBlock (data, caller, ins, out instructionsAddedOrRemoved); + break; + case ".ctor": + if (!mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) + break; + return ProcessBlockLiteralConstructor (data, caller, ins, out instructionsAddedOrRemoved); + } + + return modified; + } + + static bool ProcessLoadStaticField (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + var modified = false; + var fr = ins.Operand as FieldReference; + switch (fr?.Name) { + case "IsARM64CallingConvention": + modified |= ProcessIsARM64CallingConvention (data, caller, ins); + break; + case "Arch": + // https://app.asana.com/0/77259014252/77812690163 + modified |= ProcessRuntimeArch (data, caller, ins); + break; + } + return modified; + } + + static bool ProcessEnsureUIThread (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + if (data.Optimizations.RemoveUIThreadChecks != true) + return false; + + // Verify we're checking the right get_EnsureUIThread call + var declaringTypeNamespace = data.LinkContext.App.Platform == Utils.ApplePlatform.MacOSX ? Namespaces.AppKit : Namespaces.UIKit; + var declaringTypeName = data.LinkContext.App.Platform == Utils.ApplePlatform.MacOSX ? "NSApplication" : "UIApplication"; + var mr = ins.Operand as MethodReference; + if (mr is null || !mr.DeclaringType.Is (declaringTypeNamespace, declaringTypeName)) + return false; + + // Verify a few assumptions before doing anything + const string operation = "remove calls to [NS|UI]Application::EnsureUIThread"; + if (!ValidateInstruction (data.App, caller, ins, operation, Code.Call)) + return false; + + // This is simple: just remove the call + Nop (ins); // call void UIKit.UIApplication::EnsureUIThread() + return true; + } + + static bool? IsDirectBindingConstant (OptimizeGeneratedCodeData data, TypeDefinition type) + { + return type.IsNSObject (data.LinkContext) ? type.GetIsDirectBindingConstant (data.LinkContext) : null; + } + + static bool ProcessIsDirectBinding (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + const string operation = "inline IsDirectBinding"; + + if (data.Optimizations.InlineIsDirectBinding != true) + return false; + + bool? isdirectbinding_constant = IsDirectBindingConstant (data, caller.DeclaringType); + + // If we don't know the constant isdirectbinding value, then we can't inline anything + if (!isdirectbinding_constant.HasValue) + return false; + + // Verify we're checking the right get_IsDirectBinding call + var mr = ins.Operand as MethodReference; + if (mr is null || !mr.DeclaringType.Is (Namespaces.Foundation, "NSObject")) + return false; + + // Verify a few assumptions before doing anything + if (!ValidateInstruction (data.App, caller, ins.Previous, operation, Code.Ldarg_0)) + return false; + + if (!ValidateInstruction (data.App, caller, ins, operation, Code.Call)) + return false; + + // Clearing the branch succeeded, so clear the condition too + // ldarg.0 + Nop (ins.Previous); + // call System.Boolean Foundation.NSObject::get_IsDirectBinding() + ins.OpCode = isdirectbinding_constant.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; + ins.Operand = null; + return true; + } + + static bool ProcessSetupBlock (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) + { + instructionsAddedOrRemoved = 0; + if (data.Optimizations.OptimizeBlockLiteralSetupBlock != true) + return false; + + // This will optimize calls to SetupBlock and SetupBlockUnsafe by calculating the signature for the block + // (which both SetupBlock and SetupBlockUnsafe do), and then rewrite the code to call SetupBlockImpl instead + // (which takes the block signature as an argument instead of calculating it). This is required to + // remove the dynamic registrar, because calculating the block signature is done in the dynamic registrar. + // + // This code is a mirror of the code in BlockLiteral.SetupBlock (to calculate the block signature). + var mr = ins.Operand as MethodReference; + if (mr is null || !mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) + return false; + + if (caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral")) { + switch (caller.Name) { + case "GetBlockForDelegate": + case "CreateBlockForDelegate": + // These methods contain a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about. + return false; + } + } + + string? signature = null; + try { + // We need to figure out the type of the first argument to the call to SetupBlock[Impl]. + // + // Example sequence: + // + // ldsfld ObjCRuntime.Trampolines/DJSContextExceptionHandler ObjCRuntime.Trampolines/SDJSContextExceptionHandler::Handler + // ldarg.1 + // call System.Void ObjCRuntime.BlockLiteral::SetupBlockUnsafe(System.Delegate, System.Delegate) + // + + // Locating the instruction that loads the first argument can be complicated, so we simplify by making a few assumptions: + // 1. The instruction immediately before the call instruction (which would load the last argument) is a Push1/Pop0 instruction. + // This avoids running into trouble when the instruction does something else (it could be a any other instruction, which would throw off the next calculations) + // 2. We have a approved list of instructions we know how to calculate the type for, and which we use on the second to last instruction before the call instruction + + // First verify the Push1/Pop0 behavior in point 1. + var prev = ins.Previous; + while (prev.OpCode.Code == Code.Nop) + prev = prev.Previous; // Skip any nops. + if (prev.OpCode.StackBehaviourPush != StackBehaviour.Push1) { + //todo: localize mmp error 2106 + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); + return false; + } else if (prev.OpCode.StackBehaviourPop != StackBehaviour.Pop0) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); + return false; + } + + var loadTrampolineInstruction = prev.Previous; + while (loadTrampolineInstruction.OpCode.Code == Code.Nop) + loadTrampolineInstruction = loadTrampolineInstruction.Previous; // Skip any nops. + + // Then find the type of the previous instruction (the first argument to SetupBlock[Unsafe]) + var trampolineDelegateType = GetPushedType (caller, loadTrampolineInstruction); + if (trampolineDelegateType is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_A, caller, ins.Offset, mr.Name, loadTrampolineInstruction)); + return false; + } + + if (trampolineDelegateType.Is ("System", "Delegate") || trampolineDelegateType.Is ("System", "MulticastDelegate")) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_B, caller, trampolineDelegateType.FullName, mr.Name)); + return false; + } + + if (!data.LinkContext.App.StaticRegistrar.TryComputeBlockSignature (caller, trampolineDelegateType, out var exception, out signature)) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, exception, caller, ins, Errors.MM2106_D, caller, ins.Offset, exception.Message)); + return false; + + } + } catch (Exception e) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); + return false; + } + + // We got the information we need: rewrite the IL. + var instructions = caller.Body.Instructions; + var index = instructions.IndexOf (ins); + // Inject the extra arguments + instructions.Insert (index, Instruction.Create (OpCodes.Ldstr, signature)); + instructions.Insert (index, Instruction.Create (mr.Name == "SetupBlockUnsafe" ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1)); + // Change the call to call SetupBlockImpl instead + ins.Operand = GetBlockSetupImpl (data, caller, ins); + + //data.App.Log (4, "Optimized call to BlockLiteral.SetupBlock in {0} at offset {1} with delegate type {2} and signature {3}", caller, ins.Offset, delegateType.FullName, signature); + instructionsAddedOrRemoved = 2; + return true; + } + + internal static bool IsBlockLiteralCtor_Type_String (MethodDefinition md) + { + if (!md.HasParameters) + return false; + + if (md.Parameters.Count != 4) + return false; + + if (!(md.Parameters [0].ParameterType is PointerType pt) || !pt.ElementType.Is ("System", "Void")) + return false; + + if (!md.Parameters [1].ParameterType.Is ("System", "Object")) + return false; + + if (!md.Parameters [2].ParameterType.Is ("System", "Type")) + return false; + + if (!md.Parameters [3].ParameterType.Is ("System", "String")) + return false; + + return true; + } + + static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) + { + instructionsAddedOrRemoved = 0; + + if (data.Optimizations.OptimizeBlockLiteralSetupBlock != true) + return false; + + // This will optimize calls to this BlockLiteral constructor: + // (void* ptr, object context, Type trampolineType, string trampolineMethod) + // by calculating the signature for the block using the last two arguments, + // and then rewrite the code to call this constructor overload instead: + // (void* ptr, object context, string signature) + // This is required to remove the dynamic registrar, because calculating the block signature + // is done in the dynamic registrar. + // + // This code is a mirror of the code in BlockLiteral.SetupBlock (to calculate the block signature). + var mr = ins.Operand as MethodReference; + if (mr is null || !mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) + return false; + + var md = mr.Resolve (); + if (md is null || !IsBlockLiteralCtor_Type_String (md)) + return false; + + string? signature = null; + Instruction? sequenceStart; + try { + // We need to figure out the last argument to the call to the ctor + // + // Example sequence: + // + // ldarg.0 + // ldarg.1 + // ldtoken ... + // call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle) + // ldstr ... + // newobj BlockLiteral (void*, System.Object, System.Type, System.String) + // + + // Verify 'ldstr ...' + var loadString = GetPreviousSkippingNops (ins); + if (loadString.OpCode != OpCodes.Ldstr) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadString)); + return false; + } + + // Verify 'call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)' + var callGetTypeFromHandle = GetPreviousSkippingNops (loadString); + if (callGetTypeFromHandle.OpCode != OpCodes.Call || !(callGetTypeFromHandle.Operand is MethodReference methodOperand) || methodOperand.Name != "GetTypeFromHandle" || !methodOperand.DeclaringType.Is ("System", "Type")) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, callGetTypeFromHandle)); + return false; + } + + // Verify 'ldtoken ...' + var loadType = GetPreviousSkippingNops (callGetTypeFromHandle); + if (loadType.OpCode != OpCodes.Ldtoken) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType)); + return false; + } + + // Then find the type of the previous instruction + var trampolineContainerTypeReference = loadType.Operand as TypeReference; + if (trampolineContainerTypeReference is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType.Operand)); + return false; + } + + var trampolineContainerType = trampolineContainerTypeReference.Resolve (); + if (trampolineContainerType is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, trampolineContainerTypeReference)); + return false; + } + + // Find the trampoline method + var trampolineMethodName = (string) loadString.Operand; + var trampolineMethods = trampolineContainerType.Methods.Where (v => v.Name == trampolineMethodName).ToArray (); + if (!trampolineMethods.Any ()) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E1 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because no method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); + return false; + } else if (trampolineMethods.Count () > 1) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E2 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because more than one method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); + return false; + } + var trampolineMethod = trampolineMethods [0]; + if (!trampolineMethod.HasParameters || trampolineMethod.Parameters.Count < 1) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_F /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' must have at least one parameter. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); + return false; + } + + // Check that the method's first parameter is either IntPtr, void* or BlockLiteral* + var firstParameterType = trampolineMethod.Parameters [0].ParameterType; + if (firstParameterType.Is ("System", "IntPtr")) { + // ok + } else if (firstParameterType is PointerType ptrType) { + var ptrTargetType = ptrType.ElementType; + if (!(ptrTargetType.Is ("System", "Void") || ptrTargetType.Is ("ObjCRuntime", "BlockLiteral"))) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); + return false; + } + // ok + } else { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); + return false; + } + + // Check that the method has [UnmanagedCallersOnly] + if (!trampolineMethod.HasCustomAttributes || !trampolineMethod.CustomAttributes.Any (v => v.AttributeType.Is ("System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute"))) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_H /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' does not have an [UnmanagedCallersOnly] attribute. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); + return false; + } + + var userDelegateType = data.LinkContext.App.StaticRegistrar.GetUserDelegateType (trampolineMethod); + MethodReference? userMethod = null; + var blockSignature = true; + if (userDelegateType is not null) { + userMethod = data.LinkContext.App.StaticRegistrar.GetDelegateInvoke (userDelegateType); + } else { + userMethod = trampolineMethod; + } + + if (userMethod is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_D, caller, ins.Offset, "Could not find delegate invoke method")); + return false; + } + + // Calculate the block signature. + var parameters = new TypeReference [userMethod.Parameters.Count]; + for (int p = 0; p < parameters.Length; p++) + parameters [p] = userMethod.Parameters [p].ParameterType; + signature = data.LinkContext.App.StaticRegistrar.ComputeSignature (userMethod.DeclaringType, false, userMethod.ReturnType, parameters, userMethod.Resolve (), isBlockSignature: blockSignature); + + sequenceStart = loadType; + } catch (Exception e) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); + return false; + } + + // We got the information we need: rewrite the IL. + var instructions = caller.Body.Instructions; + var index = instructions.IndexOf (sequenceStart); + int instructionDiff = 0; + while (instructions [index] != ins) { + instructions.RemoveAt (index); + instructionDiff--; + } + // Inject the extra arguments + instructions.Insert (index, Instruction.Create (OpCodes.Ldstr, signature)); + instructionDiff++; + // Change the call to call the ctor with the string signature parameter instead + ins.Operand = GetBlockLiteralConstructor (data, caller, ins); + + data.App.Log (4, "Optimized call to BlockLiteral..ctor in {0} at offset {1} with signature {2}", caller, ins.Offset, signature); + instructionsAddedOrRemoved = instructionDiff; + return true; + } + + static Instruction GetPreviousSkippingNops (Instruction ins) + { + do { + ins = ins.Previous; + } while (ins.OpCode == OpCodes.Nop); + return ins; + } + + static Instruction? SkipNops (Instruction? ins) + { + if (ins is null) + return null; + + while (ins.OpCode == OpCodes.Nop) { + if (ins.Next is null) + return null; + ins = ins.Next; + } + return ins; + } + + static bool ProcessIsARM64CallingConvention (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + const string operation = "inline Runtime.IsARM64CallingConvention"; + + if (data.Optimizations.InlineIsARM64CallingConvention != true) + return false; + + if (!data.InlineIsArm64CallingConvention.HasValue) + return false; + + // Verify we're checking the right IsARM64CallingConvention field + var fr = ins.Operand as FieldReference; + if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) + return false; + + if (!ValidateInstruction (data.App, caller, ins, operation, Code.Ldsfld)) + return false; + + // We're fine, inline the Runtime.IsARM64CallingConvention value + ins.OpCode = data.InlineIsArm64CallingConvention.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; + ins.Operand = null; + + return true; + } + + static bool ProcessRuntimeArch (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + const string operation = "inline Runtime.Arch"; + + if (data.Optimizations.InlineRuntimeArch != true) + return false; + + // Verify we're checking the right Arch field + var fr = ins.Operand as FieldReference; + if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) + return false; + + // Verify a few assumptions before doing anything + if (!ValidateInstruction (data.App, caller, ins, operation, Code.Ldsfld)) + return false; + + // We're fine, inline the Runtime.Arch condition + // The enum values are Runtime.DEVICE = 0 and Runtime.SIMULATOR = 1, + ins.OpCode = data.Device ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1; + ins.Operand = null; + return true; + } + + // Returns the type of the value pushed on the stack by the given instruction. + // Returns null for unknown instructions, or for instructions that don't push anything on the stack. + static TypeReference? GetPushedType (MethodDefinition method, Instruction ins) + { + var index = 0; + switch (ins.OpCode.Code) { + case Code.Ldloc_0: + case Code.Ldarg_0: + index = 0; + break; + case Code.Ldloc_1: + case Code.Ldarg_1: + index = 1; + break; + case Code.Ldloc_2: + case Code.Ldarg_2: + index = 2; + break; + case Code.Ldloc_3: + case Code.Ldarg_3: + index = 3; + break; + case Code.Ldloc: + case Code.Ldloc_S: + return ((VariableDefinition) ins.Operand).VariableType; + case Code.Ldarg: + case Code.Ldarg_S: + return ((ParameterDefinition) ins.Operand).ParameterType; + case Code.Ldfld: + case Code.Ldsfld: + return ((FieldReference) ins.Operand).FieldType; + case Code.Call: + case Code.Calli: + case Code.Callvirt: + return ((MethodReference) ins.Operand).ReturnType; + default: + return null; + } + + switch (ins.OpCode.Code) { + case Code.Ldloc: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + return method.Body.Variables [index].VariableType; + case Code.Ldarg: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + if (method.IsStatic) { + return method.Parameters [index].ParameterType; + } else if (index == 0) { + return method.DeclaringType; + } else { + return method.Parameters [index - 1].ParameterType; + } + default: + return null; + } + } + + static MethodReference GetBlockSetupImpl (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + if (data.SetupBlockImplDefinition is null) { + var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); + foreach (var method in type.Methods) { + if (method.Name != "SetupBlockImpl") + continue; + data.SetupBlockImplDefinition = method; + data.SetupBlockImplDefinition.IsPublic = true; // Make sure the method is callable from the optimized code. + break; + } + if (data.SetupBlockImplDefinition is null) + throw ErrorHelper.CreateError (data.LinkContext.App, 99, caller, ins, Errors.MX0099, $"could not find the method {Namespaces.ObjCRuntime}.BlockLiteral.SetupBlockImpl"); + } + return caller.Module.ImportReference (data.SetupBlockImplDefinition); + } + + static MethodReference GetBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + if (data.BlockCtorDefinition is null) { + var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); + foreach (var method in type.Methods) { + if (!method.IsConstructor) + continue; + if (method.IsStatic) + continue; + if (!method.HasParameters || method.Parameters.Count != 3) + continue; + if (!(method.Parameters [0].ParameterType is PointerType pt) || !pt.ElementType.Is ("System", "Void")) + continue; + if (!method.Parameters [1].ParameterType.Is ("System", "Object")) + continue; + if (!method.Parameters [2].ParameterType.Is ("System", "String")) + continue; + data.BlockCtorDefinition = method; + break; + } + if (data.BlockCtorDefinition is null) + throw ErrorHelper.CreateError (data.LinkContext.App, 99, caller, ins, Errors.MX0099, $"could not find the constructor ObjCRuntime.BlockLiteral (void*, object, string)"); + } + return caller.Module.ImportReference (data.BlockCtorDefinition); + } + + static bool ProcessProtocolInterfaceStaticConstructor (OptimizeGeneratedCodeData data, MethodDefinition method) + { + // The static cctor in protocol interfaces exists only to preserve the protocol's members, for inspection by the registrar(s). + // If we're registering protocols, then we don't need to preserve protocol members, because the registrar + // already knows everything about it => we can remove the static cctor. + + if (!(method.DeclaringType.IsInterface && method.IsStatic && method.IsConstructor && method.HasBody)) + return false; + + if (data.Optimizations.RegisterProtocols != true) { + data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: the 'register-protocols' optimization is disabled.", method.DeclaringType.FullName); + return false; + } + + if (!method.DeclaringType.HasCustomAttributes || !method.DeclaringType.CustomAttributes.Any (v => v.AttributeType.Is ("Foundation", "ProtocolAttribute"))) { + data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: no Protocol attribute found.", method.DeclaringType.FullName); + return false; + } + + var ins = SkipNops (method.Body.Instructions.First ()); + if (ins is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + return false; + } else if (ins.OpCode != OpCodes.Ldnull) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + return false; + } + + ins = SkipNops (ins.Next); + if (ins is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + return false; + } + var callGCKeepAlive = ins; + if (callGCKeepAlive.OpCode != OpCodes.Call || !(callGCKeepAlive.Operand is MethodReference methodOperand) || methodOperand.Name != "KeepAlive" || !methodOperand.DeclaringType.Is ("System", "GC")) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + return false; + } + + ins = SkipNops (ins.Next); + if (ins is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + return false; + } else if (ins.OpCode != OpCodes.Ret) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + return false; + } + + ins = SkipNops (ins.Next); + if (ins is not null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + return false; + } + + // We can just remove the entire method, however that confuses the linker later on, so just empty it out and remove all the attributes. + data.App.Log (4, "Optimized static constructor in the protocol interface {0} (static constructor was cleared and custom attributes removed)", method.DeclaringType.FullName); + method.Body.Instructions.Clear (); + method.Body.Instructions.Add (Instruction.Create (OpCodes.Ret)); + + // Only remove DynamicDependency attributes that takes a single string argument. + // The generator generates other DynamicDependency attributes, and we don't want to remove those. + for (var i = method.CustomAttributes.Count - 1; i >= 0; i--) { + var ca = method.CustomAttributes [i]; + + if (!ca.AttributeType.Is ("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute")) + continue; + + if (!ca.HasConstructorArguments) + continue; + + if (ca.ConstructorArguments.Count != 1) + continue; + + if (!ca.ConstructorArguments [0].Type.Is ("System", "String")) + continue; + + method.CustomAttributes.RemoveAt (i); + } + + return true; + } + } + + public class OptimizeGeneratedCodeData { + public required Xamarin.Tuner.DerivedLinkContext LinkContext; + public required Optimizations Optimizations; + public required bool Device; + + public MethodDefinition? SetupBlockImplDefinition; + public MethodDefinition? BlockCtorDefinition; + public bool? InlineIsArm64CallingConvention; + + public Application App => LinkContext.App; + } + +} diff --git a/tools/linker/RegistrarRemovalTrackingStep.cs b/tools/linker/RegistrarRemovalTrackingStep.cs index b2cd10bc170c..c295752c86cc 100644 --- a/tools/linker/RegistrarRemovalTrackingStep.cs +++ b/tools/linker/RegistrarRemovalTrackingStep.cs @@ -127,7 +127,7 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) break; case ".ctor": if (mr.Resolve () is MethodDefinition md) - requires |= Xamarin.Linker.OptimizeGeneratedCodeHandler.IsBlockLiteralCtor_Type_String (md); + requires |= Xamarin.Linker.OptimizeGeneratedCode.IsBlockLiteralCtor_Type_String (md); if (requires && warnIfRequired) Warn (assembly, mr); break; From 01d6ef406087e4b3895faf41b0e290812b6ade23 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:08:36 +0200 Subject: [PATCH 88/97] [dotnet-linker] Refactor LinkerConfiguration to support read/write. Refactor the big switch statement in the LinkerConfiguration constructor into a GetConfigurator() method that returns a dictionary of (Load, Save) delegate pairs for each configuration key. This enables both reading configuration from file and writing/serializing the current configuration state. Also add a Save() method that uses the configurator to write out the current configuration state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/common/Application.cs | 2 + tools/dotnet-linker/LinkerConfiguration.cs | 770 +++++++++++++-------- 2 files changed, 486 insertions(+), 286 deletions(-) diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 03f0d58bc48c..05ffef60c8d9 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -152,6 +152,7 @@ public bool IsDefaultMarshalManagedExceptionMode { // How Mono should be embedded into the app. #if !LEGACY_TOOLS AssemblyBuildTarget? libmono_link_mode; + public bool HasLibMonoLinkMode => libmono_link_mode.HasValue; public AssemblyBuildTarget LibMonoLinkMode { get { if (!libmono_link_mode.HasValue) @@ -165,6 +166,7 @@ public AssemblyBuildTarget LibMonoLinkMode { // How libxamarin should be embedded into the app. AssemblyBuildTarget? libxamarin_link_mode; + public bool HasLibXamarinLinkMode => libxamarin_link_mode.HasValue; public AssemblyBuildTarget LibXamarinLinkMode { get { if (!libxamarin_link_mode.HasValue) diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index c0152c375d5b..41d25c990fa0 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -115,6 +115,471 @@ public static LinkerConfiguration GetInstance (LinkContext context) return instance; } + public delegate void LoadValue (string key, string value); + public delegate void SaveValue (string key, List storage); + + delegate void LoadBool (string key, string value, out bool result); + delegate void LoadNullableBool (string key, string value, out bool? result); + + public class Configurator : Dictionary { } + + Configurator GetConfigurator (string linker_file) + { + var saveNonEmpty = new Action> ((key, value, storage) => { + if (string.IsNullOrEmpty (value)) + return; + storage.Add ($"{key}={value}"); + }); + var saveNullableBool = new Action> ((key, value, storage) => { + if (!value.HasValue) + return; + storage.Add ($"{key}={(value.Value ? "true" : "false")}"); + }); + var saveOptionalDefaultFalseBool = new Action> ((key, value, storage) => { + if (!value) + return; + storage.Add ($"{key}=true"); + }); + var loadBool = new LoadBool ((string key, string value, out bool result) => { + result = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); + }); + var loadNullableBool = new LoadNullableBool ((key, value, out result) => { + if (!TryParseOptionalBoolean (value, out result)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + }); + var loadWarningLevel = new Action ((key, value, level) => { + try { + ErrorHelper.ParseWarningLevel (Application, level, value); + } catch (Exception ex) { + throw new InvalidOperationException ($"Invalid {key} '{value}' in {linker_file}", ex); + } + }); + var saveWarningLevel = new Action, ErrorHelper.WarningLevel> ((key, storage, level) => { + var warningLevels = ErrorHelper.GetWarningLevels (Application); + if (warningLevels is null) + return; + foreach (var kvp in warningLevels.Where (v => v.Value == level).OrderBy (v => v.Key)) { + if (kvp.Key == -1) { + storage.Add (key); + } else { + storage.Add ($"{key}={kvp.Key}"); + } + } + }); + + var dict = new Configurator () { + { "AreAnyAssembliesTrimmed", ( + new LoadValue ((key, value) => Application.AreAnyAssembliesTrimmed = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.AreAnyAssembliesTrimmed ? "true" : "false")}")) + )}, + { "AssemblyName", ( + // This is the _AssemblyName MSBuild property for the main project (which is also the root/entry assembly) + new LoadValue ((key, value) => Application.RootAssemblies.Add (value)), + new SaveValue ((key, storage) => storage.AddRange (Application.RootAssemblies.Select (v => $"{key}={v}"))) + )}, + { "AOTArgument", ( + new LoadValue ((key, value) => + { + if (!string.IsNullOrEmpty (value)) + Application.AotArguments.Add (value); + }), + new SaveValue ((key, storage) => + storage.AddRange (Application.AotArguments.Where (v => !string.IsNullOrEmpty (v)).Select (v => $"{key}={v}"))) + )}, + { "AOTCompiler", ( + new LoadValue ((key, value) => AOTCompiler = value), + new SaveValue ((key, storage) => saveNonEmpty (key, AOTCompiler, storage)) + )}, + { "AOTOutputDirectory", ( + new LoadValue ((key, value) => AOTOutputDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, AOTOutputDirectory, storage)) + )}, + { "AppBundleManifestPath", ( + new LoadValue ((key, value) => Application.InfoPListPath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.InfoPListPath, storage)) + )}, + { "CacheDirectory", ( + new LoadValue ((key, value) => CacheDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, CacheDirectory, storage)) + )}, + { "CustomLinkFlags", ( + new LoadValue ((key, value) => Application.ParseCustomLinkFlags (value, "gcc_flags")), + new SaveValue ((key, storage) => storage.AddRange (Application.CustomLinkFlags?.Select (v => $"{key}={v}") ?? [])) + )}, + { "Debug", ( + new LoadValue ((key, value) => Application.EnableDebug = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.EnableDebug ? "true" : "false")}")) + )}, + { "DedupAssembly", ( + new LoadValue ((key, value) => DedupAssembly = value), + new SaveValue ((key, storage) => saveNonEmpty (key, DedupAssembly, storage)) + )}, + { "DeploymentTarget", ( + new LoadValue ((key, value) => { + if (!Version.TryParse (value, out var deployment_target)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + DeploymentTarget = deployment_target; + }), + new SaveValue ((key, storage) => saveNonEmpty (key, DeploymentTarget?.ToString (), storage)) + )}, + { "Dlsym", ( + new LoadValue ((key, value) => Application.ParseDlsymOptions (value)), + new SaveValue ((key, storage) => { + switch (Application.DlsymOptions) { + case DlsymOptions.None: + storage.Add ($"Dlsym=false"); + break; + case DlsymOptions.All: + storage.Add ($"Dlsym=true"); + break; + case DlsymOptions.Custom: + if (Application.DlsymAssemblies is not null) + storage.Add ($"Dlsym={string.Join (",", Application.DlsymAssemblies.Select (v => (v.Item2 ? "+" : "-") + v.Item1 + ".dll"))}"); + break; + case DlsymOptions.Default: + // don't store default + break; + default: + throw new InvalidOperationException ($"Unknown DlsymOptions value: {Application.DlsymOptions}"); + } + }) + )}, + { "EnableSGenConc", ( + new LoadValue ((key, value) => Application.EnableSGenConc = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.EnableSGenConc ? "true" : "false")}")) + )}, + { "EnvironmentVariable", ( + // Format is either of: + // NAME=VALUE + // Overwrite=BOOL|NAME=VALUE + new LoadValue ((key, value) => { + var overwrite = true; + var needle = "Overwrite="; + if (value.StartsWith (needle, StringComparison.Ordinal)) { + var pipe = value.IndexOf ('|', needle.Length); + if (pipe > 0) { + var overwriteString = value [needle.Length..pipe]; + if (!TryParseOptionalBoolean (overwriteString, out var parsedOverwrite)) + throw new InvalidOperationException ($"Unable to parse the 'Overwrite' value '{overwriteString}' for the environment variable entry '{value}' in {linker_file}"); + overwrite = parsedOverwrite.Value; + value = value [(pipe + 1)..]; + } + } + var separators = new char [] { ':', '=' }; + var equals = value.IndexOfAny (separators); + var name = value.Substring (0, equals); + var val = value.Substring (equals + 1); + Application.EnvironmentVariables.Add (name, new (val, overwrite)); + }), + new SaveValue ((key, storage) => storage.AddRange (Application.EnvironmentVariables.Select (v => $"{key}=Overwrite={v.Value.Overwrite}|{v.Key}={v.Value.Value}").OrderBy (v => v))) + )}, + { "FrameworkAssembly", ( + new LoadValue ((key, value) => FrameworkAssemblies.Add (value)), + new SaveValue ((key, storage) => storage.AddRange (FrameworkAssemblies.OrderBy (v => v).Select (v => $"{key}={v}"))) + )}, + { "InlineDlfcnMethods", ( + new LoadValue ((key, value) => { + if (Enum.TryParse (value, true, out var inlineDlfcnMode)) + InlineDlfcnMethods = inlineDlfcnMode; + else if (string.IsNullOrEmpty (value)) + InlineDlfcnMethods = InlineDlfcnMethodsMode.Disabled; + else + throw new InvalidOperationException ($"Unknown InlineDlfcnMethods value: {value}"); + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={InlineDlfcnMethods}")) + )}, + { "InlineClassGetHandle", ( + new LoadValue ((key, value) => { + if (Enum.TryParse (value, true, out var inlineClassGetHandleMode)) + InlineClassGetHandle = inlineClassGetHandleMode; + else if (string.IsNullOrEmpty (value)) + InlineClassGetHandle = InlineClassGetHandleMode.Disabled; + else + throw new InvalidOperationException ($"Unknown InlineClassGetHandle value: {value}"); + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={InlineClassGetHandle}")) + )}, + { "Interpreter", ( + new LoadValue ((key, value) => { + if (!string.IsNullOrEmpty (value)) + Application.ParseInterpreter (value); + }), + new SaveValue ((key, storage) => { + if (!Application.UseInterpreter) + return; + storage.Add ($"{key}={string.Join (",", Application.InterpretedAssemblies)}"); + }) + )}, + { "IntermediateLinkDir", ( + new LoadValue ((key, value) => IntermediateLinkDir = value), + new SaveValue ((key, storage) => saveNonEmpty (key, IntermediateLinkDir, storage)) + )}, + { "IntermediateOutputPath", ( + new LoadValue ((key, value) => IntermediateOutputPath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, IntermediateOutputPath, storage)) + )}, + { "IsAppExtension", ( + new LoadValue ((key, value) => Application.IsExtension = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.IsExtension ? "true" : "false")}")) + )}, + { "ItemsDirectory", ( + new LoadValue ((key, value) => ItemsDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, ItemsDirectory, storage)) + )}, + { "IsSimulatorBuild", ( + new LoadValue ((key, value) => IsSimulatorBuild = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(IsSimulatorBuild ? "true" : "false")}")) + )}, + { "LibMonoLinkMode", ( + new LoadValue ((key, value) => Application.LibMonoLinkMode = ParseLinkMode (value, key)), + new SaveValue ((key, storage) => { if (Application.HasLibMonoLinkMode) storage.Add ($"{key}={Application.LibMonoLinkMode}"); }) + )}, + { "LibXamarinLinkMode", ( + new LoadValue ((key, value) => Application.LibXamarinLinkMode = ParseLinkMode (value, key)), + new SaveValue ((key, storage) => { if (Application.HasLibXamarinLinkMode) storage.Add ($"{key}={Application.LibXamarinLinkMode}"); }) + )}, + { "MarshalManagedExceptionMode", ( + new LoadValue ((key, value) => { + if (!string.IsNullOrEmpty (value)) { + if (!Application.TryParseManagedExceptionMode (value, out var mode)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.MarshalManagedExceptions = mode; + } + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={Application.MarshalManagedExceptions.ToString ().ToLowerInvariant ()}")) + )}, + { "MarshalObjectiveCExceptionMode", ( + new LoadValue ((key, value) => { + if (!string.IsNullOrEmpty (value)) { + if (!Application.TryParseObjectiveCExceptionMode (value, out var mode)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.MarshalObjectiveCExceptions = mode; + } + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={Application.MarshalObjectiveCExceptions.ToString ().ToLowerInvariant ()}")) + )}, + { "MonoLibrary", ( + new LoadValue ((key, value) => Application.MonoLibraries.Add (value)), + new SaveValue ((key, storage) => storage.AddRange (Application.MonoLibraries.OrderBy (v => v).Select (v => $"{key}={v}"))) + )}, + { "MtouchFloat32", ( + new LoadValue ((key, value) => loadNullableBool (key, value, out Application.AotFloat32)), + new SaveValue ((key, storage) => saveNullableBool (key, Application.AotFloat32, storage)) + )}, + { "NoWarn", ( + new LoadValue ((key, value) => loadWarningLevel (key, value, ErrorHelper.WarningLevel.Disable)), + new SaveValue ((key, storage) => saveWarningLevel (key, storage, ErrorHelper.WarningLevel.Disable)) + )}, + { "Optimize", ( + new LoadValue ((key, value) => user_optimize_flags = value), + new SaveValue ((key, storage) => saveNonEmpty (key, user_optimize_flags, storage)) + )}, + { "PartialStaticRegistrarLibrary", ( + new LoadValue ((key, value) => PartialStaticRegistrarLibrary = value), + new SaveValue ((key, storage) => saveNonEmpty (key, PartialStaticRegistrarLibrary, storage)) + )}, + { "Platform", ( + new LoadValue ((key, value) => Platform = ApplePlatformExtensions.Parse (value)), + new SaveValue ((key, storage) => storage.Add ($"{key}={Platform.AsString ()}")) + )}, + { "PlatformAssembly", ( + new LoadValue ((key, value) => PlatformAssembly = Path.GetFileNameWithoutExtension (value)), + new SaveValue ((key, storage) => saveNonEmpty (key, string.IsNullOrEmpty (PlatformAssembly) ? PlatformAssembly : PlatformAssembly + ".dll", storage)) + )}, + { "PrepareAssemblies", ( + new LoadValue ((key, value) => loadBool (key, value, out Application.PrepareAssemblies)), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, Application.PrepareAssemblies, storage)) + )}, + { "PublishTrimmed", ( + new LoadValue ((key, value) => PublishTrimmed = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(PublishTrimmed ? "true" : "false")}")) + )}, + { "ReferenceNativeSymbol", ( + new LoadValue ((key, value) => { + (string symbolType, string symbolMode, string symbol) = SplitString3 (value, ':'); + var mode = SymbolMode.Default; + switch (symbolMode) { + case "Ignore": + mode = SymbolMode.Ignore; + break; + case "": + break; + default: + throw new InvalidOperationException ($"Unknown symbol mode '{symbolMode}' for symbol '{symbol}'. Expected 'Ignore' or nothing at all."); + } + switch (symbolType) { + case "Function": + DerivedLinkContext.RequiredSymbols.AddFunction (symbol, mode); + break; + case "ObjectiveCClass": + DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (symbol, mode); + break; + case "Field": + DerivedLinkContext.RequiredSymbols.AddField (symbol, mode); + break; + default: + throw new InvalidOperationException ($"Unknown symbol type '{symbolType}' for symbol '{symbol}'. Expected 'Function', 'ObjectiveCClass', or 'Field'."); + } + }), + new SaveValue ((key, storage) => { + foreach (var symbol in DerivedLinkContext.RequiredSymbols) { + var mode = symbol.Mode == SymbolMode.Ignore ? "Ignore" : ""; + switch (symbol.Type) { + case SymbolType.Function: + case SymbolType.ObjectiveCClass: + case SymbolType.Field: + storage.Add ($"{key}={symbol.Type}:{mode}:{symbol.Name}"); + break; + default: + throw new InvalidOperationException ($"Unknown symbol type '{symbol.Type}' for symbol '{symbol.Name}'. Expected 'Function', 'ObjectiveCClass', or 'Field'."); + } + } + }) + )}, + { "RelativeAppBundlePath", ( + new LoadValue ((key, value) => RelativeAppBundlePath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, RelativeAppBundlePath, storage)) + )}, + { "Registrar", ( + new LoadValue ((key, value) => Application.ParseRegistrar (value)), + new SaveValue ((key, storage) => { + if (Application.Registrar == RegistrarMode.Default) + return; + storage.Add ($"{key}={Application.Registrar}"); + }) + )}, + { "RequireLinkWithAttributeForObjectiveCClassSearch", ( + new LoadValue ((key, value) => { + if (!TryParseOptionalBoolean (value, out var require_link_with_attribute_for_objectivec_class_search, defaultValue: false)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.RequireLinkWithAttributeForObjectiveCClassSearch = require_link_with_attribute_for_objectivec_class_search.Value; + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.RequireLinkWithAttributeForObjectiveCClassSearch ? "true" : "false")}")) + )}, + { "RequirePInvokeWrappers", ( + new LoadValue ((key, value) => { + if (!TryParseOptionalBoolean (value, out var require_pinvoke_wrappers, defaultValue: false)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.RequiresPInvokeWrappers = require_pinvoke_wrappers.Value; + }), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, Application.RequiresPInvokeWrappers, storage)) + )}, + { "RuntimeConfigurationFile", ( + new LoadValue ((key, value) => Application.RuntimeConfigurationFile = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.RuntimeConfigurationFile, storage)) + )}, + { "SdkDevPath", ( + new LoadValue ((key, value) => Application.SdkRoot = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.SdkRoot, storage)) + )}, + { "SdkRootDirectory", ( + new LoadValue ((key, value) => { + SdkRootDirectory = value; + Application.FrameworkCurrentDirectory = value; + }), + new SaveValue ((key, storage) => saveNonEmpty (key, SdkRootDirectory, storage)) + )}, + { "SdkVersion", ( + new LoadValue ((key, value) => { + if (!Version.TryParse (value, out var sdk_version)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + SdkVersion = sdk_version; + }), + new SaveValue ((key, storage) => saveNonEmpty (key, SdkVersion?.ToString (), storage)) + )}, + { "SkipMarkingNSObjectsInUserAssemblies", ( + new LoadValue ((key, value) => { + if (!TryParseOptionalBoolean (value, out var skip_marking_nsobjects_in_user_assemblies)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.SkipMarkingNSObjectsInUserAssemblies = skip_marking_nsobjects_in_user_assemblies.Value; + }), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, Application.SkipMarkingNSObjectsInUserAssemblies, storage)) + )}, + { "TargetArchitectures", ( + new LoadValue ((key, value) => { + if (!Enum.TryParse (value, out var abi)) + throw new InvalidOperationException ($"Unknown target architectures: {value} in {linker_file}"); + Abi = abi | (Abi & Abi.LLVM); // Preserve the LLVM flag if it was set, since TargetArchitectures is orthogonal to LLVM + }), + new SaveValue ((key, storage) => saveNonEmpty (key, (Abi & ~Abi.LLVM).ToString (), storage)) + )}, + { "TargetFramework", ( + new LoadValue ((key, value) => { + if (!TargetFramework.TryParse (value, out var tf)) + throw new InvalidOperationException ($"Invalid TargetFramework '{value}' in {linker_file}"); + Application.TargetFramework = TargetFramework.Parse (value); + }), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.TargetFramework.ToString (), storage)) + )}, + { "TypeMapAssemblyName", ( + new LoadValue ((key, value) => Application.TypeMapAssemblyName = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapAssemblyName, storage)) + )}, + { "TypeMapFilePath", ( + new LoadValue ((key, value) => TypeMapFilePath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, TypeMapFilePath, storage)) + )}, + { "TypeMapOutputDirectory", ( + new LoadValue ((key, value) => Application.TypeMapOutputDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapOutputDirectory, storage)) + )}, + { "UseLlvm", ( + new LoadValue ((key, value) => { + var use_llvm = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); + if (use_llvm) { + Abi |= Abi.LLVM; + } else { + Abi &= ~Abi.LLVM; + } + }), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, Abi.HasFlag (Abi.LLVM), storage)) + )}, + { "Verbosity", ( + new LoadValue ((key, value) => { + if (!int.TryParse (value, out var verbosity)) + throw new InvalidOperationException ($"Invalid Verbosity '{value}' in {linker_file}"); + Application.Verbosity = verbosity; + }), + new SaveValue ((key, storage) => { + if (Application.Verbosity != 0) + storage.Add ($"{key}={Application.Verbosity}"); + }) + )}, + { "Warn", ( + new LoadValue ((key, value) => loadWarningLevel (key, value, ErrorHelper.WarningLevel.Warning)), + new SaveValue ((key, storage) => saveWarningLevel (key, storage, ErrorHelper.WarningLevel.Warning)) + )}, + { "WarnAsError", ( + new LoadValue ((key, value) => loadWarningLevel (key, value, ErrorHelper.WarningLevel.Error)), + new SaveValue ((key, storage) => saveWarningLevel (key, storage, ErrorHelper.WarningLevel.Error)) + )}, + { "XamarinRuntime", ( + new LoadValue ((key, value) => { + if (!Enum.TryParse (value, out var rv)) + throw new InvalidOperationException ($"Invalid XamarinRuntime '{value}' in {linker_file}"); + Application.XamarinRuntime = rv; + }), + new SaveValue ((key, storage) => { + storage.Add ($"{key}={Application.XamarinRuntime}"); + }) + )}, + { "InvariantGlobalization", ( + new LoadValue ((key, value) => InvariantGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, InvariantGlobalization, storage)) + )}, + { "HybridGlobalization", ( + new LoadValue ((key, value) => HybridGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, HybridGlobalization, storage)) + )}, + { "XamarinNativeLibraryDirectory", ( + new LoadValue ((key, value) => XamarinNativeLibraryDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, XamarinNativeLibraryDirectory, storage)) + )}, + }; + + return dict; + } + LinkerConfiguration (string linker_file) { if (!File.Exists (linker_file)) @@ -126,6 +591,7 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application = new Application (this); CompilerFlags = new CompilerFlags (Application); + var configurator = GetConfigurator (linker_file); var lines = File.ReadAllLines (linker_file); var significantLines = new List (); // This is the input the cache uses to verify if the cache is still valid for (var i = 0; i < lines.Length; i++) { @@ -145,289 +611,10 @@ public static LinkerConfiguration GetInstance (LinkContext context) if (string.IsNullOrEmpty (value)) continue; - switch (key) { - case "AreAnyAssembliesTrimmed": - Application.AreAnyAssembliesTrimmed = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "AssemblyName": - // This is the AssemblyName MSBuild property for the main project (which is also the root/entry assembly) - Application.RootAssemblies.Add (value); - break; - case "AOTArgument": - if (!string.IsNullOrEmpty (value)) - Application.AotArguments.Add (value); - break; - case "AOTCompiler": - AOTCompiler = value; - break; - case "AOTOutputDirectory": - AOTOutputDirectory = value; - break; - case "DedupAssembly": - DedupAssembly = value; - break; - case "CacheDirectory": - CacheDirectory = value; - break; - case "AppBundleManifestPath": - Application.InfoPListPath = value; - break; - case "CustomLinkFlags": - Application.ParseCustomLinkFlags (value, "gcc_flags"); - break; - case "Debug": - Application.EnableDebug = string.Equals (value, "true", StringComparison.OrdinalIgnoreCase); - break; - case "DeploymentTarget": - if (!Version.TryParse (value, out var deployment_target)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - DeploymentTarget = deployment_target; - break; - case "Dlsym": - Application.ParseDlsymOptions (value); - break; - case "EnableSGenConc": - Application.EnableSGenConc = string.Equals (value, "true", StringComparison.OrdinalIgnoreCase); - break; - case "EnvironmentVariable": - var overwrite = true; - var needle = "Overwrite="; - if (value.StartsWith (needle, StringComparison.Ordinal)) { - var pipe = value.IndexOf ('|', needle.Length); - if (pipe > 0) { - var overwriteString = value [needle.Length..pipe]; - if (!TryParseOptionalBoolean (overwriteString, out var parsedOverwrite)) - throw new InvalidOperationException ($"Unable to parse the 'Overwrite' value '{overwriteString}' for the environment variable entry '{value}' in {linker_file}"); - overwrite = parsedOverwrite.Value; - value = value [(pipe + 1)..]; - } - } - var separators = new char [] { ':', '=' }; - var equals = value.IndexOfAny (separators); - var name = value.Substring (0, equals); - var val = value.Substring (equals + 1); - Application.EnvironmentVariables.Add (name, new (val, overwrite)); - break; - case "FrameworkAssembly": - FrameworkAssemblies.Add (value); - break; - case "InlineClassGetHandle": - if (Enum.TryParse (value, true, out var inlineClassGetHandleMode)) - InlineClassGetHandle = inlineClassGetHandleMode; - else if (string.IsNullOrEmpty (value)) - InlineClassGetHandle = InlineClassGetHandleMode.Disabled; - else - throw new InvalidOperationException ($"Unknown InlineClassGetHandle value: {value}"); - break; - case "InlineDlfcnMethods": - if (Enum.TryParse (value, true, out var inlineDlfcnMode)) - InlineDlfcnMethods = inlineDlfcnMode; - else if (string.IsNullOrEmpty (value)) - InlineDlfcnMethods = InlineDlfcnMethodsMode.Disabled; - else - throw new InvalidOperationException ($"Unknown InlineDlfcnMethods value: {value}"); - break; - case "IntermediateLinkDir": - IntermediateLinkDir = value; - break; - case "IntermediateOutputPath": - IntermediateOutputPath = value; - break; - case "Interpreter": - if (!string.IsNullOrEmpty (value)) - Application.ParseInterpreter (value); - break; - case "IsAppExtension": - Application.IsExtension = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "ItemsDirectory": - ItemsDirectory = value; - break; - case "IsSimulatorBuild": - IsSimulatorBuild = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "LibMonoLinkMode": - Application.LibMonoLinkMode = ParseLinkMode (value, key); - break; - case "LibXamarinLinkMode": - Application.LibXamarinLinkMode = ParseLinkMode (value, key); - break; - case "MarshalManagedExceptionMode": - if (!string.IsNullOrEmpty (value)) { - if (!Application.TryParseManagedExceptionMode (value, out var mode)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.MarshalManagedExceptions = mode; - } - break; - case "MarshalObjectiveCExceptionMode": - if (!string.IsNullOrEmpty (value)) { - if (!Application.TryParseObjectiveCExceptionMode (value, out var mode)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.MarshalObjectiveCExceptions = mode; - } - break; - case "MonoLibrary": - Application.MonoLibraries.Add (value); - break; - case "MtouchFloat32": - if (!TryParseOptionalBoolean (value, out Application.AotFloat32)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - break; - case "NoWarn": - try { - ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Disable, value); - } catch (Exception ex) { - throw new InvalidOperationException ($"Invalid WarnAsError '{value}' in {linker_file}", ex); - } - break; - case "Optimize": - user_optimize_flags = value; - break; - case "PartialStaticRegistrarLibrary": - PartialStaticRegistrarLibrary = value; - break; - case "Platform": - switch (value) { - case "iOS": - Platform = ApplePlatform.iOS; - break; - case "tvOS": - Platform = ApplePlatform.TVOS; - break; - case "macOS": - Platform = ApplePlatform.MacOSX; - break; - case "MacCatalyst": - Platform = ApplePlatform.MacCatalyst; - break; - default: - throw new InvalidOperationException ($"Unknown platform: {value} for the entry {line} in {linker_file}"); - } - break; - case "PlatformAssembly": - PlatformAssembly = Path.GetFileNameWithoutExtension (value); - break; - case "ReferenceNativeSymbol": { - (string symbolType, string symbolMode, string symbol) = SplitString3 (value, ':'); - var mode = SymbolMode.Default; - switch (symbolMode) { - case "Ignore": - mode = SymbolMode.Ignore; - break; - case "": - break; - default: - throw new InvalidOperationException ($"Unknown symbol mode '{symbolMode}' for symbol '{symbol}'. Expected 'Ignore' or nothing at all."); - } - switch (symbolType) { - case "Function": - DerivedLinkContext.RequiredSymbols.AddFunction (symbol, mode); - break; - case "ObjectiveCClass": - DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (symbol, mode); - break; - case "Field": - DerivedLinkContext.RequiredSymbols.AddField (symbol, mode); - break; - default: - throw new InvalidOperationException ($"Unknown symbol type '{symbolType}' for symbol '{symbol}'. Expected 'Function', 'ObjectiveCClass', or 'Field'."); - } - break; - } - case "RelativeAppBundlePath": - RelativeAppBundlePath = value; - break; - case "Registrar": - Application.ParseRegistrar (value); - break; - case "RequireLinkWithAttributeForObjectiveCClassSearch": - if (!string.IsNullOrEmpty (value)) { // The default is 'false' - if (!TryParseOptionalBoolean (value, out var require_link_with_attribute_for_objectivec_class_search)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.RequireLinkWithAttributeForObjectiveCClassSearch = require_link_with_attribute_for_objectivec_class_search.Value; - } - break; - case "RequirePInvokeWrappers": - if (!TryParseOptionalBoolean (value, out var require_pinvoke_wrappers)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.RequiresPInvokeWrappers = require_pinvoke_wrappers.Value; - break; - case "RuntimeConfigurationFile": - Application.RuntimeConfigurationFile = value; - break; - case "SdkDevPath": - Application.SdkRoot = value; - break; - case "SdkRootDirectory": - SdkRootDirectory = value; - Application.FrameworkCurrentDirectory = value; - break; - case "SdkVersion": - if (!Version.TryParse (value, out var sdk_version)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - SdkVersion = sdk_version; - break; - case "SkipMarkingNSObjectsInUserAssemblies": - if (!TryParseOptionalBoolean (value, out var skip_marking_nsobjects_in_user_assemblies)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.SkipMarkingNSObjectsInUserAssemblies = skip_marking_nsobjects_in_user_assemblies.Value; - break; - case "TargetArchitectures": - if (!Enum.TryParse (value, out Abi)) - throw new InvalidOperationException ($"Unknown target architectures: {value} in {linker_file}"); - break; - case "TargetFramework": - if (!TargetFramework.TryParse (value, out var tf)) - throw new InvalidOperationException ($"Invalid TargetFramework '{value}' in {linker_file}"); - Application.TargetFramework = TargetFramework.Parse (value); - break; - case "TypeMapAssemblyName": - Application.TypeMapAssemblyName = value; - break; - case "TypeMapFilePath": - TypeMapFilePath = value; - break; - case "TypeMapOutputDirectory": - Application.TypeMapOutputDirectory = value; - break; - case "UseLlvm": - use_llvm = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "Verbosity": - if (!int.TryParse (value, out var verbosity)) - throw new InvalidOperationException ($"Invalid Verbosity '{value}' in {linker_file}"); - Application.Verbosity += verbosity; - break; - case "Warn": - try { - ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Warning, value); - } catch (Exception ex) { - throw new InvalidOperationException ($"Invalid Warn '{value}' in {linker_file}", ex); - } - break; - case "WarnAsError": - try { - ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Error, value); - } catch (Exception ex) { - throw new InvalidOperationException ($"Invalid WarnAsError '{value}' in {linker_file}", ex); - } - break; - case "XamarinRuntime": - if (!Enum.TryParse (value, out var rv)) - throw new InvalidOperationException ($"Invalid XamarinRuntime '{value}' in {linker_file}"); - Application.XamarinRuntime = rv; - break; - case "InvariantGlobalization": - InvariantGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "HybridGlobalization": - HybridGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "XamarinNativeLibraryDirectory": - XamarinNativeLibraryDirectory = value; - break; - default: - throw new InvalidOperationException ($"Unknown key '{key}' in {linker_file}"); + if (configurator.TryGetValue (key, out var actions)) { + actions.Load (key, value); + } else { + throw new InvalidOperationException ($"Unknown configuration key '{key}' in {linker_file} at line {i + 1}."); } } @@ -457,8 +644,11 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application.BuildTarget = IsSimulatorBuild ? BuildTarget.Simulator : BuildTarget.Device; break; case ApplePlatform.MacOSX: - default: + case ApplePlatform.MacCatalyst: break; + + default: + throw new System.InvalidOperationException ($"Unknown platform: {Platform}"); } if (Application.TargetFramework.Platform != Platform) @@ -475,6 +665,14 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application.Initialize (); } + public void Save (List storage) + { + var configurator = GetConfigurator (LinkerFile); + foreach (var kvp in configurator.OrderBy (v => v.Key)) { + kvp.Value.Save (kvp.Key, storage); + } + } + // Splits a string in three based on the split character. // "a:b" => "a", "b", "" // "a:b:c" => "a", "b", "c" @@ -496,12 +694,12 @@ public static LinkerConfiguration GetInstance (LinkContext context) } - bool TryParseOptionalBoolean (string input, [NotNullWhen (true)] out bool? value) + bool TryParseOptionalBoolean (string input, [NotNullWhen (true)] out bool? value, bool defaultValue = true) { value = null; if (string.IsNullOrEmpty (input)) { - value = true; + value = defaultValue; return true; } From 8a10ef2d5be0ada229604d1eed396236781f0670 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:37:05 +0200 Subject: [PATCH 89/97] [tools] VSCode tasks. --- msbuild/.vscode/tasks.json | 17 +++++++++++++++++ tools/.vscode/tasks.json | 17 +++++++++++++++++ tools/ap-launcher/.vscode/tasks.json | 17 +++++++++++++++++ tools/assembly-preparer/.vscode/tasks.json | 17 +++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 msbuild/.vscode/tasks.json create mode 100644 tools/.vscode/tasks.json create mode 100644 tools/ap-launcher/.vscode/tasks.json create mode 100644 tools/assembly-preparer/.vscode/tasks.json diff --git a/msbuild/.vscode/tasks.json b/msbuild/.vscode/tasks.json new file mode 100644 index 000000000000..45465f774797 --- /dev/null +++ b/msbuild/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "label": "make" + } + ] +} diff --git a/tools/.vscode/tasks.json b/tools/.vscode/tasks.json new file mode 100644 index 000000000000..45465f774797 --- /dev/null +++ b/tools/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "label": "make" + } + ] +} diff --git a/tools/ap-launcher/.vscode/tasks.json b/tools/ap-launcher/.vscode/tasks.json new file mode 100644 index 000000000000..69a9b5f361b2 --- /dev/null +++ b/tools/ap-launcher/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "label": "build" + } + ] +} diff --git a/tools/assembly-preparer/.vscode/tasks.json b/tools/assembly-preparer/.vscode/tasks.json new file mode 100644 index 000000000000..69a9b5f361b2 --- /dev/null +++ b/tools/assembly-preparer/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "label": "build" + } + ] +} From 3d3b7d29bbd3626fe0a43b5a6085abfb2794d989 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:51:04 +0200 Subject: [PATCH 90/97] [tools] Add link to NoWarn issue --- tools/dotnet-linker/LinkerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 41d25c990fa0..1ad78a1a8313 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -366,7 +366,7 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => loadNullableBool (key, value, out Application.AotFloat32)), new SaveValue ((key, storage) => saveNullableBool (key, Application.AotFloat32, storage)) )}, - { "NoWarn", ( + { "NoWarn", ( // we should support '$(NoWarn)' at some point: https://github.com/dotnet/macios/issues/25645 new LoadValue ((key, value) => loadWarningLevel (key, value, ErrorHelper.WarningLevel.Disable)), new SaveValue ((key, storage) => saveWarningLevel (key, storage, ErrorHelper.WarningLevel.Disable)) )}, From 9e1532c9f9efb840a16bf683494df0890b58b57d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 08:37:46 +0200 Subject: [PATCH 91/97] [tests] Create an opt-in test to capture build performance --- tests/dotnet/UnitTests/PerformanceTests.cs | 52 ++++++++++++++++++++++ tests/dotnet/UnitTests/TestBaseClass.cs | 15 +++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/dotnet/UnitTests/PerformanceTests.cs diff --git a/tests/dotnet/UnitTests/PerformanceTests.cs b/tests/dotnet/UnitTests/PerformanceTests.cs new file mode 100644 index 000000000000..73f88a7af72e --- /dev/null +++ b/tests/dotnet/UnitTests/PerformanceTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Xamarin.Tests { + [TestFixture] + public class PerformanceTests : TestBaseClass { + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64")] + public void PrepareAssemblies (ApplePlatform platform, string runtimeIdentifiers) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + if (string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("ENABLE_PERFORMANCE_TESTS"))) + Assert.Ignore ("Test ignored because ENABLE_PERFORMANCE_TESTS is not set."); + + var projects = new string [] { "MySimpleApp", "monotouch-test" }; + var results = new List (); + var attempts = 3; + foreach (var project in projects) { + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath); + + var result = new PrepareAssembliesTestResult { + Project = project, + }; + foreach (var propertyValue in new bool [] { false, true }) { + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["PrepareAssemblies"] = propertyValue.ToString (); + + for (var i = 1; i <= attempts; i++) { + Clean (project_path); + var rv = DotNet.AssertBuild (project_path, properties); + (propertyValue ? result.EnabledBuildTimes : result.DisabledBuildTimes).Add (rv.Duration); + } + } + results.Add (result); + } + foreach (var result in results.OrderBy (v => v.Project)) { + Console.WriteLine ($"Results for: {result.Project}"); + Console.WriteLine ($" Enabled timings: {string.Join (", ", result.EnabledBuildTimes.Select (v => v.ToString ()))} average: {TimeSpan.FromTicks ((long) result.EnabledBuildTimes.Average (v => v.Ticks))}"); + Console.WriteLine ($" Disabled timings: {string.Join (", ", result.DisabledBuildTimes.Select (v => v.ToString ()))} average: {TimeSpan.FromTicks ((long) result.DisabledBuildTimes.Average (v => v.Ticks))}"); + } + } + + class PrepareAssembliesTestResult { + public required string Project; + public List EnabledBuildTimes = new (); + public List DisabledBuildTimes = new (); + } + } +} + diff --git a/tests/dotnet/UnitTests/TestBaseClass.cs b/tests/dotnet/UnitTests/TestBaseClass.cs index e421218252eb..fbe234b535a6 100644 --- a/tests/dotnet/UnitTests/TestBaseClass.cs +++ b/tests/dotnet/UnitTests/TestBaseClass.cs @@ -133,6 +133,9 @@ protected static string GetDefaultRuntimeIdentifier (ApplePlatform platform, str protected static string GetProjectPath (string project, string? subdir = null, ApplePlatform? platform = null) { + if (TryGetTestProjectPath (project, platform ?? ApplePlatform.None, out var testProjectPath)) + return testProjectPath; + var project_dir = Path.Combine (Configuration.SourceRoot, "tests", "dotnet", project); if (!string.IsNullOrEmpty (subdir)) project_dir = Path.Combine (project_dir, subdir); @@ -154,6 +157,18 @@ protected static string GetProjectPath (string project, string? subdir = null, A return project_path; } + static bool TryGetTestProjectPath (string project, ApplePlatform platform, [NotNullWhen (true)] out string? projectPath) + { + projectPath = null; + + switch (project) { + case "monotouch-test": + projectPath = Path.Combine (Configuration.SourceRoot, "tests", project, "dotnet", platform.AsString (), project + ".csproj"); + return true; + } + return false; + } + protected string GetPlugInsRelativePath (ApplePlatform platform) { switch (platform) { From 97d66d0f4cc1c17c80cdf428a7a57e7c37ee5161 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 08:48:48 +0200 Subject: [PATCH 92/97] [dotnet-linker] Move methods from ManagedRegistrarLookupTablesStep to AppBundleRewriter. Move FindNSObjectConstructor, FindINativeObjectConstructor, ImplementConstructNSObjectFactoryMethod, ImplementConstructINativeObjectFactoryMethod, and AddTypeInterfaceImplementation from ManagedRegistrarLookupTablesStep to AppBundleRewriter, and update all call sites accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/dotnet-linker/AppBundleRewriter.cs | 171 +++++++++++++++++ .../Steps/ManagedRegistrarLookupTablesStep.cs | 174 +----------------- .../Steps/ManagedRegistrarStep.cs | 10 +- .../Steps/TrimmableRegistrarStep.cs | 6 +- 4 files changed, 183 insertions(+), 178 deletions(-) diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index 1b3908f009ea..1914b02994d7 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; @@ -1726,5 +1727,175 @@ public MethodDefinition CreateInternalPInvoke (ModuleDefinition module, string @ return rv; } + + internal static MethodDefinition? FindNSObjectConstructor (TypeDefinition type) + { + return FindConstructorWithOneParameter ("ObjCRuntime", "NativeHandle") + ?? FindConstructorWithOneParameter ("System", "IntPtr"); + + MethodDefinition? FindConstructorWithOneParameter (string ns, string cls) + => type.Methods.SingleOrDefault (method => + method.IsConstructor + && !method.IsStatic + && method.HasParameters + && method.Parameters.Count == 1 + && method.Parameters [0].ParameterType.Is (ns, cls)); + } + + internal static MethodDefinition? FindINativeObjectConstructor (TypeDefinition type) + { + return FindConstructorWithTwoParameters ("ObjCRuntime", "NativeHandle", "System", "Boolean") + ?? FindConstructorWithTwoParameters ("System", "IntPtr", "System", "Boolean"); + + MethodDefinition? FindConstructorWithTwoParameters (string ns1, string cls1, string ns2, string cls2) + => type.Methods.SingleOrDefault (method => + method.IsConstructor + && !method.IsStatic + && method.HasParameters + && method.Parameters.Count == 2 + && method.Parameters [0].ParameterType.Is (ns1, cls1) + && method.Parameters [1].ParameterType.Is (ns2, cls2)); + } + + internal void ImplementConstructNSObjectFactoryMethod (Tuner.DerivedLinkContext context, TypeDefinition type, MethodReference ctor) + { + var abr = this; + + // skip creating the factory for NSObject itself + if (type.Is ("Foundation", "NSObject")) + return; + + // Make sure the type implements INSObjectFactory, otherwise we can't override the _Xamarin_ConstructNSObject method from it. + AddTypeInterfaceImplementation (abr, context, type, abr.Foundation_INSObjectFactory); + + var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructNSObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.Foundation_NSObject); + var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); + abr.Foundation_INSObjectFactory.Resolve ().IsPublic = true; + createInstanceMethod.Overrides.Add (abr.INSObjectFactory__Xamarin_ConstructNSObject); + var body = createInstanceMethod.CreateBody (out var il); + + if (type.HasGenericParameters) { + ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); + } + + // return new TypeA (nativeHandle); // for NativeHandle ctor + // return new TypeA ((IntPtr) nativeHandle); // for IntPtr ctor + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Newobj, ctor); + il.Emit (OpCodes.Ret); + + body.GenerateILOffsets (); + + // make sure the trimmer doesn't trim it away if the type is kept + if (context.App.Registrar == RegistrarMode.TrimmableStatic) { + // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) + abr.AddDynamicDependencyAttributeToStaticConstructor (type, createInstanceMethod); + } else { + context.Annotations.Mark (createInstanceMethod); + } + } + + internal void ImplementConstructINativeObjectFactoryMethod (Tuner.DerivedLinkContext context, TypeDefinition type, MethodReference? ctor) + { + var abr = this; + + // skip creating the factory for NSObject itself + if (type.Is ("Foundation", "NSObject")) + return; + + // If the type is a subclass of NSObject, we prefer the NSObject "IntPtr" constructor + MethodReference? nsobjectConstructor = type.IsNSObject (context) ? AppBundleRewriter.FindNSObjectConstructor (type) : null; + if (nsobjectConstructor is null && ctor is null) + return; + + // Make sure the type implements INativeObject, otherwise we can't override the _Xamarin_ConstructINativeObject method from it. + AddTypeInterfaceImplementation (abr, context, type, abr.ObjCRuntime_INativeObject); + + var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructINativeObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject); + var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); + var ownsParameter = createInstanceMethod.AddParameter ("owns", abr.System_Boolean); + abr.INativeObject__Xamarin_ConstructINativeObject.Resolve ().IsPublic = true; + createInstanceMethod.Overrides.Add (abr.INativeObject__Xamarin_ConstructINativeObject); + var body = createInstanceMethod.CreateBody (out var il); + + if (nsobjectConstructor is not null) { + // var instance = new TypeA (nativeHandle); + // // alternatively with a cast: new TypeA ((IntPtr) nativeHandle); + // if (instance is not null && owns) + // Runtime.TryReleaseINativeObject (instance); + // return instance; + + if (type.HasGenericParameters) { + nsobjectConstructor = type.CreateMethodReferenceOnGenericType (nsobjectConstructor, type.GenericParameters.ToArray ()); + } + + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (nsobjectConstructor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Newobj, nsobjectConstructor); + + var falseTarget = il.Create (OpCodes.Nop); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Ldnull); + il.Emit (OpCodes.Cgt_Un); + il.Emit (OpCodes.Ldarg, ownsParameter); + il.Emit (OpCodes.And); + il.Emit (OpCodes.Brfalse_S, falseTarget); + + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Call, abr.Runtime_TryReleaseINativeObject); + + il.Append (falseTarget); + + il.Emit (OpCodes.Ret); + } else if (ctor is not null) { + // return new TypeA (nativeHandle, owns); // for NativeHandle ctor + // return new TypeA ((IntPtr) nativeHandle, owns); // IntPtr ctor + + if (type.HasGenericParameters) { + ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); + } + + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Ldarg, ownsParameter); + il.Emit (OpCodes.Newobj, ctor); + il.Emit (OpCodes.Ret); + } else { + throw new UnreachableException (); + } + + body.GenerateILOffsets (); + + // make sure the trimmer doesn't trim it away if the type is kept + if (context.App.Registrar == RegistrarMode.TrimmableStatic) { + // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) + abr.AddDynamicDependencyAttributeToStaticConstructor (type, createInstanceMethod); + } else { + context.Annotations.Mark (createInstanceMethod); + } + } + + static void AddTypeInterfaceImplementation (AppBundleRewriter abr, Tuner.DerivedLinkContext context, TypeDefinition type, TypeReference iface) + { + if (type.HasInterfaces && type.Interfaces.Any (v => v.InterfaceType == iface)) + return; + + var ifaceImplementation = new InterfaceImplementation (iface); + type.Interfaces.Add (ifaceImplementation); + + // make sure the trimmer doesn't trim it away if the type is kept + if (context.App.Registrar == RegistrarMode.TrimmableStatic) { + // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) + abr.AddAttributeToStaticConstructor (type, abr.CreateDynamicDependencyAttribute (DynamicallyAccessedMemberTypes.Interfaces, type)); + } else { + context.Annotations.Mark (ifaceImplementation); + context.Annotations.Mark (ifaceImplementation.InterfaceType); + context.Annotations.Mark (ifaceImplementation.InterfaceType.Resolve ()); + } + } } } diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index 68bbe468e009..4396c7319478 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -328,7 +328,7 @@ void GenerateConstructNSObject (TypeDefinition registrarType) var types = GetRelevantTypes (type => type.IsNSObject (DerivedLinkContext) && !type.IsAbstract && !type.IsInterface); foreach (var type in types) { - var ctorRef = FindNSObjectConstructor (type); + var ctorRef = AppBundleRewriter.FindNSObjectConstructor (type); if (ctorRef is null) { App.Log (9, $"Cannot include {type.FullName} in ConstructNSObject because it doesn't have a suitable constructor"); continue; @@ -357,7 +357,7 @@ void GenerateConstructNSObject (TypeDefinition registrarType) } // In addition to the big lookup method, implement the static factory method on the type: - ImplementConstructNSObjectFactoryMethod (abr, DerivedLinkContext, type, ctor); + abr.ImplementConstructNSObjectFactoryMethod (DerivedLinkContext, type, ctor); } // return default (NSObject); @@ -386,7 +386,7 @@ void GenerateConstructINativeObject (TypeDefinition registrarType) var types = GetRelevantTypes (type => type.IsNativeObject () && !type.IsAbstract && !type.IsInterface); foreach (var type in types) { - var ctorRef = FindINativeObjectConstructor (type); + var ctorRef = AppBundleRewriter.FindINativeObjectConstructor (type); if (ctorRef is not null) { var ctor = abr.CurrentAssembly.MainModule.ImportReference (ctorRef); @@ -415,7 +415,7 @@ void GenerateConstructINativeObject (TypeDefinition registrarType) } // In addition to the big lookup method, implement the static factory method on the type: - ImplementConstructINativeObjectFactoryMethod (abr, DerivedLinkContext, type, ctorRef); + abr.ImplementConstructINativeObjectFactoryMethod (DerivedLinkContext, type, ctorRef); } // return default (NSObject) @@ -444,172 +444,6 @@ void MarkConstructorIfTrimmed (MethodReference ctor) } } - static void AddTypeInterfaceImplementation (AppBundleRewriter abr, Tuner.DerivedLinkContext context, TypeDefinition type, TypeReference iface) - { - if (type.HasInterfaces && type.Interfaces.Any (v => v.InterfaceType == iface)) - return; - - var ifaceImplementation = new InterfaceImplementation (iface); - type.Interfaces.Add (ifaceImplementation); - - // make sure the trimmer doesn't trim it away if the type is kept - if (context.App.Registrar == RegistrarMode.TrimmableStatic) { - // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) - abr.AddAttributeToStaticConstructor (type, abr.CreateDynamicDependencyAttribute (DynamicallyAccessedMemberTypes.Interfaces, type)); - } else { - context.Annotations.Mark (ifaceImplementation); - context.Annotations.Mark (ifaceImplementation.InterfaceType); - context.Annotations.Mark (ifaceImplementation.InterfaceType.Resolve ()); - } - } - - internal static void ImplementConstructNSObjectFactoryMethod (AppBundleRewriter abr, Tuner.DerivedLinkContext context, TypeDefinition type, MethodReference ctor) - { - // skip creating the factory for NSObject itself - if (type.Is ("Foundation", "NSObject")) - return; - - // Make sure the type implements INSObjectFactory, otherwise we can't override the _Xamarin_ConstructNSObject method from it. - AddTypeInterfaceImplementation (abr, context, type, abr.Foundation_INSObjectFactory); - - var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructNSObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.Foundation_NSObject); - var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); - abr.Foundation_INSObjectFactory.Resolve ().IsPublic = true; - createInstanceMethod.Overrides.Add (abr.INSObjectFactory__Xamarin_ConstructNSObject); - var body = createInstanceMethod.CreateBody (out var il); - - if (type.HasGenericParameters) { - ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); - } - - // return new TypeA (nativeHandle); // for NativeHandle ctor - // return new TypeA ((IntPtr) nativeHandle); // for IntPtr ctor - il.Emit (OpCodes.Ldarg, nativeHandleParameter); - if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) - il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); - il.Emit (OpCodes.Newobj, ctor); - il.Emit (OpCodes.Ret); - - body.GenerateILOffsets (); - - // make sure the trimmer doesn't trim it away if the type is kept - if (context.App.Registrar == RegistrarMode.TrimmableStatic) { - // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) - abr.AddDynamicDependencyAttributeToStaticConstructor (type, createInstanceMethod); - } else { - context.Annotations.Mark (createInstanceMethod); - } - } - - internal static void ImplementConstructINativeObjectFactoryMethod (AppBundleRewriter abr, Tuner.DerivedLinkContext context, TypeDefinition type, MethodReference? ctor) - { - // skip creating the factory for NSObject itself - if (type.Is ("Foundation", "NSObject")) - return; - - // If the type is a subclass of NSObject, we prefer the NSObject "IntPtr" constructor - MethodReference? nsobjectConstructor = type.IsNSObject (context) ? FindNSObjectConstructor (type) : null; - if (nsobjectConstructor is null && ctor is null) - return; - - // Make sure the type implements INativeObject, otherwise we can't override the _Xamarin_ConstructINativeObject method from it. - AddTypeInterfaceImplementation (abr, context, type, abr.ObjCRuntime_INativeObject); - - var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructINativeObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject); - var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); - var ownsParameter = createInstanceMethod.AddParameter ("owns", abr.System_Boolean); - abr.INativeObject__Xamarin_ConstructINativeObject.Resolve ().IsPublic = true; - createInstanceMethod.Overrides.Add (abr.INativeObject__Xamarin_ConstructINativeObject); - var body = createInstanceMethod.CreateBody (out var il); - - if (nsobjectConstructor is not null) { - // var instance = new TypeA (nativeHandle); - // // alternatively with a cast: new TypeA ((IntPtr) nativeHandle); - // if (instance is not null && owns) - // Runtime.TryReleaseINativeObject (instance); - // return instance; - - if (type.HasGenericParameters) { - nsobjectConstructor = type.CreateMethodReferenceOnGenericType (nsobjectConstructor, type.GenericParameters.ToArray ()); - } - - il.Emit (OpCodes.Ldarg, nativeHandleParameter); - if (nsobjectConstructor.Parameters [0].ParameterType.Is ("System", "IntPtr")) - il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); - il.Emit (OpCodes.Newobj, nsobjectConstructor); - - var falseTarget = il.Create (OpCodes.Nop); - il.Emit (OpCodes.Dup); - il.Emit (OpCodes.Ldnull); - il.Emit (OpCodes.Cgt_Un); - il.Emit (OpCodes.Ldarg, ownsParameter); - il.Emit (OpCodes.And); - il.Emit (OpCodes.Brfalse_S, falseTarget); - - il.Emit (OpCodes.Dup); - il.Emit (OpCodes.Call, abr.Runtime_TryReleaseINativeObject); - - il.Append (falseTarget); - - il.Emit (OpCodes.Ret); - } else if (ctor is not null) { - // return new TypeA (nativeHandle, owns); // for NativeHandle ctor - // return new TypeA ((IntPtr) nativeHandle, owns); // IntPtr ctor - - if (type.HasGenericParameters) { - ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); - } - - il.Emit (OpCodes.Ldarg, nativeHandleParameter); - if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) - il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); - il.Emit (OpCodes.Ldarg, ownsParameter); - il.Emit (OpCodes.Newobj, ctor); - il.Emit (OpCodes.Ret); - } else { - throw new UnreachableException (); - } - - body.GenerateILOffsets (); - - // make sure the trimmer doesn't trim it away if the type is kept - if (context.App.Registrar == RegistrarMode.TrimmableStatic) { - // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) - abr.AddDynamicDependencyAttributeToStaticConstructor (type, createInstanceMethod); - } else { - context.Annotations.Mark (createInstanceMethod); - } - } - - internal static MethodDefinition? FindNSObjectConstructor (TypeDefinition type) - { - return FindConstructorWithOneParameter ("ObjCRuntime", "NativeHandle") - ?? FindConstructorWithOneParameter ("System", "IntPtr"); - - MethodDefinition? FindConstructorWithOneParameter (string ns, string cls) - => type.Methods.SingleOrDefault (method => - method.IsConstructor - && !method.IsStatic - && method.HasParameters - && method.Parameters.Count == 1 - && method.Parameters [0].ParameterType.Is (ns, cls)); - } - - internal static MethodDefinition? FindINativeObjectConstructor (TypeDefinition type) - { - return FindConstructorWithTwoParameters ("ObjCRuntime", "NativeHandle", "System", "Boolean") - ?? FindConstructorWithTwoParameters ("System", "IntPtr", "System", "Boolean"); - - MethodDefinition? FindConstructorWithTwoParameters (string ns1, string cls1, string ns2, string cls2) - => type.Methods.SingleOrDefault (method => - method.IsConstructor - && !method.IsStatic - && method.HasParameters - && method.Parameters.Count == 2 - && method.Parameters [0].ParameterType.Is (ns1, cls1) - && method.Parameters [1].ParameterType.Is (ns2, cls2)); - } - void GenerateRegisterWrapperTypes (TypeDefinition type) { var method = type.AddMethod ("RegisterWrapperTypes", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_Void); diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 70c4cc72a7bf..28d906d981db 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -183,22 +183,22 @@ bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos, List acti // INativeObject subclasses var inativeObjectTypes = StaticRegistrar.GetAllTypes (assembly).Where (t => !t.IsInterface && !t.IsAbstract && t.IsNativeObject ()); foreach (var tr in inativeObjectTypes.OrderBy (v => v.FullName)) { - var inativeObjCtor = ManagedRegistrarLookupTablesStep.FindINativeObjectConstructor (tr); + var inativeObjCtor = AppBundleRewriter.FindINativeObjectConstructor (tr); if (inativeObjCtor is null) continue; @@ -373,7 +373,7 @@ void addPostAction (AssemblyDefinition assembly, Action acti var createObjectMethod = proxyType.AddMethod ("CreateObject", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, abr.Foundation_NSObject); createObjectMethod.AddParameter ("handle", abr.System_IntPtr); il = createObjectMethod.Body.GetILProcessor (); - var nativeHandleCtor = ManagedRegistrarLookupTablesStep.FindNSObjectConstructor (td); + var nativeHandleCtor = AppBundleRewriter.FindNSObjectConstructor (td); if (nativeHandleCtor is not null) { il.Append (il.Create (OpCodes.Ldarg_1)); if (nativeHandleCtor.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle")) @@ -512,7 +512,7 @@ void addPostAction (AssemblyDefinition assembly, Action acti createObjectMethod.AddParameter ("handle", abr.System_IntPtr); createObjectMethod.AddParameter ("owns", abr.System_Boolean); createObjectMethod.CreateBody (out il); - var nativeHandleCtor = ManagedRegistrarLookupTablesStep.FindINativeObjectConstructor (objcType.ProtocolWrapperType.Resolve ()); + var nativeHandleCtor = AppBundleRewriter.FindINativeObjectConstructor (objcType.ProtocolWrapperType.Resolve ()); if (nativeHandleCtor is not null) { il.Append (il.Create (OpCodes.Ldarg_1)); if (nativeHandleCtor.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle")) From 6b1cb511d4932a7ba786f928ae1d9a9d7f8e45bd Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 Jan 2026 11:45:54 +0100 Subject: [PATCH 93/97] [assembly-preparer] Create a new tool to eventually replace custom linker steps. Create an 'assembly-preparer' tool which will do what the existing custom linker steps will do, and add all the scaffolding around it: * Add a new _PrepareAssembly target and a PrepareAssemblies task that calls the new tool. * Add a unit test project, which is able to run tests directly using the new tool (and not necessarily through the new MSBuild target/task, to speed up execution and make debugging *much* easier). * Add the new unit test project to xharness. * Make the new tool what the custom linker steps currently do pre-mark. Add corresponding tests as well. * Add an opt-in .NET unit test to check the speed of using (and not using) our new assembly preparing logic. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 79 ++-- msbuild/ILMerge.targets | 1 + .../MSBStrings.resx | 5 + msbuild/Xamarin.MacDev.Tasks.slnx | 4 + .../ConsoleToTaskWriter.cs | 103 ++++++ .../ErrorHelper.msbuild.cs | 54 --- .../Xamarin.MacDev.Tasks/LoggingExtensions.cs | 23 ++ .../Tasks/PrepareAssemblies.cs | 109 ++++++ .../Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs | 37 +- .../Xamarin.MacDev.Tasks.csproj | 36 +- msbuild/Xamarin.Shared/Xamarin.Shared.targets | 49 +++ src/ObjCRuntime/Registrar.core.cs | 8 + src/ObjCRuntime/Registrar.cs | 12 + src/bgen/BindingTouch.cs | 7 +- tests/assembly-preparer/BaseClass.cs | 98 +++++ tests/assembly-preparer/GlobalUsings.cs | 17 + .../InlineDlfcnMethodsStepTests.cs | 54 +++ tests/assembly-preparer/Makefile | 8 + .../MarkIProtocolHandlerTests.cs | 73 ++++ .../OptimizeGeneratedCodeHandlerTests.cs | 251 +++++++++++++ .../PreserveBlockCodeHandlerTests.cs | 52 +++ .../PreserveSmartEnumConversionsTest.cs | 119 ++++++ tests/assembly-preparer/ReproTest.cs | 203 +++++++++++ .../assembly-preparer-tests.csproj | 50 +++ .../assembly-preparer/assembly-preparer.slnx | 4 + tests/common/Configuration.cs | 57 +++ tests/common/DotNet.cs | 12 +- tests/common/test-variations.csproj | 6 + tests/linker/link all/dotnet/shared.csproj | 1 + .../xharness/Jenkins/TestVariationsFactory.cs | 34 ++ tools/Makefile | 1 + tools/ap-launcher/.gitignore | 2 + tools/ap-launcher/Makefile | 8 + tools/ap-launcher/Program.cs | 74 ++++ tools/ap-launcher/README.md | 2 + tools/ap-launcher/ap-launcher.csproj | 17 + tools/ap-launcher/ap-launcher.slnx | 5 + tools/assembly-preparer/.gitignore | 2 + tools/assembly-preparer/AssemblyPreparer.cs | 331 +++++++++++++++++ .../DynamicallyAccessedMemberTypes.cs | 176 +++++++++ tools/assembly-preparer/GlobalUsings.cs | 16 + .../assembly-preparer/IAssemblyPreparerLog.cs | 26 ++ tools/assembly-preparer/Makefile | 25 ++ .../NetStandardExtensions.cs | 109 ++++++ tools/assembly-preparer/README.md | 35 ++ .../Scaffolding/AnnotationStore.cs | 167 +++++++++ .../Scaffolding/AssemblyAction.cs | 15 + .../assembly-preparer/Scaffolding/BaseStep.cs | 85 +++++ tools/assembly-preparer/Scaffolding/IStep.cs | 38 ++ .../Scaffolding/LinkContext.cs | 48 +++ tools/assembly-preparer/Scaffolding/Target.cs | 11 + ...System_Diagnostics_UnreachableException.cs | 48 +++ tools/assembly-preparer/System_Index.cs | 170 +++++++++ tools/assembly-preparer/System_Range.cs | 141 ++++++++ .../assembly-preparer.csproj | 341 ++++++++++++++++++ .../assembly-preparer/assembly-preparer.slnx | 5 + tools/common/Application.cs | 52 ++- tools/common/Assembly.cs | 9 +- tools/common/CoreResolver.cs | 2 + tools/common/DerivedLinkContext.cs | 17 +- tools/common/Driver.cs | 7 +- tools/common/ErrorHelper.tools.cs | 6 + tools/common/IToolLog.cs | 18 +- tools/common/Make.common | 3 + tools/common/Makefile | 5 + tools/common/NullableAttributes.cs | 18 + tools/common/Optimizations.cs | 2 +- tools/common/StaticRegistrar.cs | 10 +- tools/common/cache.cs | 2 + tools/common/error.cs | 7 + .../Program.cs | 122 ++++++- tools/dotnet-linker/AppBundleRewriter.cs | 77 +++- .../ApplyPreserveAttributeBase.cs | 3 +- .../ApplyPreserveAttributeStep.cs | 6 + tools/dotnet-linker/Compat.cs | 2 +- tools/dotnet-linker/DotNetGlobals.cs | 1 + tools/dotnet-linker/DotNetResolver.cs | 6 + tools/dotnet-linker/LinkerConfiguration.cs | 143 +++++++- tools/dotnet-linker/MarkIProtocolHandler.cs | 20 +- .../Steps/ExceptionalMarkHandler.cs | 4 + .../Steps/InlineDlfcnMethodsStep.cs | 2 +- .../Steps/ManagedRegistrarLookupTablesStep.cs | 34 +- .../Steps/ManagedRegistrarStep.cs | 80 +++- .../Steps/PreserveBlockCodeHandler.cs | 3 +- .../Steps/SetBeforeFieldInitStep.cs | 33 +- .../Steps/TrimmableRegistrarStep.cs | 19 +- tools/linker/CoreTypeMapStep.cs | 6 +- tools/linker/MarkNSObjects.cs | 3 +- tools/linker/MobileExtensions.cs | 2 +- tools/linker/MonoTouch.Tuner/Extensions.cs | 4 + tools/linker/ObjCExtensions.cs | 6 +- tools/linker/OptimizeGeneratedCode.cs | 7 +- tools/mtouch/Errors.designer.cs | 9 + tools/mtouch/Errors.resx | 4 + tools/mtouch/mtouch.cs | 3 +- tools/tools.slnx | 1 + 96 files changed, 4004 insertions(+), 218 deletions(-) create mode 100644 msbuild/Xamarin.MacDev.Tasks/ConsoleToTaskWriter.cs delete mode 100644 msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs create mode 100644 msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs create mode 100644 tests/assembly-preparer/BaseClass.cs create mode 100644 tests/assembly-preparer/GlobalUsings.cs create mode 100644 tests/assembly-preparer/InlineDlfcnMethodsStepTests.cs create mode 100644 tests/assembly-preparer/Makefile create mode 100644 tests/assembly-preparer/MarkIProtocolHandlerTests.cs create mode 100644 tests/assembly-preparer/OptimizeGeneratedCodeHandlerTests.cs create mode 100644 tests/assembly-preparer/PreserveBlockCodeHandlerTests.cs create mode 100644 tests/assembly-preparer/PreserveSmartEnumConversionsTest.cs create mode 100644 tests/assembly-preparer/ReproTest.cs create mode 100644 tests/assembly-preparer/assembly-preparer-tests.csproj create mode 100644 tests/assembly-preparer/assembly-preparer.slnx create mode 100644 tools/ap-launcher/.gitignore create mode 100644 tools/ap-launcher/Makefile create mode 100644 tools/ap-launcher/Program.cs create mode 100644 tools/ap-launcher/README.md create mode 100644 tools/ap-launcher/ap-launcher.csproj create mode 100644 tools/ap-launcher/ap-launcher.slnx create mode 100644 tools/assembly-preparer/.gitignore create mode 100644 tools/assembly-preparer/AssemblyPreparer.cs create mode 100644 tools/assembly-preparer/DynamicallyAccessedMemberTypes.cs create mode 100644 tools/assembly-preparer/GlobalUsings.cs create mode 100644 tools/assembly-preparer/IAssemblyPreparerLog.cs create mode 100644 tools/assembly-preparer/Makefile create mode 100644 tools/assembly-preparer/NetStandardExtensions.cs create mode 100644 tools/assembly-preparer/README.md create mode 100644 tools/assembly-preparer/Scaffolding/AnnotationStore.cs create mode 100644 tools/assembly-preparer/Scaffolding/AssemblyAction.cs create mode 100644 tools/assembly-preparer/Scaffolding/BaseStep.cs create mode 100644 tools/assembly-preparer/Scaffolding/IStep.cs create mode 100644 tools/assembly-preparer/Scaffolding/LinkContext.cs create mode 100644 tools/assembly-preparer/Scaffolding/Target.cs create mode 100644 tools/assembly-preparer/System_Diagnostics_UnreachableException.cs create mode 100644 tools/assembly-preparer/System_Index.cs create mode 100644 tools/assembly-preparer/System_Range.cs create mode 100644 tools/assembly-preparer/assembly-preparer.csproj create mode 100644 tools/assembly-preparer/assembly-preparer.slnx create mode 100644 tools/common/Makefile diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 02926aa4ed8c..ec3045cc5df3 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -271,6 +271,7 @@ _ResolveAppExtensionReferences; _ExtendAppExtensionReferences; _ComputeLinkerArguments; + _PrepareAssemblies; _ComputeFrameworkFilesToPublish; _ComputeDynamicLibrariesToPublish; ComputeFilesToPublish; @@ -633,6 +634,7 @@ AOTOutputDirectory=$(_AOTOutputDirectory) DedupAssembly=$(_DedupAssembly) AppBundleManifestPath=$(_AppBundleManifestPath) + AssemblyPublishDir=$(_AssemblyPublishDir) CacheDirectory=$(_LinkerCacheDirectory) @(_BundlerDlsym -> 'Dlsym=%(Identity)') Debug=$(_BundlerDebug) @@ -662,6 +664,8 @@ PartialStaticRegistrarLibrary=$(_LibPartialStaticRegistrar) Platform=$(_PlatformName) PlatformAssembly=$(_PlatformAssemblyName).dll + PrepareAssemblies=$(PrepareAssemblies) + PublishTrimmed=$(PublishTrimmed) RelativeAppBundlePath=$(_RelativeAppBundlePath) Registrar=$(Registrar) @(ReferenceNativeSymbol -> 'ReferenceNativeSymbol=%(SymbolType):%(SymbolMode):%(Identity)') @@ -674,9 +678,11 @@ SkipMarkingNSObjectsInUserAssemblies=$(_SkipMarkingNSObjectsInUserAssemblies) TargetArchitectures=$(TargetArchitectures) TargetFramework=$(_ComputedTargetFrameworkMoniker) + TrimMode=$(TrimMode) TypeMapAssemblyName=$(_TypeMapAssemblyName) TypeMapFilePath=$(_TypeMapFilePath) TypeMapOutputDirectory=$(_TypeMapOutputDirectory) + UnmanagedCallersOnlyMapPath=$(_UnmanagedCallersOnlyMapPath) UseLlvm=$(MtouchUseLlvm) Verbosity=$(_BundlerVerbosity) Warn=$(_BundlerWarn) @@ -784,44 +790,45 @@ <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.CollectAssembliesStep" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.CoreTypeMapStep" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.ProcessExportedFields" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.PreserveProtocolsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveBlockCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.OptimizeGeneratedCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.ApplyPreserveAttributeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseLinkDescriptionForApplyPreserveAttribute)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkForStaticRegistrarStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkStaticRegistrar)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkNSObjectsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkNSObjects)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineDlfcnMethodsStep" Condition="'$(InlineDlfcnMethods)' != ''" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.PreserveProtocolsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveBlockCodeStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.OptimizeGeneratedCodeStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.ApplyPreserveAttributeStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseLinkDescriptionForApplyPreserveAttribute)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkForStaticRegistrarStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkStaticRegistrar)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkNSObjectsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkNSObjects)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineDlfcnMethodsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(InlineDlfcnMethods)' != ''" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" Condition="'$(PrepareAssemblies)' != 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreMarkDispatcher" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.TrimmableRegistrarStep" Condition="'$(Registrar)' == 'trimmable-static'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineClassGetHandleStep" Condition="'$(InlineClassGetHandle)' != '' And '$(InlineClassGetHandle)' != 'disabled'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreMarkDispatcher" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(PrepareAssemblies)' != 'true' And ('$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static')" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.TrimmableRegistrarStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(Registrar)' == 'trimmable-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineClassGetHandleStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(InlineClassGetHandle)' != '' And '$(InlineClassGetHandle)' != 'disabled'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' != 'true'" Type="Xamarin.Linker.OptimizeGeneratedCodeHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.BackingFieldDelayHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' != 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' != 'true'" Type="Xamarin.Linker.OptimizeGeneratedCodeHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.BackingFieldDelayHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' != 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkDispatcher)' != 'true'" Type="Xamarin.Linker.Steps.MarkDispatcher" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkDispatcher)' != 'true'" Type="Xamarin.Linker.Steps.MarkDispatcher" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="SweepStep" Type="Xamarin.Linker.ManagedRegistrarLookupTablesStep" Condition="'$(Registrar)' == 'managed-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="SweepStep" Type="Xamarin.Linker.ManagedRegistrarLookupTablesStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(Registrar)' == 'managed-static'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" AfterStep="SweepStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PostSweepDispatcher" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" AfterStep="SweepStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PostSweepDispatcher" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(PrepareAssemblies)' == 'true' And ('$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static')" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.TrimmableRegistrarStep" Condition="'$(PrepareAssemblies)' == 'true' And '$(Registrar)' == 'trimmable-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.ManagedRegistrarLookupTablesStep" Condition="'$(PrepareAssemblies)' == 'true' And '$(Registrar)' == 'managed-static'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.RegistrarStep" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.GenerateMainStep" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.GenerateReferencesStep" /> @@ -1328,6 +1338,9 @@ <_TypeMapFilePath Condition="'$(_TypeMapFilePath)' == ''">$(DeviceSpecificIntermediateOutputPath)type-map.txt + + + <_UnmanagedCallersOnlyMapPath Condition="'$(_UnmanagedCallersOnlyMapPath)' == ''">$(DeviceSpecificIntermediateOutputPath)unmanaged_callers_only_map.txt @@ -1413,13 +1426,27 @@ - - - - + + + + + + + + + + + <_IntermediateAssemblyProperty>@(IntermediateAssembly) + <_PreparedIntermediateAssemblyProperty>@(PreparedIntermediateAssembly->WithMetadataValue('BeforePrepareAssembliesPath','$(_IntermediateAssemblyProperty)')) + + + <_PreparedRootedIntermediateAssembly Include="@(TrimmerRootAssembly->'$(_PreparedIntermediateAssemblyProperty)')" Condition="'%(Identity)' == '$(_IntermediateAssemblyProperty)'" /> + + + + diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index 5de6bb55b800..0741be004f37 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1661,4 +1661,9 @@ Shown when the tool reports a simulator runtime version mismatch. {0} - The platform name (e.g. "iOS" or "tvOS"). + + Console.StandardOutput or Console.StandardError was accessed during a build task. This should not happen, use the MSBuild logging infrastructure instead. Stack trace: {0} + Shown when an MSBuild task writes to the console instead of using MSBuild logging. +{0} - The stack trace of the offending call. + diff --git a/msbuild/Xamarin.MacDev.Tasks.slnx b/msbuild/Xamarin.MacDev.Tasks.slnx index e3c799a54619..1f17eb9aa95f 100644 --- a/msbuild/Xamarin.MacDev.Tasks.slnx +++ b/msbuild/Xamarin.MacDev.Tasks.slnx @@ -36,4 +36,8 @@ + + + + diff --git a/msbuild/Xamarin.MacDev.Tasks/ConsoleToTaskWriter.cs b/msbuild/Xamarin.MacDev.Tasks/ConsoleToTaskWriter.cs new file mode 100644 index 000000000000..1e97dc541697 --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks/ConsoleToTaskWriter.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Text; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +#nullable enable + +namespace Xamarin.Utils; + +class ConsoleToTaskWriter : TextWriter { + TaskLoggingHelper helper; + bool errorShown; + + public ConsoleToTaskWriter (TaskLoggingHelper helper) + { + this.helper = helper; + } + + public override Encoding Encoding => Encoding.UTF8; + + public override void Write (char value) + { + ShowError (); + helper.LogMessage (MessageImportance.Low, value.ToString ()); + } + + public override void Write (char [] buffer, int index, int count) + { + ShowError (); + helper.LogMessage (MessageImportance.Low, new string (buffer, index, count)); + } + + public override void Write (string? value) + { + ShowError (); + helper.LogMessage (MessageImportance.Low, value ?? string.Empty); + } + + public override void WriteLine () + { + ShowError (); + } + + public override void WriteLine (string? value) + { + ShowError (); + helper.LogMessage (MessageImportance.Low, value ?? string.Empty); + } + + void ShowError () + { + if (errorShown) + return; + errorShown = true; + + helper.LogError (null, "MT7178" /* Console.StandardOutput or Console.StandardError was accessed during a build task. This should not happen, use the MSBuild logging infrastructure instead. Stack trace: {0} */, null, null, 0, 0, 0, 0, Xamarin.Localization.MSBuild.MSBStrings.E7178, Environment.StackTrace); + } + + public static IDisposable EnsureNoConsoleUsage (TaskLoggingHelper log) + { + return new NoConsoleUsage (new ConsoleToTaskWriter (log)); + } + + class NoConsoleUsage : IDisposable { + TextWriter? originalStdout; + TextWriter? originalStderr; + + public NoConsoleUsage (ConsoleToTaskWriter redirector) + { + originalStdout = Console.Out; + originalStderr = Console.Error; + Console.SetOut (redirector); + Console.SetError (redirector); + } + + ~NoConsoleUsage () + { + Restore (); + } + + void IDisposable.Dispose () + { + Restore (); + GC.SuppressFinalize (this); + } + + void Restore () + { + if (originalStdout is not null) { + Console.SetOut (originalStdout); + originalStdout = null; + } + if (originalStderr is not null) { + Console.SetError (originalStderr); + originalStderr = null; + } + } + } +} diff --git a/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs b/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs deleted file mode 100644 index 8bda5c48e690..000000000000 --- a/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2021, Microsoft Corp. All rights reserved, - -using System.Collections.Generic; - -using Xamarin.Utils; - -#nullable enable - -namespace Xamarin.Bundler { - public static partial class ErrorHelper { - public static ApplePlatform Platform; - - internal static string GetPrefix (IToolLog? log) - { - return Xamarin.MacDev.Tasks.LoggingExtensions.ErrorPrefix; - } - - public enum WarningLevel { - Error = -1, - Warning = 0, - Disable = 1, - } - - static Dictionary? warning_levels; - - public static WarningLevel GetWarningLevel (IToolLog log, int code) - { - WarningLevel level; - - if (warning_levels is null) - return WarningLevel.Warning; - - // code -1: all codes - if (warning_levels.TryGetValue (-1, out level)) - return level; - - if (warning_levels.TryGetValue (code, out level)) - return level; - - return WarningLevel.Warning; - } - - public static void SetWarningLevel (WarningLevel level, int? code = null /* if null, apply to all warnings */) - { - if (warning_levels is null) - warning_levels = new Dictionary (); - if (code.HasValue) { - warning_levels [code.Value] = level; - } else { - warning_levels [-1] = level; // code -1: all codes. - } - } - } -} diff --git a/msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs b/msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs index 367f3f5bd46a..2f0f9b7a5538 100644 --- a/msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs +++ b/msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs @@ -74,6 +74,19 @@ public static void LogTaskProperty (this TaskLoggingHelper log, string propertyN log.LogMessage (TaskPropertyImportance, " {0}: {1}", propertyName, value); } + /// + /// Creates an MSBuild error following our MTErrors convention. + /// + /// For every new error we need to update "docs/website/mtouch-errors.md" and "tools/mtouch/error.cs". + /// In the 7xxx range for MSBuild error. + /// The error's message to be displayed in the error pad. + /// Path to the known guilty file or null. + /// Line number in the file where the error was found, or 0 if unknown. + public static void LogError (this TaskLoggingHelper log, int errorCode, string? fileName, int lineNumber, string message, params object? [] args) + { + log.LogError (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", lineNumber, 0, 0, 0, message, args); + } + /// /// Creates an MSBuild error following our MTErrors convention. /// @@ -91,6 +104,16 @@ public static void LogWarning (this TaskLoggingHelper log, int errorCode, string log.LogWarning (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", 0, 0, 0, 0, message, args); } + public static void LogWarning (this TaskLoggingHelper log, int errorCode, string? fileName, int lineNumber, string message, params object? [] args) + { + log.LogWarning (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", lineNumber, 0, 0, 0, message, args); + } + + public static void LogMessage (this TaskLoggingHelper log, MessageImportance importance, int errorCode, string? fileName, int lineNumber, string message, params object? [] args) + { + log.LogMessage (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", lineNumber, 0, 0, 0, importance, message, args); + } + public static bool LogErrorsFromException (this TaskLoggingHelper log, Exception exception, bool showStackTrace = true, bool showDetail = true) { var exceptions = new List (); diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs new file mode 100644 index 000000000000..8be422665932 --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +using Xamarin.Build; +using Xamarin.Bundler; +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.MacDev.Tasks { + public class PrepareAssemblies : XamarinTask { + const string ErrorPrefix = "MX"; + + #region Inputs + [Required] + public ITaskItem [] InputAssemblies { get; set; } = []; + + public string MakeReproPath { get; set; } = ""; + + public string OutputDirectory { get; set; } = ""; + + [Required] + public ITaskItem? OptionsFile { get; set; } + #endregion + + #region Outputs + [Output] + public ITaskItem [] OutputAssemblies { get; set; } = []; + #endregion + + Dictionary map = new (); + + AssemblyPreparerInfo GetAssemblyInfo (ITaskItem item) + { + var inputPath = item.ItemSpec; + var outputPath = Path.Combine (OutputDirectory, Path.GetFileName (inputPath)); + var isTrimmableString = item.GetMetadata ("IsTrimmable"); + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = item.GetMetadata ("TrimMode"); + var rv = new AssemblyPreparerInfo (inputPath, outputPath, isTrimmable, trimMode); + map [rv] = item; + return rv; + } + + public override bool Execute () + { + // Capture Console usage and show an error if anything uses Console.[Error.]Write* + using var consoleToLog = ConsoleToTaskWriter.EnsureNoConsoleUsage (Log); + + try { + var infos = InputAssemblies.Select (GetAssemblyInfo).ToArray (); + using var preparer = new AssemblyPreparer (this, infos, OptionsFile?.ItemSpec ?? ""); + preparer.MakeReproPath = MakeReproPath; + var rv = preparer.Prepare (out var exceptions); + + foreach (var pe in exceptions) { + if (pe.IsError (this)) { + ((IToolLog) this).LogError (pe); + } else { + ((IToolLog) this).LogWarning (pe); + } + } + + var outputAssemblies = preparer.Assemblies.Select (v => { + var item = new TaskItem (v.OutputPath); + map [v].CopyMetadataTo (item); + item.SetMetadata ("BeforePrepareAssembliesPath", v.InputPath); + return (ITaskItem) item; + }).ToList (); + + outputAssemblies.AddRange (preparer.AddedAssemblies.Select (v => { + var rv = new TaskItem (v.Path); + rv.SetMetadata ("PostprocessAssembly", "true"); + rv.SetMetadata ("RelativePath", preparer.Configuration.AssemblyPublishDir + Path.GetFileName (v.Path)); + if (v.OriginatingAssembly is not null) { + var originatingItem = map.SingleOrDefault (kvp => Path.GetFileName (kvp.Key.InputPath) == Path.GetFileName (v.OriginatingAssembly)).Value; + if (originatingItem is null) { + Log.LogMessage (MessageImportance.Low, $"Could not find originating assembly for {v.Path} with originating assembly name {v.OriginatingAssembly}"); + } else { + var metadata = originatingItem.MetadataNames.Cast ().ToList (); + if (metadata.Contains ("TrimMode")) + rv.SetMetadata ("TrimMode", originatingItem.GetMetadata ("TrimMode")); + if (metadata.Contains ("IsTrimmable")) + rv.SetMetadata ("IsTrimmable", originatingItem.GetMetadata ("IsTrimmable")); + } + } + return rv; + })); + + OutputAssemblies = outputAssemblies.ToArray (); + return rv && !Log.HasLoggedErrors; + } catch (Exception e) { + ((IToolLog) this).LogException (e); + return false; + } + } + } +} diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs index 3cd5a9386821..99003df534c4 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs @@ -13,7 +13,6 @@ using Xamarin.Localization.MSBuild; using Xamarin.Messaging.Build.Client; using Xamarin.Utils; -using static Xamarin.Bundler.FileCopier; #nullable enable @@ -371,8 +370,11 @@ void ICustomLogger.LogError (string message, Exception? ex) { if (!string.IsNullOrEmpty (message)) Log.LogError (message); - if (ex is not null) + if (ex is ProductException pe) { + LogDiagnostic (pe); + } else if (ex is not null) { Log.LogErrorFromException (ex); + } } void ICustomLogger.LogWarning (string messageFormat, params object? [] args) @@ -404,12 +406,37 @@ void IToolLog.LogError (string message) void IToolLog.LogException (Exception exception) { - ((ICustomLogger) this).LogError ("", exception); + if (exception is ProductException pe) { + LogDiagnostic (pe); + } else { + ((ICustomLogger) this).LogError ($"Unexpected exception '{GetType ().Name}': {exception.Message}", exception); + } + } + + void IToolLog.LogError (ProductException exception) + { + LogDiagnostic (exception); } - void IToolLog.LogError (Exception exception) + void IToolLog.LogWarning (ProductException exception) { - ((ICustomLogger) this).LogError ("", exception); + LogDiagnostic (exception); + } + + protected void LogDiagnostic (ProductException exception) + { + switch (exception.GetWarningLevel (this)) { + case ErrorHelper.WarningLevel.Warning: + Log.LogWarning (exception.Code, exception.FileName, exception.LineNumber, exception.Message); + break; + case ErrorHelper.WarningLevel.Error: + Log.LogError (exception.Code, exception.FileName, exception.LineNumber, exception.Message); + break; + case ErrorHelper.WarningLevel.Disable: + default: + Log.LogMessage (MessageImportance.Low, exception.Code, exception.FileName, exception.LineNumber, exception.Message); + break; + } } #endregion } diff --git a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj index 713db306ebef..cbd090125523 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj +++ b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj @@ -1,7 +1,8 @@ - netstandard2.0;net$(BundledNETCoreAppTargetFrameworkVersion) + net$(BundledNETCoreAppTargetFrameworkVersion);netstandard2.0 + net$(BundledNETCoreAppTargetFrameworkVersion) false compile true @@ -37,6 +38,7 @@ + ProjectReference @@ -69,45 +71,15 @@ - - ApplePlatform.cs - - JsonExtensions.cs - - - IToolLog.cs + external\JsonExtensions.cs StringUtils.cs - - Symbols.cs - - - FileCopier.cs - - - TargetFramework.cs - - - Execution.cs - - - PathUtils.cs - PListExtensions.cs - - MachO.cs - - - error.cs - - - ErrorHelper.cs - external\RuntimeException.cs diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 8307a85ebf48..84749fe8f117 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -84,6 +84,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. + @@ -3395,6 +3396,54 @@ Copyright (C) 2018 Microsoft. All rights reserved. + + false + + + + <_PrepareAssembliesForPreparationDependsOn> + _ComputeAssembliesToPostprocessOnPublish; + _ComputeLinkerInputs; + + + + + + <_AssembliesToPrepare Include="@(ResolvedFileToPublish)" Condition="'%(ResolvedFileToPublish.PostprocessAssembly)' == 'true'" /> + + + $([MSBuild]::EnsureTrailingSlash('$(DeviceSpecificIntermediateOutputPath)prepared-assemblies')) + + + + + + + + + + + + + + + + diff --git a/src/ObjCRuntime/Registrar.core.cs b/src/ObjCRuntime/Registrar.core.cs index 958a17174874..86cd2436e8d0 100644 --- a/src/ObjCRuntime/Registrar.core.cs +++ b/src/ObjCRuntime/Registrar.core.cs @@ -8,7 +8,11 @@ abstract partial class Registrar { [return: NotNullIfNotNull (nameof (getterSelector))] internal static string? CreateSetterSelector (string? getterSelector) { +#if NET if (string.IsNullOrEmpty (getterSelector)) +#else + if (string.IsNullOrEmpty (getterSelector) || getterSelector is null) +#endif return getterSelector; var first = (int) getterSelector [0]; @@ -21,7 +25,11 @@ abstract partial class Registrar { [return: NotNullIfNotNull (nameof (name))] public static string? SanitizeObjectiveCName (string? name) { +#if NET if (string.IsNullOrEmpty (name)) +#else + if (string.IsNullOrEmpty (name) || name is null) +#endif return name; StringBuilder? sb = null; diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index d33f63746e84..303004248263 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -220,7 +220,11 @@ public void VerifyRegisterAttribute ([NotNullIfNotNull (nameof (exceptions))] re return; var name = RegisterAttribute.Name; +#if NET if (string.IsNullOrEmpty (name)) +#else + if (string.IsNullOrEmpty (name) || name is null) +#endif return; for (int i = 0; i < name.Length; i++) { @@ -540,7 +544,11 @@ abstract internal class ObjCMember { public bool SetExportAttribute (ExportAttribute ea, [NotNullIfNotNull (nameof (exceptions))] ref List? exceptions) { +#if NET if (string.IsNullOrEmpty (ea.Selector)) { +#else + if (string.IsNullOrEmpty (ea.Selector) || ea.Selector is null) { +#endif AddException (ref exceptions, Registrar.CreateException (4135, this, Errors.MT4135, FullName)); return false; } @@ -2181,7 +2189,11 @@ void FlattenInterfaces (TType? [] ifaces) objcType.Add (objcGetter, ref exceptions); +#if NET if (!string.IsNullOrEmpty (attrib.SetterSelector)) { +#else + if (!string.IsNullOrEmpty (attrib.SetterSelector) && attrib.SetterSelector is not null) { +#endif var objcSetter = new ObjCMethod (this, objcType, null) { Name = attrib.Name, Selector = attrib.SetterSelector, diff --git a/src/bgen/BindingTouch.cs b/src/bgen/BindingTouch.cs index 7b9e2d32dadb..2744ae3f2547 100644 --- a/src/bgen/BindingTouch.cs +++ b/src/bgen/BindingTouch.cs @@ -587,7 +587,12 @@ public void LogError (string message) Console.Error.WriteLine (message); } - public void LogError (Exception exception) + public void LogError (BindingException exception) + { + ErrorHelper.Show (exception); + } + + public void LogWarning (BindingException exception) { ErrorHelper.Show (exception); } diff --git a/tests/assembly-preparer/BaseClass.cs b/tests/assembly-preparer/BaseClass.cs new file mode 100644 index 000000000000..93e3f02a0011 --- /dev/null +++ b/tests/assembly-preparer/BaseClass.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AssemblyPreparerTests; + +public abstract class BaseClass { + public void AssertPrepare (AssemblyPreparer preparer) + { + if (!preparer.Prepare (out var exceptions)) { + foreach (var ex in exceptions) { + Console.WriteLine (ex.ToString ()); + if (ex.InnerException is not null) + Console.WriteLine ($" Inner: {ex.InnerException}"); + } + Assert.Fail ($"Prepare failed, exceptions:\n\t{string.Join ("\n\t", exceptions.Select (v => v.ToString ()))}"); + } + Assert.That (exceptions, Is.Empty, "Exceptions"); + } + + public bool AssertPrepare (ApplePlatform platform, bool isCoreCLR, string code, out AssemblyDefinition assemblyDefinition) + { + return AssertPrepare (platform, isCoreCLR, RegistrarMode.Dynamic, code, out assemblyDefinition); + } + + // returns true if the test assembly was modified + public bool AssertPrepare (ApplePlatform platform, bool isCoreCLR, RegistrarMode registrar, string code, out AssemblyDefinition assemblyDefinition) + { + AssemblyPreparer? preparer = null; + var rv = AssertPrepareCode (platform, isCoreCLR, p => { + p.Registrar = registrar; + preparer = p; + }, code, out string outputPath); + var resolver = new DefaultAssemblyResolver (); + var dirs = preparer!.Assemblies.Select (v => Path.GetDirectoryName (v.OutputPath)).Distinct ().ToList (); + dirs.ForEach (v => resolver.AddSearchDirectory (v)); + var readerParameters = new ReaderParameters { + ReadSymbols = true, + AssemblyResolver = resolver, + }; + assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath, readerParameters); + return rv; + } + + // returns true if the test assembly was modified + public bool AssertPrepareCode (ApplePlatform platform, bool isCoreCLR, Action? configure, string code, out string outputPath) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + var csproj = $@" + + + net$(BundledNETCoreAppTargetFrameworkVersion)-{platform.AsString ().ToLower ()} + false + true + + + "; + + var tmpdir = Xamarin.Cache.CreateTemporaryDirectory (); + + var config = $@" + AreAnyAssembliesTrimmed=true + PublishTrimmed=true + IntermediateOutputPath={Path.Combine (tmpdir, "intermediate")} + Platform={platform.AsString ()} + PlatformAssembly=Microsoft.{platform.AsString ()}.dll + SdkDevPath={Configuration.XcodeLocation} + SdkVersion={Configuration.GetSdkVersion (platform)} + TargetFramework={TargetFramework.GetTargetFramework (platform)} + "; + var configpath = Path.Combine (tmpdir, "config.txt"); + File.WriteAllText (configpath, config); + + File.WriteAllText (Path.Combine (tmpdir, "Test.cs"), code); + var csprojPath = Path.Combine (tmpdir, "Test.csproj"); + File.WriteAllText (csprojPath, csproj); + var properties = new Dictionary { + { "TreatWarningsAsErrors", "false" }, + }; + DotNet.AssertBuild (csprojPath, properties); + var assemblyDir = Path.Combine (tmpdir, "bin", "Debug"); + + var assemblies = Configuration.GetImplementationAssemblies (platform, isCoreCLR); + assemblies.Add (Path.Combine (assemblyDir, "Test.dll")); + var infos = assemblies.Select (v => new AssemblyPreparerInfo (v, Path.Combine (assemblyDir, "out", Path.GetFileName (v)), true, "link")).ToArray (); + var logger = new TestLogger () { Platform = platform }; + var preparer = new AssemblyPreparer (logger, infos, configpath); + if (configure is not null) + configure (preparer); + AssertPrepare (preparer); + + var testInfo = infos.Single (v => Path.GetFileNameWithoutExtension (v.InputPath) == "Test"); + outputPath = testInfo.OutputPath; + Console.WriteLine ("Output assembly: " + outputPath); + preparer.Dispose (); + return testInfo.InputPath != testInfo.OutputPath; + } +} diff --git a/tests/assembly-preparer/GlobalUsings.cs b/tests/assembly-preparer/GlobalUsings.cs new file mode 100644 index 000000000000..4e06615734f1 --- /dev/null +++ b/tests/assembly-preparer/GlobalUsings.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +global using System.IO; +global using System.Runtime.InteropServices; + +global using Mono.Cecil; +global using NUnit.Framework; + +global using Xamarin; +global using Xamarin.Bundler; +global using Xamarin.Build; +global using Xamarin.Tests; +global using Xamarin.Utils; + +// These tests are rather memory hungry, so running them in parallel really makes my machine crawl. +// [assembly: Parallelizable (ParallelScope.Children)] diff --git a/tests/assembly-preparer/InlineDlfcnMethodsStepTests.cs b/tests/assembly-preparer/InlineDlfcnMethodsStepTests.cs new file mode 100644 index 000000000000..fa0d1f66f6a4 --- /dev/null +++ b/tests/assembly-preparer/InlineDlfcnMethodsStepTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class InlineDlfcnMethodsStepTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void MarkedTest (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using CoreAnimation; + using Foundation; + using ObjCRuntime; + + class MyClass : NSObject { + void GetIntPtr () + { + Console.WriteLine (Dlfcn.GetIntPtr (0, ""NativeSymbol"")); + } + }"; + + AssertPrepare (platform, isCoreCLR, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var platformReference = assemblyDefinition.MainModule.AssemblyReferences.Single (v => v.Name == $"Microsoft.{platform.AsString ()}"); + var platformAssembly = assemblyDefinition.MainModule.AssemblyResolver.Resolve (platformReference); + var dlfcn = platformAssembly.MainModule.Types.Single (v => v.Name == "Dlfcn"); + + var cctor = type.GetStaticConstructor (); + Assert.That (cctor, Is.Null, "No static constructor should be needed."); + + void AssertHasDlfcnPInvokeCall (MethodDefinition method) + { + var instructions = method.Body.Instructions; + var call = instructions.FirstOrDefault (v => v.OpCode == OpCodes.Call && v.Operand is MethodReference mr && mr.DeclaringType.FullName == dlfcn.FullName); + Assert.That (call, Is.Not.Null, $"Expected a call to Dlfcn in {method}"); + var resolvedMethod = ((MethodReference) call.Operand).Resolve (); + Assert.That (resolvedMethod, Is.Not.Null, $"Expected the call to resolve to a method in Dlfcn for {method}"); + Assert.That (resolvedMethod.PInvokeInfo, Is.Null, $"Expected the method to not be a PInvoke method for {method}"); + } + + Assert.Multiple (() => { + AssertHasDlfcnPInvokeCall (type.Methods.Single (v => v.Name == "GetIntPtr")); + }); + } +} diff --git a/tests/assembly-preparer/Makefile b/tests/assembly-preparer/Makefile new file mode 100644 index 000000000000..b01174cb5734 --- /dev/null +++ b/tests/assembly-preparer/Makefile @@ -0,0 +1,8 @@ +TOP=../.. +include $(TOP)/Make.config + +build: + $(DOTNET) build *.csproj + +run-tests: + $(DOTNET) test *.csproj diff --git a/tests/assembly-preparer/MarkIProtocolHandlerTests.cs b/tests/assembly-preparer/MarkIProtocolHandlerTests.cs new file mode 100644 index 000000000000..fb7f0fcd41dd --- /dev/null +++ b/tests/assembly-preparer/MarkIProtocolHandlerTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class MarkIProtocolHandlerTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void DynamicRegistrar (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + [Protocol] + interface IProtocol { + } + + class MyClass : NSObject, IProtocol { + } + "; + + AssertPrepare (platform, isCoreCLR, RegistrarMode.Dynamic, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var cctor = type.GetStaticConstructor (); + Assert.That (cctor, Is.Not.Null, "Expected a static constructor to be added"); + var attribs = cctor.CustomAttributes?.Where (v => v.AttributeType.Name == "DynamicDependencyAttribute").OrderBy (v => string.Join (", ", v.ConstructorArguments.Select (v => v.Value?.ToString ()))).ToArray (); + Assert.That (attribs, Is.Not.Null, "Attributes"); + Assert.That (attribs.Count, Is.EqualTo (1), "Attribute count"); + // PreserveProtocolsStep adds DDA(DynamicallyAccessedMemberTypes.Interfaces, typeof(MyClass)) + Assert.That ((int) attribs [0].ConstructorArguments [0].Value, Is.EqualTo (0x2000), "First attribute's first argument (DynamicallyAccessedMemberTypes.Interfaces)"); + Assert.That (((TypeReference) attribs [0].ConstructorArguments [1].Value).FullName, Is.EqualTo ("MyClass"), "First attribute's second argument"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void ManagedStaticRegistrar (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + [Protocol] + interface IProtocol { + } + + class MyClass : NSObject, IProtocol { + } + "; + + // With ManagedStatic registrar, PreserveProtocolsStep doesn't run, + // so MyClass's cctor should not have any DDA for protocol preservation. + AssertPrepare (platform, isCoreCLR, RegistrarMode.ManagedStatic, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var cctor = type.GetStaticConstructor (); + if (cctor is not null) { + var ddaAttribs = cctor.CustomAttributes?.Where (v => v.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (ddaAttribs, Is.Empty, "No DynamicDependencyAttributes expected on MyClass's cctor"); + } + } +} diff --git a/tests/assembly-preparer/OptimizeGeneratedCodeHandlerTests.cs b/tests/assembly-preparer/OptimizeGeneratedCodeHandlerTests.cs new file mode 100644 index 000000000000..5a5ee83d47e4 --- /dev/null +++ b/tests/assembly-preparer/OptimizeGeneratedCodeHandlerTests.cs @@ -0,0 +1,251 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class OptimizeGeneratedCodeHandlerTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void RemoveEnsureUIThread (ApplePlatform platform, bool isCoreCLR) + { + var ensureUIThreadCall = platform == ApplePlatform.MacOSX + ? "AppKit.NSApplication.EnsureUIThread ();" + : "UIKit.UIApplication.EnsureUIThread ();"; + + var usingDirective = platform == ApplePlatform.MacOSX + ? "using AppKit;" + : "using UIKit;"; + + var code = $@" + using System; + using Foundation; + using ObjCRuntime; + {usingDirective} + + class MyClass : NSObject {{ + [BindingImpl (BindingImplOptions.Optimizable)] + [Export (""myMethod"")] + public void MyMethod () {{ + {ensureUIThreadCall} + }} + }}"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RemoveUIThreadChecks = true; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var method = type.Methods.Single (v => v.Name == "MyMethod"); + + var hasEnsureUIThread = method.Body.Instructions.Any (i => + i.OpCode.Code == Code.Call && + (i.Operand as MethodReference)?.Name == "EnsureUIThread"); + Assert.That (hasEnsureUIThread, Is.False, "EnsureUIThread call should be removed"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void KeepEnsureUIThreadWhenOptimizationDisabled (ApplePlatform platform, bool isCoreCLR) + { + var ensureUIThreadCall = platform == ApplePlatform.MacOSX + ? "AppKit.NSApplication.EnsureUIThread ();" + : "UIKit.UIApplication.EnsureUIThread ();"; + + var usingDirective = platform == ApplePlatform.MacOSX + ? "using AppKit;" + : "using UIKit;"; + + var code = $@" + using System; + using Foundation; + using ObjCRuntime; + {usingDirective} + + class MyClass : NSObject {{ + [BindingImpl (BindingImplOptions.Optimizable)] + [Export (""myMethod"")] + public void MyMethod () {{ + {ensureUIThreadCall} + }} + }}"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RemoveUIThreadChecks = false; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var method = type.Methods.Single (v => v.Name == "MyMethod"); + + var hasEnsureUIThread = method.Body.Instructions.Any (i => + i.OpCode.Code == Code.Call && + (i.Operand as MethodReference)?.Name == "EnsureUIThread"); + Assert.That (hasEnsureUIThread, Is.True, "EnsureUIThread call should be preserved when optimization is disabled"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void OptimizeProtocolInterfaceStaticConstructor (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + [Protocol] + interface IMyProtocol { + [BindingImpl (BindingImplOptions.Optimizable)] + static IMyProtocol () { + GC.KeepAlive (null); + } + } + + class MyClass : NSObject, IMyProtocol { + }"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RegisterProtocols = true; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "IMyProtocol"); + var cctor = type.GetStaticConstructor (); + + Assert.That (cctor, Is.Not.Null, "Static constructor should still exist"); + Assert.That (cctor.Body.Instructions.Count, Is.EqualTo (1), "Static constructor should have only a ret instruction"); + Assert.That (cctor.Body.Instructions [0].OpCode.Code, Is.EqualTo (Code.Ret), "Static constructor should only contain ret"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void KeepProtocolStaticConstructorWhenOptimizationDisabled (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + [Protocol] + interface IMyProtocol { + [BindingImpl (BindingImplOptions.Optimizable)] + static IMyProtocol () { + GC.KeepAlive (null); + } + } + + class MyClass : NSObject, IMyProtocol { + }"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RegisterProtocols = false; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "IMyProtocol"); + var cctor = type.GetStaticConstructor (); + + Assert.That (cctor, Is.Not.Null, "Static constructor should still exist"); + Assert.That (cctor.Body.Instructions.Count, Is.GreaterThan (1), "Static constructor should not be optimized"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void NoOptimizationWithoutBindingAttributes (ApplePlatform platform, bool isCoreCLR) + { + var ensureUIThreadCall = platform == ApplePlatform.MacOSX + ? "AppKit.NSApplication.EnsureUIThread ();" + : "UIKit.UIApplication.EnsureUIThread ();"; + + var usingDirective = platform == ApplePlatform.MacOSX + ? "using AppKit;" + : "using UIKit;"; + + var code = $@" + using System; + using Foundation; + using ObjCRuntime; + {usingDirective} + + class MyClass : NSObject {{ + [Export (""myMethod"")] + public void MyMethod () {{ + {ensureUIThreadCall} + }} + }}"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RemoveUIThreadChecks = true; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var method = type.Methods.Single (v => v.Name == "MyMethod"); + + var hasEnsureUIThread = method.Body.Instructions.Any (i => + i.OpCode.Code == Code.Call && + (i.Operand as MethodReference)?.Name == "EnsureUIThread"); + Assert.That (hasEnsureUIThread, Is.True, "EnsureUIThread call should be preserved without [BindingImpl(Optimizable)]"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void DeadCodeElimination (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + class MyClass : NSObject { + [BindingImpl (BindingImplOptions.Optimizable)] + [Export (""myMethod"")] + public int MyMethod () { + if (true) { + return 1; + } + return 2; + } + }"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.DeadCodeElimination = true; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var method = type.Methods.Single (v => v.Name == "MyMethod"); + + // After dead code elimination, there should be no ldc.i4.2 (the unreachable return 2) + var hasDeadCode = method.Body.Instructions.Any (i => + i.OpCode.Code == Code.Ldc_I4_2); + Assert.That (hasDeadCode, Is.False, "Dead code (return 2) should be eliminated"); + } +} diff --git a/tests/assembly-preparer/PreserveBlockCodeHandlerTests.cs b/tests/assembly-preparer/PreserveBlockCodeHandlerTests.cs new file mode 100644 index 000000000000..ad39cd811c5c --- /dev/null +++ b/tests/assembly-preparer/PreserveBlockCodeHandlerTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class PreserveBlockCodeHandlerTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void MarkedTest (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using ObjCRuntime; + namespace ObjCRuntime; + class Trampolines { + static internal class SDInnerBlock { + // this field is not preserved by other means, but it must not be linked away + static internal readonly DInnerBlock Handler = Invoke; + + [MonoPInvokeCallback (typeof (DInnerBlock))] + static internal void Invoke (IntPtr block, int magic_number) + { + } + + public delegate void DInnerBlock (IntPtr block, int magic_number); + } + }"; + + AssertPrepare (platform, isCoreCLR, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "Trampolines").NestedTypes.Single (v => v.Name == "SDInnerBlock"); + var cctor = type.GetStaticConstructor (); + var attribs = cctor.CustomAttributes?.OrderBy (v => string.Join (", ", v.ConstructorArguments.Select (v => v.Value?.ToString ()))).ToArray (); + Assert.That (attribs, Is.Not.Null, "Attributes"); + Assert.That (attribs.Count, Is.EqualTo (2), "Attribute count"); + Assert.That (attribs.All (v => v.AttributeType.Name == "DynamicDependencyAttribute"), Is.True, "Attribute name"); + // Handler field: DDA(string memberSignature, string typeName, string assemblyName) + Assert.That (attribs [0].ConstructorArguments.Count, Is.EqualTo (3), "First attribute's argument count"); + Assert.That ((string) attribs [0].ConstructorArguments [0].Value, Is.EqualTo ("Handler"), "First attribute's first argument"); + Assert.That ((string) attribs [0].ConstructorArguments [1].Value, Is.EqualTo ("ObjCRuntime.Trampolines.SDInnerBlock"), "First attribute's second argument"); + Assert.That ((string) attribs [0].ConstructorArguments [2].Value, Is.EqualTo ("Test"), "First attribute's third argument"); + + // Invoke method: DDA(string memberSignature) - same declaring type, so simpler constructor + Assert.That (attribs [1].ConstructorArguments.Count, Is.EqualTo (1), "Second attribute's argument count"); + Assert.That ((string) attribs [1].ConstructorArguments [0].Value, Is.EqualTo ("Invoke(System.IntPtr,System.Int32)"), "Second attribute's first argument"); + } +} diff --git a/tests/assembly-preparer/PreserveSmartEnumConversionsTest.cs b/tests/assembly-preparer/PreserveSmartEnumConversionsTest.cs new file mode 100644 index 000000000000..7c690caa9037 --- /dev/null +++ b/tests/assembly-preparer/PreserveSmartEnumConversionsTest.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class PreserveSmartEnumConversionsTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void MarkedTest (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using CoreAnimation; + using Foundation; + using ObjCRuntime; + + class MyClass : NSObject { + [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode RWProperty { get; set; } + + [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode ROProperty { get { return default; } } + + [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode WOProperty { set { } } + + [return: BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode Method1 () { return default; } + + public void Method2 ([BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p1) {} + + public void Method3 ([BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p1, [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p2) {} + + [return: BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode Method4 ([BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p1, [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p2) { return default;} + }"; + + AssertPrepare (platform, isCoreCLR, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var cctor = type.GetStaticConstructor (); + Assert.That (cctor, Is.Null, "No static constructor should be needed."); + + void AssertHasDynamicDependency (ICustomAttributeProvider provider, string memberSignature, string typeName, string assemblyName) + { + var ddaAttributes = provider.CustomAttributes.Where (v => v.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute").ToArray (); + var found = 0; + foreach (var ca in ddaAttributes) { + if (ca.ConstructorArguments.Count != 3) + continue; + var attribMemberSignature = (string) ca.ConstructorArguments [0].Value!; + if (attribMemberSignature != memberSignature) + continue; + var attribTypeName = (string) ca.ConstructorArguments [1].Value!; + if (attribTypeName != typeName) + continue; + var attribAssemblyName = (string) ca.ConstructorArguments [2].Value!; + if (attribAssemblyName != assemblyName) + continue; + + found++; + } + + if (found == 1) + return; + + var attributesAsString = ddaAttributes + .Select (v => { + switch (v.ConstructorArguments.Count) { + case 3: + return $"[DynamicDependency (\"{v.ConstructorArguments [0].Value}\", \"{v.ConstructorArguments [1].Value}\", \"{v.ConstructorArguments [2].Value}\")]"; + case 2: + return $"[DynamicDependency (\"{v.ConstructorArguments [0].Value}\", \"{v.ConstructorArguments [1].Value}\")]"; + case 1: + return $"[DynamicDependency (\"{v.ConstructorArguments [0].Value}\")]"; + default: + return string.Join (", ", v.ConstructorArguments.Select (x => x.Value?.ToString () ?? "null")); + } + }) + .OrderBy (v => v) + .ToArray (); + + string msg; + if (found == 0) { + if (attributesAsString.Length == 0) { + msg = $"Expected [DynamicDependency (\"{memberSignature}\", \"{typeName}\", \"{assemblyName}\")] on {provider}, got no attributes."; + } else { + msg = $"Expected [DynamicDependency (\"{memberSignature}\", \"{typeName}\", \"{assemblyName}\")] on {provider}, got:\n\t{string.Join ("\n\t", attributesAsString)}"; + } + } else { + msg = $"Expected exactly one [DynamicDependency (\"{memberSignature}\", \"{typeName}\", \"{assemblyName}\")] on {provider}, got {found}:\n\t{string.Join ("\n\t", attributesAsString)}"; + } + Console.WriteLine (msg); + Assert.Fail (msg); + } + + void AssertHasDynamicDependencies (ICustomAttributeProvider provider) + { + AssertHasDynamicDependency (provider, "GetConstant(CoreAnimation.CAToneMapMode)", "CoreAnimation.CAToneMapModeExtensions", $"Microsoft.{platform.AsString ()}"); + AssertHasDynamicDependency (provider, "GetValue(Foundation.NSString)", "CoreAnimation.CAToneMapModeExtensions", $"Microsoft.{platform.AsString ()}"); + } + + Assert.Multiple (() => { + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "get_RWProperty")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "set_RWProperty")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "get_ROProperty")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "set_WOProperty")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "Method1")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "Method2")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "Method3")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "Method4")); + }); + } +} diff --git a/tests/assembly-preparer/ReproTest.cs b/tests/assembly-preparer/ReproTest.cs new file mode 100644 index 000000000000..267f7f417568 --- /dev/null +++ b/tests/assembly-preparer/ReproTest.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Build.Framework; +using Microsoft.Build.Logging.StructuredLogger; + +namespace AssemblyPreparerTests; + +public class ReproTest : BaseClass { + [TestCase (ApplePlatform.iOS, false)] + public void RoundTrip (ApplePlatform platform, bool isCoreCLR) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + // build once with a repro path + // load everything from the repro path, prepare again (with a different repro path this time), + // and verify that the arguments.txt files from each preparation are identical + // + // this test can also be repurposed to run an existing repro by setting the _PrepareAssembliesMakeReproPath variable + + var reproPath = Environment.GetEnvironmentVariable ("_PrepareAssembliesMakeReproPath"); + var referenceAssemblies = Configuration.GetReferenceAssemblies (platform, false); + if (string.IsNullOrEmpty (reproPath)) { + var code = @"public class SomeLibrary {}"; + + reproPath = Xamarin.Cache.CreateTemporaryDirectory (); + Directory.Delete (reproPath); // the repro path can't exist prior to Prepare + AssertPrepareCode (platform, isCoreCLR, (preparer) => { + preparer.MakeReproPath = reproPath; + preparer.Registrar = RegistrarMode.Dynamic; + }, code, out string _); + } + + var lines = File.ReadAllLines (Path.Combine (reproPath, "arguments.txt")); + + var ap = AssemblyPreparer.LoadFromReproPath (reproPath); + ap.MakeReproPath = Xamarin.Cache.CreateTemporaryDirectory (); + Directory.Delete (ap.MakeReproPath); // the repro path can't exist prior to Prepare + AssertPrepare (ap); + + var lines2 = File.ReadAllLines (Path.Combine (ap.MakeReproPath, "arguments.txt")); + + // Normalize repro paths before comparison, since they will always differ + var normalizedLines = lines.Select (l => l.Replace (reproPath, "")).ToArray (); + var normalizedLines2 = lines2.Select (l => l.Replace (ap.MakeReproPath, "")).ToArray (); + Assert.That (normalizedLines, Is.EqualTo (normalizedLines2), "Repro arguments match"); + } + + // This test is here only to easily debug the PrepareAssemblies task from a build that produced a binlog. + // Just copy the binlog to /tmp/assembly-preparer.binlog and run this test. It will find the PrepareAssemblies + // task in the binlog, extract the relevant parameters, and run the preparation logic with those parameters. + [Test] + public void FromBinlog () + { + var binlogPath = "/tmp/assembly-preparer.binlog"; + if (!File.Exists (binlogPath)) + Assert.Ignore (); // The binlog doesn't exist, so nothing to do + + var reader = new BinLogReader (); + var records = reader.ReadRecords (binlogPath).ToArray (); + var originalBinlogPath = records + .Select (r => r.Args) + .OfType () + .Where (r => r.SenderName == "BinaryLogger" && r.Message?.StartsWith ("BinLogFilePath=") == true) + .Select (v => v.Message?.Substring ("BinLogFilePath=".Length) ?? "") + .SingleOrDefault (); + var originalBinlogDirectory = Path.GetDirectoryName (originalBinlogPath)!; + foreach (var record in records) { + if (record is null) + continue; + + if (record.Args is null) + continue; + + if (record.Args is TaskStartedEventArgs tsea && tsea.TaskName == "PrepareAssemblies") { + var taskId = tsea.BuildEventContext?.TaskId; + if (taskId is null) + continue; + var relevantRecords = records. + Select (v => v.Args). + Where (v => v is not null). + Where (v => v.BuildEventContext?.TaskId == taskId). + ToArray (); + + var taskParameters = relevantRecords.Where (v => v is TaskParameterEventArgs).Cast ().ToArray (); + + string? getProperty (string name) + { + var param = taskParameters.SingleOrDefault (v => v.ItemType == name); + if (param is null) + return null; + if (param.Items is null) + return null; + if (param.Items.Count != 1) + return null; + var item = param.Items [0]; + if (item is null) + return null; + return ((ITaskItem) item).ItemSpec; + } + ITaskItem []? getItems (string name) + { + var param = taskParameters.SingleOrDefault (v => v.ItemType == name); + if (param is null) + return null; + if (param.Items is null) + return null; + return param.Items.Cast ().ToArray (); + } + var outputDirectory = getProperty ("OutputDirectory"); + var optionsFile = getProperty ("OptionsFile"); + var targetFrameworkMoniker = getProperty ("TargetFrameworkMoniker"); + var makeReproPath = getProperty ("MakeReproPath"); + var inputAssemblies = getItems ("InputAssemblies"); + + if (string.IsNullOrEmpty (outputDirectory)) + throw new InvalidOperationException ("OutputDirectory is required"); + outputDirectory = Path.GetFullPath (outputDirectory, originalBinlogDirectory); + + if (string.IsNullOrEmpty (optionsFile)) + throw new InvalidOperationException ("OptionsFile is required"); + optionsFile = Path.GetFullPath (optionsFile, originalBinlogDirectory); + + if (string.IsNullOrEmpty (targetFrameworkMoniker)) + throw new InvalidOperationException ("TargetFrameworkMoniker is required"); + + if (inputAssemblies is null || inputAssemblies.Length == 0) + throw new InvalidOperationException ("InputAssemblies is required"); + + AssemblyPreparerInfo GetAssemblyInfo (ITaskItem item) + { + var inputPath = Path.GetFullPath (item.ItemSpec, originalBinlogDirectory); + var outputPath = Path.Combine (outputDirectory, Path.GetFileName (inputPath)); + var metadataNames = item.MetadataNames.Cast ().Select (v => v.ToLowerInvariant ()).ToHashSet (); + var isTrimmableString = item.GetMetadata ("IsTrimmable"); + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = item.GetMetadata ("TrimMode"); + + var rv = new AssemblyPreparerInfo (inputPath, outputPath, isTrimmable, trimMode); + return rv; + } + + var platformString = File.ReadAllLines (optionsFile).Single (v => v.StartsWith ("Platform=")).Substring ("Platform=".Length); + var platform = ApplePlatformExtensions.Parse (platformString); + + var infos = inputAssemblies.Select (GetAssemblyInfo).ToArray (); + var logger = new TestLogger () { Platform = platform }; + using var preparer = new AssemblyPreparer (logger, infos, optionsFile); + preparer.MakeReproPath = makeReproPath ?? ""; + var rv = preparer.Prepare (out var exceptions); + return; + } + } + + Assert.Fail ("The task 'PrepareAssemblies' was not found in the provided binlog."); + } +} + + +class TestLogger : IToolLog { + public int Verbosity => 0; + public required ApplePlatform Platform { get; set; } + + public void Log (string value) + { + Console.WriteLine (value); + } + + public void Log (string format, params object? [] args) + { + Console.WriteLine (format, args); + } + + public void LogException (Exception ex) + { + Console.WriteLine (ex.ToString ()); + } + + public void LogError (ProductException ex) + { + Console.WriteLine (ex.ToString ()); + } + + public void LogError (Exception ex) + { + Console.WriteLine (ex.ToString ()); + } + + public void LogError (string value) + { + Console.WriteLine (value); + } + + public void LogWarning (ProductException ex) + { + Console.WriteLine (ex.ToString ()); + } + + public void LogWarning (Exception ex) + { + Console.WriteLine (ex.ToString ()); + } +} diff --git a/tests/assembly-preparer/assembly-preparer-tests.csproj b/tests/assembly-preparer/assembly-preparer-tests.csproj new file mode 100644 index 000000000000..aab0b2d3d64e --- /dev/null +++ b/tests/assembly-preparer/assembly-preparer-tests.csproj @@ -0,0 +1,50 @@ + + + net$(BundledNETCoreAppTargetFrameworkVersion) + latest + enable + enable + false + true + true + + + + + + + + + + + + + + + + external/tests/common/BinLog.cs + + + external/tests/mtouch/Cache.cs + + + external/tests/common/Configuration.cs + + + external/tests/common/ConfigurationNUnit.cs + + + external/tests/common/DotNet.cs + + + external/tests/common/ExecutionHelper.cs + + + external/tools/common/SdkVersions.cs + + + external/tools/common/StringUtils.cs + + + + diff --git a/tests/assembly-preparer/assembly-preparer.slnx b/tests/assembly-preparer/assembly-preparer.slnx new file mode 100644 index 000000000000..990bc0a7a9fb --- /dev/null +++ b/tests/assembly-preparer/assembly-preparer.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/common/Configuration.cs b/tests/common/Configuration.cs index 1735ac8df815..20f92ed994ff 100644 --- a/tests/common/Configuration.cs +++ b/tests/common/Configuration.cs @@ -577,6 +577,63 @@ public static string GetRefLibrary (ApplePlatform platform) { return GetBaseLibrary (platform); } + + public static List GetBCLAssemblies (ApplePlatform platform, bool isCoreCLR) + { + var assemblies = new List (); + string rid; + string packageName; + switch (platform) { + case ApplePlatform.MacCatalyst: + rid = "maccatalyst-arm64"; + packageName = isCoreCLR ? $"microsoft.netcore.app.runtime.maccatalyst-arm64" : $"microsoft.netcore.app.runtime.mono.maccatalyst-arm64"; + break; + case ApplePlatform.iOS: + rid = "ios-arm64"; + packageName = isCoreCLR ? $"microsoft.netcore.app.runtime.ios-arm64" : $"microsoft.netcore.app.runtime.mono.ios-arm64"; + break; + case ApplePlatform.TVOS: + rid = "tvos-arm64"; + packageName = isCoreCLR ? $"microsoft.netcore.app.runtime.tvOS-arm64" : $"microsoft.netcore.app.runtime.mono.tvOS-arm64"; + break; + case ApplePlatform.MacOSX: + rid = "osx-arm64"; + packageName = "microsoft.netcore.app.runtime.osx-arm64"; + if (!isCoreCLR) + throw new InvalidOperationException ($"Mono doesn't support macOS, but was asked for the BCL assemblies for macOS"); + break; + default: + throw new NotSupportedException ($"Unsupported platform: {platform}"); + } + var microsoftNetCoreAppRefPackageVersion = File.ReadAllLines (Path.Combine (RootPath, "dotnet.config")).Single (v => v.StartsWith ("BUNDLED_NETCORE_PLATFORMS_PACKAGE_VERSION=", StringComparison.Ordinal)).Replace ("BUNDLED_NETCORE_PLATFORMS_PACKAGE_VERSION=", ""); + var bclDir = Path.Combine (RootPath, "packages", packageName, microsoftNetCoreAppRefPackageVersion, "runtimes", rid, "lib", DotNetTfm); + var nativeDir = Path.Combine (RootPath, "packages", packageName, microsoftNetCoreAppRefPackageVersion, "runtimes", rid, "native"); + + assemblies.AddRange (Directory.GetFiles (bclDir, "*.dll")); + assemblies.AddRange (Directory.GetFiles (nativeDir, "*.dll")); + + return assemblies; + } + + public static List GetReferenceAssemblies (ApplePlatform platform, bool isCoreCLR) + { + var assemblies = new List (); + + assemblies.AddRange (GetBCLAssemblies (platform, isCoreCLR)); + assemblies.AddRange (GetRefLibrary (platform)); + + return assemblies; + } + + public static List GetImplementationAssemblies (ApplePlatform platform, bool isCoreCLR) + { + var assemblies = new List (); + + assemblies.AddRange (GetBCLAssemblies (platform, isCoreCLR)); + assemblies.AddRange (GetBaseLibraryImplementations (platform).First ()); + + return assemblies; + } #endif // !XAMMAC_TESTS public static IEnumerable GetIncludedPlatforms () diff --git a/tests/common/DotNet.cs b/tests/common/DotNet.cs index 1f7a5c73e868..9e01afeebe45 100644 --- a/tests/common/DotNet.cs +++ b/tests/common/DotNet.cs @@ -109,7 +109,7 @@ public static ExecutionResult AssertNew (string outputDirectory, string template Console.WriteLine (output); Assert.That (rv.ExitCode, Is.EqualTo (0), $"Exit code: {Executable} {StringUtils.FormatArguments (args)}"); } - return new ExecutionResult (output, output, rv.ExitCode); + return new ExecutionResult (output, output, rv.ExitCode, rv.Duration); } public static ExecutionResult InstallWorkload (params string [] workloads) @@ -136,7 +136,7 @@ public static ExecutionResult InstallWorkload (params string [] workloads) Console.WriteLine (msg); Assert.Fail (msg.ToString ()); } - return new ExecutionResult (output, output, rv.ExitCode); + return new ExecutionResult (output, output, rv.ExitCode, rv.Duration); } public static ExecutionResult InstallTool (string tool, string path) @@ -206,7 +206,7 @@ public static ExecutionResult ExecuteCommand (string exe, Dictionary? properties, bool assert_success = true, string? target = null, bool? msbuildParallelism = null, TimeSpan? timeout = null, params string [] extraArguments) @@ -338,7 +338,7 @@ public static ExecutionResult Execute (string verb, string project, Dictionary + @@ -190,6 +191,11 @@ <_TestVariationApplied>true + + true + <_TestVariationApplied>true + + <_InvalidTestVariations Include="$(TestVariation.Split('|'))" Exclude="@(TestVariations)" /> diff --git a/tests/linker/link all/dotnet/shared.csproj b/tests/linker/link all/dotnet/shared.csproj index 6b75ec2a2d4e..33fc00ceb233 100644 --- a/tests/linker/link all/dotnet/shared.csproj +++ b/tests/linker/link all/dotnet/shared.csproj @@ -9,6 +9,7 @@ $(MtouchLink) --optimize=all,-remove-dynamic-registrar,-force-rejected-types-removal $(MtouchExtraArgs) --optimize=-remove-uithread-checks + $(MtouchExtraArgs) --nowarn:2003 $(MtouchExtraArgs) $(RootTestsDirectory)\linker\link all true diff --git a/tests/xharness/Jenkins/TestVariationsFactory.cs b/tests/xharness/Jenkins/TestVariationsFactory.cs index 167d8f6247bb..b623ed22710c 100644 --- a/tests/xharness/Jenkins/TestVariationsFactory.cs +++ b/tests/xharness/Jenkins/TestVariationsFactory.cs @@ -33,6 +33,7 @@ IEnumerable GetTestData (RunTestTask test) var arm64_sim_runtime_identifier = string.Empty; var x64_sim_runtime_identifier = string.Empty; var supports_coreclr = test.Platform == TestPlatform.Mac || jenkins.Harness.DotNetVersion.Major >= 11; + var supports_monovm = test.Platform != TestPlatform.Mac; var supports_x64 = string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("ACES")); // x64 is not supported on ACES machines switch (test.Platform) { @@ -55,7 +56,39 @@ IEnumerable GetTestData (RunTestTask test) } switch (test.TestName) { + case "dont link": + if (supports_coreclr) { + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Dynamic Registrar)", TestVariation = "coreclr|prepare-assemblies|dynamic-registrar", Ignored = ignore }; + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Managed Static Registrar)", TestVariation = "coreclr|prepare-assemblies|managed-static-registrar", Ignored = ignore }; + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + if (supports_monovm) { + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Dynamic Registrar)", TestVariation = "monovm|prepare-assemblies|dynamic-registrar", Ignored = ignore }; + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Managed Static Registrar)", TestVariation = "monovm|prepare-assemblies|managed-static-registrar", Ignored = ignore }; + if (jenkins.Harness.DotNetVersion.Major >= 11) { // on Mono, the trimmable static registrar only works in .NET 11 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Trimmable Static Registrar)", TestVariation = "monovm|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + } + break; + case "link sdk": + if (supports_coreclr) { + // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which only works on CoreCLR in .NET 10 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + if (supports_monovm && jenkins.Harness.DotNetVersion.Major >= 11) { + // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which, on Mono, only works in .NET 11 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Trimmable Static Registrar)", TestVariation = "monovm|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + break; case "link all": + if (supports_coreclr) { + // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which only works on CoreCLR in .NET 10 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + if (supports_monovm && jenkins.Harness.DotNetVersion.Major >= 11) { + // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which, on Mono, only works in .NET 11 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Trimmable Static Registrar)", TestVariation = "monovm|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } if (test.ProjectConfiguration == "Debug") { yield return new TestData { Variation = "Debug (don't bundle original resources)", TestVariation = "do-not-bundle-original-resources" }; } @@ -63,6 +96,7 @@ IEnumerable GetTestData (RunTestTask test) case "monotouch-test": yield return new TestData { Variation = "Release (link sdk)", TestVariation = "release|linksdk", Ignored = ignore }; yield return new TestData { Variation = "Release (link all)", TestVariation = "release|linkall", Ignored = ignore }; + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies)", TestVariation = "prepare-assemblies", Ignored = ignore }; break; } diff --git a/tools/Makefile b/tools/Makefile index 00d896286809..c4c969bf82b7 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -14,3 +14,4 @@ SUBDIRS+=mlaunch SUBDIRS += dotnet-linker +SUBDIRS += assembly-preparer diff --git a/tools/ap-launcher/.gitignore b/tools/ap-launcher/.gitignore new file mode 100644 index 000000000000..af1e7137432d --- /dev/null +++ b/tools/ap-launcher/.gitignore @@ -0,0 +1,2 @@ +.build-stamp + diff --git a/tools/ap-launcher/Makefile b/tools/ap-launcher/Makefile new file mode 100644 index 000000000000..adb26c8c4e3c --- /dev/null +++ b/tools/ap-launcher/Makefile @@ -0,0 +1,8 @@ +TOP=../.. +include $(TOP)/Make.config +include $(TOP)/mk/rules.mk + +build: + $(Q_BUILD) $(DOTNET) build /bl:$@.binlog *.csproj $(DOTNET_BUILD_VERBOSITY) + +all-local:: build diff --git a/tools/ap-launcher/Program.cs b/tools/ap-launcher/Program.cs new file mode 100644 index 000000000000..44da342c46a9 --- /dev/null +++ b/tools/ap-launcher/Program.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Xamarin.Build; +using Xamarin.Bundler; +using Xamarin.Utils; + +class Program { + public static int Main (string [] args) + { + var optionsFile = args.Single (v => v.StartsWith ("--options-file=")).Substring ("--options-file=".Length); + var makeReproPath = args.SingleOrDefault (v => v.StartsWith ("--make-repro-path="))?.Substring ("--make-repro-path=".Length) ?? ""; + + var api = new List (); + foreach (var inputArgs in args.Where (v => v.StartsWith ("--input-assembly=")).Select (v => v.Substring ("--input-assembly=".Length))) { + var ia = inputArgs.Split ('|'); + var inputPath = ia.Single (v => v.StartsWith ("InputPath="))?.Substring ("InputPath=".Length) ?? throw new InvalidOperationException ("InputPath is required"); + var outputPath = ia.Single (v => v.StartsWith ("OutputPath="))?.Substring ("OutputPath=".Length) ?? throw new InvalidOperationException ("OutputPath is required"); + var isTrimmableString = ia.Single (v => v.StartsWith ("IsTrimmable="))?.Substring ("IsTrimmable=".Length); + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = ia.Single (v => v.StartsWith ("TrimMode="))?.Substring ("TrimMode=".Length) ?? ""; + + api.Add (new AssemblyPreparerInfo (inputPath, outputPath, isTrimmable, trimMode)); + } + + var platformString = File.ReadAllLines (optionsFile).Single (v => v.StartsWith ("Platform=")).Substring ("Platform=".Length); + var platform = ApplePlatformExtensions.Parse (platformString); + + var infos = api.ToArray (); + var logger = new TestLogger () { + Platform = platform, + }; + using var preparer = new AssemblyPreparer (logger, infos, optionsFile); + preparer.MakeReproPath = makeReproPath ?? ""; + var rv = preparer.Prepare (out var exceptions); + + return rv && exceptions.Count == 0 ? 0 : 1; + } +} + +class TestLogger : IToolLog { + public int Verbosity => 0; + public required ApplePlatform Platform { get; set; } + + public void Log (string value) + { + Console.WriteLine (value); + } + + public void LogError (string value) + { + Console.WriteLine (value); + } + + public void Log (string format, params object? [] args) + { + Console.WriteLine (format, args); + } + + public void LogException (Exception ex) + { + Console.Error.WriteLine (ex.ToString ()); + } + + public void LogError (ProductException ex) + { + Console.Error.WriteLine (ex.ToString ()); + } + + public void LogWarning (ProductException ex) + { + Console.WriteLine (ex.ToString ()); + } +} diff --git a/tools/ap-launcher/README.md b/tools/ap-launcher/README.md new file mode 100644 index 000000000000..5007c9cc4226 --- /dev/null +++ b/tools/ap-launcher/README.md @@ -0,0 +1,2 @@ +This is just a simple project that references the assembly-preparer library, to make it easy to debug the library in a debugger. + diff --git a/tools/ap-launcher/ap-launcher.csproj b/tools/ap-launcher/ap-launcher.csproj new file mode 100644 index 000000000000..ea7daa76d3f5 --- /dev/null +++ b/tools/ap-launcher/ap-launcher.csproj @@ -0,0 +1,17 @@ + + + + Exe + net$(BundledNETCoreAppTargetFrameworkVersion) + false + false + ap_launcher + enable + enable + + + + + + + diff --git a/tools/ap-launcher/ap-launcher.slnx b/tools/ap-launcher/ap-launcher.slnx new file mode 100644 index 000000000000..f543c9a91276 --- /dev/null +++ b/tools/ap-launcher/ap-launcher.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/tools/assembly-preparer/.gitignore b/tools/assembly-preparer/.gitignore new file mode 100644 index 000000000000..65bf06abaee5 --- /dev/null +++ b/tools/assembly-preparer/.gitignore @@ -0,0 +1,2 @@ +*.csproj.inc + diff --git a/tools/assembly-preparer/AssemblyPreparer.cs b/tools/assembly-preparer/AssemblyPreparer.cs new file mode 100644 index 000000000000..9f2936c32eaa --- /dev/null +++ b/tools/assembly-preparer/AssemblyPreparer.cs @@ -0,0 +1,331 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Runtime.Serialization; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.CompilerServices.SymbolWriter; +using Mono.Linker; +using Mono.Linker.Steps; +using Mono.Tuner; +using MonoTouch.Tuner; +using Xamarin.Bundler; +using Xamarin.Linker; +using Xamarin.Linker.Steps; +using Xamarin.Tuner; +using Xamarin.Utils; + +namespace Xamarin.Build; + +public class AssemblyPreparer : IDisposable { + AggregateLog log = new AggregateLog (); + + LinkerConfiguration configuration; + + public LinkerConfiguration Configuration => configuration; + + public string MakeReproPath { get; set; } = string.Empty; + + public RegistrarMode Registrar { + get => configuration.Application.Registrar; + set => configuration.Application.Registrar = value; + } + + public string IntermediateOutputPath { + get => configuration.IntermediateOutputPath; + } + + public Optimizations Optimizations => configuration.Application.Optimizations; + + public List Assemblies { get; set; } = new List (); + + public IList<(string Path, AssemblyDefinition Assembly, string? OriginatingAssembly)> AddedAssemblies => configuration.AddedAssemblies; + + LinkerConfiguration.Configurator GetConfigurator (string? reproPath = null, Func? assemblyPreparerInfoFactory = null) + { + var dict = new LinkerConfiguration.Configurator () { + { "AssemblyPreparer", ( + new LinkerConfiguration.LoadValue ((key, value) => { + var split = value.Split ('|'); + var input = split[0]; + var output = split[1]; + var isTrimmableString = split[2]; + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = split[3]; + var apinfo = assemblyPreparerInfoFactory is not null ? assemblyPreparerInfoFactory (input, output) : new AssemblyPreparerInfo (input, output, isTrimmable, trimMode); + Assemblies.Add (apinfo); + }), + new LinkerConfiguration.SaveValue ((key, storage) => SaveAssemblies (key, storage, reproPath, Assemblies)) + )}, + }; + return dict; + } + + static void SaveAssemblies (string key, List storage, string? reproPath, IList assemblies) + { + foreach (var assembly in assemblies) { + var input = assembly.InputPath; + var output = assembly.OutputPath; + if (!string.IsNullOrEmpty (reproPath)) { + output = Path.Combine (reproPath, Path.GetFileName (output)); + File.Copy (input, output); + } + storage.Add ($"{key}={input}|{output}|{(assembly.IsTrimmable.HasValue ? (assembly.IsTrimmable.Value ? "true" : "false") : "")}|{assembly.TrimMode}"); + } + } + + public AssemblyPreparer (IToolLog log, AssemblyPreparerInfo [] assemblies, string linker_file) + { + var lines = File.ReadAllLines (linker_file).ToList (); + SaveAssemblies ("AssemblyPreparer", lines, null, assemblies); + configuration = new LinkerConfiguration (log, lines, linker_file, GetConfigurator (null, assemblies.Length == 0 ? null : (input, output) => assemblies.Single (a => a.InputPath == input && a.OutputPath == output))); + } + + public void AddLog (IAssemblyPreparerLog log) + { + if (log is null) + throw new ArgumentNullException (nameof (log)); + this.log.Add (log); + } + + bool SaveToReproPath (List exceptions) + { + if (File.Exists (MakeReproPath) || Directory.Exists (MakeReproPath)) { + exceptions.Add (ErrorHelper.CreateError (99, $"Repro location already exists: {MakeReproPath}")); + return false; + } + Directory.CreateDirectory (MakeReproPath); + var lines = new List (); + configuration.Save (lines, GetConfigurator (MakeReproPath)); + File.WriteAllLines (Path.Combine (MakeReproPath, "arguments.txt"), lines); + log.Log ($"Created repro in {MakeReproPath}"); + + return true; + } + + public static AssemblyPreparer LoadFromReproPath (string reproPath) + { + var file = Path.Combine (reproPath, "arguments.txt"); + if (!File.Exists (file)) + throw new FileNotFoundException ($"Repro arguments file not found: {file}"); + return new AssemblyPreparer (ConsoleLog.Instance, Array.Empty (), file); + } + + public bool Prepare (out List exceptions) + { + exceptions = configuration.Exceptions; + + if (Registrar == RegistrarMode.Default) { + exceptions.Add (ErrorHelper.CreateError (99, "RegistrarMode must be explicitly set.")); + return false; + } + + if (!string.IsNullOrEmpty (MakeReproPath) && !SaveToReproPath (exceptions)) + return false; + + 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). + // CollectAssembliesStep + new CoreTypeMapStep (), + // ProcessExportedFields + new PreserveProtocolsStep (), + new PreserveSmartEnumConversionsStep (), + new PreserveBlockCodeStep (), + new OptimizeGeneratedCodeStep (), + new ApplyPreserveAttributeStep (), + new MarkForStaticRegistrarStep (), + new MarkNSObjectsStep (), + new InlineDlfcnMethodsStep (), + new RegistrarRemovalTrackingStep (), + // PreMarkDispatcher: I don't think we need this one + new ManagedRegistrarStep (), + new TrimmableRegistrarStep (), + new ManagedRegistrarLookupTablesStep (), + }; + + var linkContext = configuration.DerivedLinkContext; + + var parameters = new ReaderParameters { + AssemblyResolver = configuration.AssemblyResolver, + MetadataResolver = configuration.MetadataResolver, + ReadSymbols = true, + SymbolReaderProvider = new DefaultSymbolReaderProvider (throwIfNoSymbol: false), + }; + + foreach (var assembly in Assemblies) { + var assemblyDefinition = AssemblyDefinition.ReadAssembly (assembly.InputPath, parameters); + linkContext.Assemblies.Add (assemblyDefinition); + assembly.Assembly = assemblyDefinition; + configuration.Context.Annotations.SetAction (assemblyDefinition, ComputeAssemblyAction (assemblyDefinition, assembly)); + configuration.AssemblyResolver.ResolverCache.Add (assemblyDefinition.Name.Name, assemblyDefinition); + } + + configuration.Context.Annotations.CollectOverrides (linkContext.Assemblies, linkContext); + + foreach (var step in steps) { + step.Process (linkContext); + } + + // save assemblies + + foreach (var assembly in Assemblies) { + var assemblyDefinition = assembly.Assembly!; + + var action = configuration.Context.Annotations.GetAction (assemblyDefinition); + switch (action) { + case AssemblyAction.Copy: + case AssemblyAction.CopyUsed: + assembly.OutputPath = assembly.InputPath; + continue; + case AssemblyAction.Link: + case AssemblyAction.Save: + log.Log ($"Saving {assembly.InputPath} to {assembly.OutputPath}"); + break; + default: + exceptions.Add (ErrorHelper.CreateError (99, $"Unknown link action: {action} for assembly {assemblyDefinition.Name}")); + return false; + } + + Directory.CreateDirectory (Path.GetDirectoryName (assembly.OutputPath)!); + var writerParameters = new WriterParameters (); + if (assemblyDefinition.MainModule.HasSymbols) { + var provider = new CustomSymbolWriterProvider (); + try { + using (var tmp = provider.GetSymbolWriter (assemblyDefinition.MainModule, Path.ChangeExtension (assembly.OutputPath, ".pdb"))) { } + File.Delete (Path.ChangeExtension (assembly.OutputPath, ".pdb")); + writerParameters.WriteSymbols = true; + writerParameters.SymbolWriterProvider = provider; + } catch (Exception e) { + log.Log ($"Failed to create symbol writer for {assembly.OutputPath}, not writing symbols: {e.Message}"); + } + } + + RemoveCrossGen (assemblyDefinition); + + try { + assemblyDefinition.Write (assembly.OutputPath, writerParameters); + ModuleAttributes m = assemblyDefinition.MainModule.Attributes; + } catch (Exception e) { + exceptions.Add (ErrorHelper.CreateError (99, e, $"Failed to write {assembly.OutputPath}: {e.Message}")); + log.Log ($"Failed to write {assembly.OutputPath}: {e}"); + return false; + } + } + + return exceptions.Count == 0; + } + + void RemoveCrossGen (AssemblyDefinition assemblyDefinition) + { + // Drop crossgened code from the assembly + // Ref: https://github.com/dotnet/runtime/blob/b86458593223f866effa63122b05bec37f83015e/src/tools/illink/src/linker/Linker.Steps/OutputStep.cs#L95-L105 + foreach (var module in assemblyDefinition.Modules) { + var moduleAttributes = module.Attributes; + var isCrossGened = (moduleAttributes & ModuleAttributes.ILOnly) == 0 && (moduleAttributes & ModuleAttributes.ILLibrary) == ModuleAttributes.ILLibrary; + if (isCrossGened) { + moduleAttributes |= ModuleAttributes.ILOnly; + moduleAttributes &= ~ModuleAttributes.ILLibrary; + module.Attributes = moduleAttributes; + module.Architecture = TargetArchitecture.I386; + module.Characteristics |= ModuleCharacteristics.NoSEH; + } + } + } + + // Figure out if an assembly is trimmed or not. + // This must be identical to how it's done for ILLink/ILC. + AssemblyAction ComputeAssemblyAction (AssemblyDefinition assembly, AssemblyPreparerInfo info) + { + // Unless 'PublishTrimmed=true', nothing is trimmed, because we won't run the trimmer. + if (!configuration.PublishTrimmed) + return AssemblyAction.Copy; + + // Then if 'TrimMode' is set on the assembly, then that takes precedence + switch (info.TrimMode?.ToLowerInvariant () ?? "") { + case "link": + return AssemblyAction.Link; + case "copy": + return AssemblyAction.Copy; + case "": + break; + default: + throw new ArgumentException ($"Unknown trim mode: {info.TrimMode} for assembly {assembly.Name}"); + } + + // Then if 'IsTrimmable' is set on the assembly, that takes precedence over the default for the platform. + if (info.IsTrimmable == false) + return AssemblyAction.CopyUsed; + else if (info.IsTrimmable == true) + return AssemblyAction.Link; + + // Check the global 'TrimMode' property, if it's not 'link', 'partial' or 'full', then we're not trimming anything + var globalTrimMode = configuration.TrimMode.ToLowerInvariant (); + switch (globalTrimMode) { + case "copy": + case "": + return AssemblyAction.Copy; + case "partial": + case "full": + case "link": + break; + default: + throw new ArgumentException ($"Unknown global trim mode: {configuration.TrimMode}"); + } + + // Check the [AssemblyMetadata] attribute + var isTrimmableAttribute = assembly.CustomAttributes + .Where (v => v.AttributeType.FullName == "System.Reflection.AssemblyMetadataAttribute") + .Where (v => v.HasConstructorArguments && v.ConstructorArguments.Count == 2 && v.ConstructorArguments [0].Type.Is ("System", "String") && v.ConstructorArguments [1].Type.Is ("System", "String")) + .Where (v => (v.ConstructorArguments [0].Value as string) == "IsTrimmable" && string.Equals (v.ConstructorArguments [1].Value as string, "true", StringComparison.OrdinalIgnoreCase)) + .SingleOrDefault (); + + if (isTrimmableAttribute is null) { + // If the attribute is not present, then we trim if the global 'TrimMode' is 'full' + return globalTrimMode switch { + "link" => AssemblyAction.Copy, + "partial" => AssemblyAction.Copy, + "full" => AssemblyAction.Link, + _ => throw new ArgumentException ($"Unknown global trim mode: {configuration.TrimMode}"), + }; + } + + // if the attribute is present, then we trim if the global 'TrimMode' is 'partial', 'full' or 'link', which are the only values it should have at this point + switch (globalTrimMode) { + case "partial": + case "full": + case "link": + break; + default: + // we shouldn't get here for any other trim mode value + throw new ArgumentException ($"Unexpected global trim mode: {configuration.TrimMode}"); + } + + return AssemblyAction.Link; + } + + public void Dispose () + { + foreach (var assembly in Assemblies) + assembly.Assembly?.Dispose (); + configuration.AssemblyResolver.ResolverCache.Clear (); + configuration.DerivedLinkContext.Assemblies.Clear (); + } +} + +public class AssemblyPreparerInfo { + internal AssemblyDefinition? Assembly { get; set; } + + public string InputPath { get; private set; } + public bool? IsTrimmable { get; set; } + public string TrimMode { get; set; } + public string OutputPath { get; set; } + + public AssemblyPreparerInfo (string inputPath, string outputPath, bool? isTrimmable, string trimMode) + { + InputPath = inputPath; + OutputPath = outputPath; + IsTrimmable = isTrimmable; + TrimMode = trimMode; + } +} diff --git a/tools/assembly-preparer/DynamicallyAccessedMemberTypes.cs b/tools/assembly-preparer/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 000000000000..4a42e76ab54c --- /dev/null +++ b/tools/assembly-preparer/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// This file is needed because we compile the current project under netstandard2.0, and this type doesn't exist there. + +#if !NET + +// From https://raw.githubusercontent.com/dotnet/dotnet/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMemberTypes.cs + +global using DynamicallyAccessedMemberTypes = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes2; + +namespace System.Diagnostics.CodeAnalysis { + using System.ComponentModel; + + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + enum DynamicallyAccessedMemberTypes2 { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all non-public constructors, including those inherited from base classes. + /// + NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000, + + /// + /// Specifies all non-public methods, including those inherited from base classes. + /// + NonPublicMethodsWithInherited = NonPublicMethods | 0x8000, + + /// + /// Specifies all non-public fields, including those inherited from base classes. + /// + NonPublicFieldsWithInherited = NonPublicFields | 0x10000, + + /// + /// Specifies all non-public nested types, including those inherited from base classes. + /// + NonPublicNestedTypesWithInherited = NonPublicNestedTypes | 0x20000, + + /// + /// Specifies all non-public properties, including those inherited from base classes. + /// + NonPublicPropertiesWithInherited = NonPublicProperties | 0x40000, + + /// + /// Specifies all non-public events, including those inherited from base classes. + /// + NonPublicEventsWithInherited = NonPublicEvents | 0x80000, + + /// + /// Specifies all public constructors, including those inherited from base classes. + /// + PublicConstructorsWithInherited = PublicConstructors | 0x100000, + + /// + /// Specifies all public nested types, including those inherited from base classes. + /// + PublicNestedTypesWithInherited = PublicNestedTypes | 0x200000, + + /// + /// Specifies all constructors, including those inherited from base classes. + /// + AllConstructors = PublicConstructorsWithInherited | NonPublicConstructorsWithInherited, + + /// + /// Specifies all methods, including those inherited from base classes. + /// + AllMethods = PublicMethods | NonPublicMethodsWithInherited, + + /// + /// Specifies all fields, including those inherited from base classes. + /// + AllFields = PublicFields | NonPublicFieldsWithInherited, + + /// + /// Specifies all nested types, including those inherited from base classes. + /// + AllNestedTypes = PublicNestedTypesWithInherited | NonPublicNestedTypesWithInherited, + + /// + /// Specifies all properties, including those inherited from base classes. + /// + AllProperties = PublicProperties | NonPublicPropertiesWithInherited, + + /// + /// Specifies all events, including those inherited from base classes. + /// + AllEvents = PublicEvents | NonPublicEventsWithInherited, + + /// + /// Specifies all members. + /// + [EditorBrowsable (EditorBrowsableState.Never)] + All = ~None + } +} + +#endif // !NET diff --git a/tools/assembly-preparer/GlobalUsings.cs b/tools/assembly-preparer/GlobalUsings.cs new file mode 100644 index 000000000000..ba0a28435170 --- /dev/null +++ b/tools/assembly-preparer/GlobalUsings.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +global using System; +global using System.Collections.Generic; +global using System.Diagnostics.CodeAnalysis; +global using System.Linq; +global using System.Runtime.InteropServices; + +global using Foundation; +global using ObjCRuntime; + +global using Xamarin.Linker; + +namespace Xamarin.Tuner { } +namespace Mono.Linker.Steps { } diff --git a/tools/assembly-preparer/IAssemblyPreparerLog.cs b/tools/assembly-preparer/IAssemblyPreparerLog.cs new file mode 100644 index 000000000000..f41de7ca475e --- /dev/null +++ b/tools/assembly-preparer/IAssemblyPreparerLog.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Xamarin.Bundler; + +namespace Xamarin.Build; + +public interface IAssemblyPreparerLog { + void Log (string message); +} + +class AggregateLog : IAssemblyPreparerLog { + List logs = new (); + + public void Add (IAssemblyPreparerLog log) + { + logs.Add (log); + } + + public void Log (string message) + { + foreach (var log in logs) + log.Log (message); + } +} diff --git a/tools/assembly-preparer/Makefile b/tools/assembly-preparer/Makefile new file mode 100644 index 000000000000..587a1d6116f5 --- /dev/null +++ b/tools/assembly-preparer/Makefile @@ -0,0 +1,25 @@ +TOP=../.. +include $(TOP)/Make.config +include $(TOP)/mk/rules.mk +include ../common/Make.common + +# assembly-preparer.csproj.inc contains the $(assembly_preparer_dependencies) variable used to determine if assembly-preparer needs to be rebuilt or not. +assembly-preparer.csproj.inc: export BUILD_VERBOSITY=$(MSBUILD_VERBOSITY) +-include assembly-preparer.csproj.inc + +ASSEMBLY_PREPARER_NETCORE=bin/Debug/$(DOTNET_TFM)/assembly-preparer.dll +ASSEMBLY_PREPARER_NETSTD=bin/Debug/netstandard2.0/assembly-preparer.dll +ASSEMBLIES=$(ASSEMBLY_PREPARER_NETCORE) $(ASSEMBLY_PREPARER_NETSTD) + +.build-stamp: $(assembly_preparer_dependencies) + $(Q_BUILD) $(DOTNET) build /bl:$@.binlog *.csproj $(DOTNET_BUILD_VERBOSITY) + $(Q) touch $(ASSEMBLIES) + +$(ASSEMBLIES): .build-stamp + +all-local:: .build-stamp + +run-tests: + $(Q_BUILD) $(DOTNET) test $(TOP)/tests/assembly-preparer/assembly-preparer-tests.csproj --logger "console;verbosity=detailed" $(DOTNET_BUILD_VERBOSITY) -bl:$@.binlog + +generated-files: $(abspath ../common/SdkVersions.cs) $(abspath ../common/ProductConstants.cs) diff --git a/tools/assembly-preparer/NetStandardExtensions.cs b/tools/assembly-preparer/NetStandardExtensions.cs new file mode 100644 index 000000000000..5f81fd7f1b93 --- /dev/null +++ b/tools/assembly-preparer/NetStandardExtensions.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// This file is needed because we compile the current project under netstandard2.0, and the types here don't exist in netstandard2.0 + +#if !NET + +public static class QueueExtensions { + public static bool TryDequeue (this Queue queue, [MaybeNullWhen (false)] out T item) + { + if (queue.Count == 0) { + item = default; + return false; + } + item = queue.Dequeue (); + return true; + } + + public static bool TryAdd (this Dictionary dictionary, T key, V value) + { + if (dictionary.ContainsKey (key)) + return false; + dictionary.Add (key, value); + return true; + } +} + +public static class DictionaryExtensions { + public static bool Remove (this Dictionary dictionary, T key, [MaybeNullWhen (false)] out V value) + { + if (dictionary.TryGetValue (key, out value)) { + dictionary.Remove (key); + return true; + } + return false; + } +} + +public static class EnumerableExtensions { + public static IEnumerable SkipLast (this IEnumerable source, int count) + { + // very naive implementation, but it's only for netstandard2.0, which will go away soon, so no need to optimize it + var rv = source.ToList (); + if (rv.Count <= count) + return []; + rv.RemoveRange (rv.Count - count, count); + return rv; + } +} + +// From: https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RequiredMemberAttribute.cs +namespace System.Runtime.CompilerServices { + using System.ComponentModel; + + /// Specifies that a type has required members or that a member is required. + [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + [EditorBrowsable (EditorBrowsableState.Never)] + internal sealed class RequiredMemberAttribute : Attribute { } +} + +// From: https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerFeatureRequiredAttribute.cs +namespace System.Runtime.CompilerServices { + using System.ComponentModel; + + /// + /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. + /// + [AttributeUsage (AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute { + public CompilerFeatureRequiredAttribute (string featureName) + { + FeatureName = featureName; + } + + /// + /// The name of the compiler feature. + /// + public string FeatureName { get; } + + /// + /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand . + /// + public bool IsOptional { get; init; } + + /// + /// The used for the ref structs C# feature. + /// + public const string RefStructs = nameof (RefStructs); + + /// + /// The used for the required members C# feature. + /// + public const string RequiredMembers = nameof (RequiredMembers); + } +} + +// From: https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IsExternalInit.cs +namespace System.Runtime.CompilerServices { + using System.ComponentModel; + + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable (EditorBrowsableState.Never)] + internal static class IsExternalInit { + } +} +#endif diff --git a/tools/assembly-preparer/README.md b/tools/assembly-preparer/README.md new file mode 100644 index 000000000000..696974bf073f --- /dev/null +++ b/tools/assembly-preparer/README.md @@ -0,0 +1,35 @@ +# Assembly preparer + +This is a library that will modify assemblies when a project is built for a few reasons: + +* Collect required information for a successful build +* Transform some code patterns so that they can be properly recognized and handled correctly by trimmers. +* Optimize some code patterns we can easily recognize +* Precompute some things at build time to be able to make apps smaller and run faster. + +Currently it can: + +* PreserveCodeBlockHandler: in some cases user assemblies might contain code + created by the generator that's not trimmer safe; this handler will inject + code to ensure that trimmers don't trim way some things that shouldn't be + trimmed away. + +## Design principles + +* Easy to test (there's a unit test project, VSCode can run & debug its tests) +* Can be called from an MSBuild task (which means it currently needs to target `netstandard2.0`). +* Good error handling/reporting. +* We have two main scenarios: + * Debug loop, where we shouldn't do more than absolutely necessary to make + debug builds as fast as possible. In particular, we'll only do whatever + is necessary for a correct build, and if possible, no assembly + modification, only information gathering. + * Release builds, where we want to optimize as much as possible. +* To ease integration with existing custom linker steps, it's provides a + very simplified API of ILLink's custom linker step API - much of it is + stubbed out until it's needed. +* Until fully complete and both correctness and performance have been + validated, it should be possible to use either custom linker steps or the + assembly preparer, and as such any code that's used by both will have to + keep working in both modes. + diff --git a/tools/assembly-preparer/Scaffolding/AnnotationStore.cs b/tools/assembly-preparer/Scaffolding/AnnotationStore.cs new file mode 100644 index 000000000000..e1fc186bbf11 --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/AnnotationStore.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using Mono.Cecil; + +namespace Mono.Linker; + +public class AnnotationStore { + Dictionary assemblyActions = new Dictionary (); + Dictionary> overrides = new Dictionary> (); + public AssemblyAction GetAction (AssemblyDefinition assembly) + { + if (assemblyActions.TryGetValue (assembly, out var action)) + return action; + throw new InvalidOperationException ($"Assembly {assembly.Name} not found in the annotation store"); + } + + public void SetAction (AssemblyDefinition assembly, AssemblyAction action) + { + assemblyActions [assembly] = action; + } + + public IEnumerable? GetOverrides (MethodDefinition method) + { + if (overrides.TryGetValue (method, out var list)) + return list; + return null; + } + + // Scan all types in the given assemblies and build the overrides map. + // For each method that overrides a base virtual method (or explicitly implements an interface method), + // record an OverrideInformation entry keyed by the base method. + public void CollectOverrides (IEnumerable assemblies, LinkContext context) + { + foreach (var assembly in assemblies) { + foreach (var module in assembly.Modules) { + foreach (var type in module.GetTypes ()) { + CollectOverridesForType (type, context); + } + } + } + } + + void CollectOverridesForType (TypeDefinition type, LinkContext context) + { + if (!type.HasMethods) + return; + + foreach (var method in type.Methods) { + // Handle explicit overrides (.override directive in IL / method.Overrides in Cecil) + if (method.HasOverrides) { + foreach (var overriddenRef in method.Overrides) { + var baseMethod = TryResolve (overriddenRef); + if (baseMethod is not null) + AddOverride (baseMethod, method); + } + } + + // Handle implicit virtual method overrides via the type hierarchy + if (method.IsVirtual && !method.IsNewSlot) { + var baseMethod = GetBaseMethodInTypeHierarchy (type, method, context); + if (baseMethod is not null) + AddOverride (baseMethod, method); + } + } + } + + void AddOverride (MethodDefinition baseMethod, MethodDefinition overridingMethod) + { + if (!overrides.TryGetValue (baseMethod, out var list)) { + list = new List (); + overrides [baseMethod] = list; + } + list.Add (new OverrideInformation (overridingMethod)); + } + + static MethodDefinition? GetBaseMethodInTypeHierarchy (TypeDefinition type, MethodDefinition method, LinkContext context) + { + var baseTypeRef = type.BaseType; + while (baseTypeRef is not null) { + TypeDefinition? baseType; + try { + baseType = context.Resolve (baseTypeRef); + } catch { + break; + } + + if (baseType.HasMethods) { + foreach (var candidate in baseType.Methods) { + if (candidate.IsVirtual && MethodMatch (candidate, method)) + return candidate; + } + } + + baseTypeRef = baseType.BaseType; + } + return null; + } + + static bool MethodMatch (MethodDefinition candidate, MethodDefinition method) + { + if (candidate.Name != method.Name) + return false; + if (candidate.HasGenericParameters != method.HasGenericParameters) + return false; + if (candidate.GenericParameters.Count != method.GenericParameters.Count) + return false; + if (candidate.ReturnType.FullName != method.ReturnType.FullName) + return false; + if (candidate.HasParameters != method.HasParameters) + return false; + if (!candidate.HasParameters) + return true; + if (candidate.Parameters.Count != method.Parameters.Count) + return false; + for (int i = 0; i < candidate.Parameters.Count; i++) { + if (candidate.Parameters [i].ParameterType.FullName != method.Parameters [i].ParameterType.FullName) + return false; + } + return true; + } + + static MethodDefinition? TryResolve (MethodReference methodRef) + { + try { + return methodRef.Resolve (); + } catch { + // FIXME: figure out a way that doesn't require throwing and catching exceptions. + return null; + } + } + + Dictionary> custom_annotations = new (); + + public void SetCustomAnnotation (object key, IMetadataTokenProvider item, object value) + { + if (!custom_annotations.TryGetValue (key, out var annotations)) + custom_annotations [key] = annotations = new Dictionary (); + annotations [item] = value; + } + + public object? GetCustomAnnotation (object key, IMetadataTokenProvider item) + { + if (custom_annotations.TryGetValue (key, out var annotations) && annotations.TryGetValue (item, out var value)) + return value; + + return null; + } + + // This should not be called; once closer to done, just remove this method. + public void Mark (object obj) + { + // Console.WriteLine ($"Annotations.Mark () called from {new StackTrace (1).GetFrame (0)?.GetMethod ()}"); + } +} + +[DebuggerDisplay ("{Override}")] +public class OverrideInformation { + + public MethodDefinition Override { get; } + + internal OverrideInformation (MethodDefinition @override) + { + Override = @override; + } +} diff --git a/tools/assembly-preparer/Scaffolding/AssemblyAction.cs b/tools/assembly-preparer/Scaffolding/AssemblyAction.cs new file mode 100644 index 000000000000..8a71e3197566 --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/AssemblyAction.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Mono.Linker; + +public enum AssemblyAction { + Copy, + Link, + Save, + Skip, + CopyUsed, + AddBypassNGen, + AddBypassNGenUsed, + Delete, +} diff --git a/tools/assembly-preparer/Scaffolding/BaseStep.cs b/tools/assembly-preparer/Scaffolding/BaseStep.cs new file mode 100644 index 000000000000..f0d31bf7e00b --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/BaseStep.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// +// BaseStep.cs +// +// Author: +// Jb Evain (jbevain@novell.com) +// +// (C) 2007 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Diagnostics; +using Mono.Cecil; + +using Xamarin.Tuner; + +namespace Mono.Linker.Steps; + +public abstract class BaseStep : IStep { + DerivedLinkContext? context; + + public DerivedLinkContext Context { + get { + Debug.Assert (context is not null); + return context!; + } + } + + public AnnotationStore Annotations { + get { return Context.Annotations; } + } + + public void Process (DerivedLinkContext context) + { + this.context = context; + + if (!ConditionToProcess ()) + return; + + Process (); + + foreach (var assembly in context.GetAssemblies ()) { + ProcessAssembly (assembly); + } + + EndProcess (); + } + + protected virtual bool ConditionToProcess () + { + return true; + } + + protected virtual void Process () + { + } + + protected virtual void EndProcess () + { + } + + protected virtual void ProcessAssembly (AssemblyDefinition assembly) + { + } +} diff --git a/tools/assembly-preparer/Scaffolding/IStep.cs b/tools/assembly-preparer/Scaffolding/IStep.cs new file mode 100644 index 000000000000..0a01c17d215b --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/IStep.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// +// IStep.cs +// +// Author: +// Jb Evain (jbevain@gmail.com) +// +// (C) 2006 Jb Evain +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using Xamarin.Tuner; + +namespace Mono.Linker.Steps; + +public interface IStep { + void Process (DerivedLinkContext context); +} diff --git a/tools/assembly-preparer/Scaffolding/LinkContext.cs b/tools/assembly-preparer/Scaffolding/LinkContext.cs new file mode 100644 index 000000000000..5921dbe364ca --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/LinkContext.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Mono.Cecil; +using Xamarin.Bundler; + +namespace Mono.Linker; + +public class LinkContext { + Dictionary custom_data = new Dictionary (); + + AnnotationStore annotations = new AnnotationStore (); + public AnnotationStore Annotations { get => annotations; } + + public List Assemblies = new List (); + + public AssemblyDefinition [] GetAssemblies () { return Assemblies.ToArray (); } + + public LinkerConfiguration Configuration { get; private set; } + + public LinkerConfiguration LinkerConfiguration { get => Configuration; } + + public LinkContext (LinkerConfiguration configuration) + { + Configuration = configuration; + } + + public TypeDefinition Resolve (TypeReference type) + { + return Configuration.MetadataResolver.Resolve (type); + } + + public AssemblyDefinition? GetLoadedAssembly (string name) + { + return Assemblies.SingleOrDefault (v => v.Name.Name == name); + } + + public void SetCustomData (string key, string value) + { + custom_data [key] = value; + } + + public bool TryGetCustomData (string key, [NotNullWhen (true)] out string? value) + { + return custom_data.TryGetValue (key, out value); + } +} diff --git a/tools/assembly-preparer/Scaffolding/Target.cs b/tools/assembly-preparer/Scaffolding/Target.cs new file mode 100644 index 000000000000..5d4e99fa509b --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/Target.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Xamarin.Utils; + +namespace Xamarin.Bundler; + +public class Target { + Target () { } + public Application App { get => throw new NotImplementedException (); } +} diff --git a/tools/assembly-preparer/System_Diagnostics_UnreachableException.cs b/tools/assembly-preparer/System_Diagnostics_UnreachableException.cs new file mode 100644 index 000000000000..1f48b65b411c --- /dev/null +++ b/tools/assembly-preparer/System_Diagnostics_UnreachableException.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Copied from https://raw.githubusercontent.com/dotnet/dotnet/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Diagnostics/UnreachableException.cs + +#if !NET + +namespace System.Diagnostics { + /// + /// Exception thrown when the program executes an instruction that was thought to be unreachable. + /// + public sealed class UnreachableException : Exception { + /// + /// Initializes a new instance of the class with the default error message. + /// + public UnreachableException () + : base ("SR.Arg_UnreachableException") + { + } + + /// + /// Initializes a new instance of the + /// class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public UnreachableException (string? message) + : base (message ?? "SR.Arg_UnreachableException") + { + } + + /// + /// Initializes a new instance of the + /// class with a specified error message and a reference to the inner exception that is the cause of + /// this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public UnreachableException (string? message, Exception? innerException) + : base (message ?? "SR.Arg_UnreachableException", innerException) + { + } + } +} + +#endif // !NET diff --git a/tools/assembly-preparer/System_Index.cs b/tools/assembly-preparer/System_Index.cs new file mode 100644 index 000000000000..9dde742e6c73 --- /dev/null +++ b/tools/assembly-preparer/System_Index.cs @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Copied from https://raw.githubusercontent.com/dotnet/dotnet/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Index.cs +// to make newer compiler features compile in netstandard2.0 + +#if !NET + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System { + /// Represent a type can be used to index a collection either from the start or the end. + /// + /// Index is used by the C# compiler to support the new index syntax + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; + /// int lastElement = someArray[^1]; // lastElement = 5 + /// + /// +#if SYSTEM_PRIVATE_CORELIB || MICROSOFT_BCL_MEMORY + public +#else + internal +#endif + readonly struct Index : IEquatable { + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public Index (int value, bool fromEnd = false) + { + if (value < 0) { + ThrowValueArgumentOutOfRange_NeedNonNegNumException (); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index (int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index (0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index (~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public static Index FromStart (int value) + { + if (value < 0) { + ThrowValueArgumentOutOfRange_NeedNonNegNumException (); + } + + return new Index (value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public static Index FromEnd (int value) + { + if (value < 0) { + ThrowValueArgumentOutOfRange_NeedNonNegNumException (); + } + + return new Index (~value); + } + + /// Returns the index value. + public int Value { + get { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public int GetOffset (int length) + { + int offset = _value; + if (IsFromEnd) { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals ([NotNullWhen (true)] object? value) => value is Index && _value == ((Index) value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals (Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode () => _value; + + /// Converts integer number to an Index. + public static implicit operator Index (int value) => FromStart (value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString () + { + if (IsFromEnd) + return ToStringFromEnd (); + + return ((uint) Value).ToString (); + } + + private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException () + { +#if SYSTEM_PRIVATE_CORELIB + throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_NeedNonNegNum); +#else + throw new ArgumentOutOfRangeException ("value", "value must be non-negative"); +#endif + } + + private string ToStringFromEnd () + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char [11]; // 1 for ^ and 10 for longest possible uint value + bool formatted = ((uint) Value).TryFormat (span.Slice (1), out int charsWritten); + Debug.Assert (formatted); + span [0] = '^'; + return new string (span.Slice (0, charsWritten + 1)); +#else + return '^' + Value.ToString(); +#endif + } + } +} + +#endif // !NET diff --git a/tools/assembly-preparer/System_Range.cs b/tools/assembly-preparer/System_Range.cs new file mode 100644 index 000000000000..453dd1a4ccc7 --- /dev/null +++ b/tools/assembly-preparer/System_Range.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Copied from https://raw.githubusercontent.com/dotnet/dotnet/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Range.cs +// to make newer compiler features compile in netstandard2.0 + +#if !NET + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#if NETSTANDARD2_0 || NETFRAMEWORK +using System.Numerics.Hashing; +#endif + +namespace System { + /// Represent a range has start and end indexes. + /// + /// Range is used by the C# compiler to support the range syntax. + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; + /// int[] subArray1 = someArray[0..2]; // { 1, 2 } + /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } + /// + /// +#if SYSTEM_PRIVATE_CORELIB || MICROSOFT_BCL_MEMORY + public +#else + internal +#endif + readonly struct Range : IEquatable { + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range (Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals ([NotNullWhen (true)] object? value) => + value is Range r && + r.Start.Equals (Start) && + r.End.Equals (End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals (Range other) => other.Start.Equals (Start) && other.End.Equals (End); + + /// Returns the hash code for this instance. + public override int GetHashCode () + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + return HashCode.Combine (Start.GetHashCode (), End.GetHashCode ()); +#elif !NET + return Start.GetHashCode () ^ End.GetHashCode (); +#else + return HashHelpers.Combine(Start.GetHashCode(), End.GetHashCode()); +#endif + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString () + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char [2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint + int pos = 0; + + if (Start.IsFromEnd) { + span [0] = '^'; + pos = 1; + } + bool formatted = ((uint) Start.Value).TryFormat (span.Slice (pos), out int charsWritten); + Debug.Assert (formatted); + pos += charsWritten; + + span [pos++] = '.'; + span [pos++] = '.'; + + if (End.IsFromEnd) { + span [pos++] = '^'; + } + formatted = ((uint) End.Value).TryFormat (span.Slice (pos), out charsWritten); + Debug.Assert (formatted); + pos += charsWritten; + + return new string (span.Slice (0, pos)); +#else + return Start.ToString() + ".." + End.ToString(); +#endif + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt (Index start) => new Range (start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt (Index end) => new Range (Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new Range (Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength (int length) + { + int start = Start.GetOffset (length); + int end = End.GetOffset (length); + + if ((uint) end > (uint) length || (uint) start > (uint) end) { + ThrowArgumentOutOfRangeException (); + } + + return (start, end - start); + } + + private static void ThrowArgumentOutOfRangeException () + { + throw new ArgumentOutOfRangeException ("length"); + } + } +} + +#endif // !NET diff --git a/tools/assembly-preparer/assembly-preparer.csproj b/tools/assembly-preparer/assembly-preparer.csproj new file mode 100644 index 000000000000..89a35b82fccf --- /dev/null +++ b/tools/assembly-preparer/assembly-preparer.csproj @@ -0,0 +1,341 @@ + + + + assembly-preparer + net$(BundledNETCoreAppTargetFrameworkVersion);netstandard2.0 + net$(BundledNETCoreAppTargetFrameworkVersion) + $(DefineConstants);BUNDLER;ASSEMBLY_PREPARER + Library + true + latest + enable + + + + + + + + + + + + + + external/tools/common/ApplePlatform.cs + + + external/tools/common/Application.cs + + + external/tools/common/Assembly.cs + + + external/tools/common/AssemblyBuildTarget.cs + + + external/tools/common/cache.cs + + + external/tools/common/CoreResolver.cs + + + external/tools/common/DerivedLinkContext.cs + + + external/tools/common/DlsymOptions.cs + + + external/tools/common/Driver.cs + + + external/tools/common/Driver.execution.cs + + + external/tools/common/error.cs + + + external/tools/common/ErrorHelper.tools.cs + + + external/tools/common/Execution.cs + + + external/tools/common/FileCopier.cs + + + external/tools/common/FileUtils.cs + + + external/tools/common/Frameworks.cs + + + external/tools/common/MachO.cs + + + external/tools/common/NormalizedStringComparer.cs + + + external/tools/common/NullableAttributes.cs + + + external/tools/common/CSToObjCMap.cs + + + external/tools/common/ObjCNameIndex.cs + + + external/tools/common/Optimizations.cs + + + external/tools/common/OSPlatformAttributeExtensions.cs + + + external/tools/common/PInvokeWrapperGenerator.cs + + + external/tools/common/RegistrarMode.cs + + + external/tools/common/PathUtils.cs + + + external/tools/common/PListExtensions.cs + + + external/tools/common/ProductConstants.cs + + + external/tools/common/StaticRegistrar.cs + + + external/tools/common/StringUtils.cs + + + external/tools/common/Symbols.cs + + + external/tools/common/Target.cs + + + external/tools/common/TargetFramework.cs + + + external/tools/common/IToolLog.cs + + + external/tools/common/XamarinRuntime.cs + + + external/tools/dotnet-linker/AppBundleRewriter.cs + + + external/tools/dotnet-linker/ApplyPreserveAttributeBase.cs + + + external/tools/dotnet-linker/ApplyPreserveAttributeStep.cs + + + external/tools/dotnet-linker/Compat.cs + + + external/tools/dotnet-linker/DotNetResolver.cs + + + external/tools/dotnet-linker/Extensions.cs + + + external/tools/dotnet-linker/LinkerConfiguration.cs + + + external/tools/dotnet-linker/MarkForStaticRegistrarStep.cs + + + external/tools/dotnet-linker/MarkNSObjectsStep.cs + + + external/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs + + + external/tools/dotnet-linker/PreserveProtocolsStep.cs + + + external/tools/dotnet-linker/PreserveSmartEnumConversionsStep.cs + + + external/tools/dotnet-linker/Steps/AssemblyModifierStep.cs + + + external/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs + + + external/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs + + + external/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs + + + external/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs + + + external/tools/dotnet-linker/Steps/PreserveBlockCodeStep.cs + + + external/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs + + + external/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs + + + external/tools/linker/CoreTypeMapStep.cs + + + external/tools/linker/CustomSymbolWriter.cs + + + external/tools/linker/MarkNSObjects.cs + + + external/tools/linker/MobileExtensions.cs + + + external/tools/linker/MonoTouch.Tuner/Extensions.cs + + + external/tools/linker/ObjCExtensions.cs + + + external/tools/linker/OptimizeGeneratedCode.cs + + + external/tools/linker/RegistrarRemovalTrackingStep.cs + + + external/src/Foundation/ExportAttribute.cs + + + external/src/Foundation/ConnectAttribute.cs + + + external/src/ObjCRuntime/ArgumentSemantic.cs + + + external/src/ObjCRuntime/BindingImplAttribute.cs + + + external/src/ObjCRuntime/Constants.cs + + + external/src/ObjCRuntime/ErrorHelper.cs + + + external/src/ObjCRuntime/ExceptionMode.cs + + + external/src/ObjCRuntime/LinkWithAttribute.cs + + + external/src/ObjCRuntime/NativeNameAttribute.cs + + + external/src/ObjCRuntime/Registrar.core.cs + + + external/src/ObjCRuntime/Registrar.cs + + + external/builds/mono-ios-sdk-destdir/ios-sources/external/linker/src/tuner/Mono.Tuner/Extensions.cs + + + external/builds/mono-ios-sdk-destdir/ios-sources/external/linker/src/linker/Linker/MethodDefinitionExtensions.cs + + + mono-archive/Linker/Linker/TypeReferenceExtensions.cs + + + external/builds/mono-ios-sdk-destdir/ios-sources/external/linker/src/tuner/Mono.Tuner/CecilRocks.cs + + + external/tools/dotnet-linker/CecilExtensions.cs + + + external/tools/dotnet-linker/DocumentionComments.cs + + + external/tools/common/SdkVersions.cs + + + + + + external/tools/mtouch/Errors.designer.cs + Errors.resx + + + external/tools/mtouch/Errors.resx + ResXFileCodeGenerator + Errors.designer.cs + Xamarin.Bundler + Xamarin.Bundler.Errors + + + external/tools/mtouch/Errors.cs.resx + Xamarin.Bundler.Errors.cs + + + external/tools/mtouch/Errors.de.resx + Xamarin.Bundler.Errors.de + + + external/tools/mtouch/Errors.es.resx + Xamarin.Bundler.Errors.es + + + external/tools/mtouch/Errors.fr.resx + Xamarin.Bundler.Errors.fr + + + external/tools/mtouch/Errors.it.resx + Xamarin.Bundler.Errors.it + + + external/tools/mtouch/Errors.ja.resx + Xamarin.Bundler.Errors.ja + + + external/tools/mtouch/Errors.ko.resx + Xamarin.Bundler.Errors.ko + + + external/tools/mtouch/Errors.pl.resx + Xamarin.Bundler.Errors.pl + + + external/tools/mtouch/Errors.pt-BR.resx + Xamarin.Bundler.Errors.pt-BR + + + external/tools/mtouch/Errors.ru.resx + Xamarin.Bundler.Errors.ru + + + external/tools/mtouch/Errors.tr.resx + Xamarin.Bundler.Errors.tr + + + external/tools/mtouch/Errors.zh-Hans.resx + Xamarin.Bundler.Errors.zh-Hans + + + external/tools/mtouch/Errors.zh-Hant.resx + Xamarin.Bundler.Errors.zh-Hant + + + + + + + diff --git a/tools/assembly-preparer/assembly-preparer.slnx b/tools/assembly-preparer/assembly-preparer.slnx new file mode 100644 index 000000000000..a2411d955a58 --- /dev/null +++ b/tools/assembly-preparer/assembly-preparer.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 05ffef60c8d9..8da7ac74c63a 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -20,16 +20,14 @@ using Registrar; -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER using ClassRedirector; #endif #if LEGACY_TOOLS using PlatformResolver = MonoTouch.Tuner.MonoTouchResolver; -#elif NET -using PlatformResolver = Xamarin.Linker.DotNetResolver; #else -#error Invalid defines +using PlatformResolver = Xamarin.Linker.DotNetResolver; #endif #nullable enable @@ -82,6 +80,13 @@ public partial class Application : IToolLog { public List AotArguments = new List (); public List? AotOtherArguments = null; public bool? AotFloat32 = null; + public bool PrepareAssemblies; // True if '$(PrepareAssemblies)' == 'true' +#if ASSEMBLY_PREPARER + public bool InCustomTrimmerStep = false; +#else + public bool InCustomTrimmerStep = true; +#endif + public bool IsPostProcessingAssemblies => PrepareAssemblies && InCustomTrimmerStep; #if !LEGACY_TOOLS public DlsymOptions DlsymOptions; @@ -273,7 +278,11 @@ public Version GetMacCatalystiOSVersion (Version macOSVersion) return value; } +#if !LEGACY_TOOLS public Application (LinkerConfiguration configuration) +#else + public Application () +#endif { #if !LEGACY_TOOLS this.configuration = configuration; @@ -557,6 +566,7 @@ void InitializeDeploymentTarget () } } +#if !ASSEMBLY_PREPARER public void RunRegistrar () { // The static registrar. @@ -623,6 +633,7 @@ public void RunRegistrar () registrar.Generate (resolver, resolvedAssemblies.Values, Path.ChangeExtension (registrar_m, "h"), registrar_m, out var _); } } +#endif // !ASSEMBLY_PREPARER public Abi Abi { get { return abi; } @@ -700,7 +711,7 @@ public void ParseAbi (string abi) } #if !LEGACY_TOOLS - public void ParseRegistrar (string v) + public void ParseRegistrar (string? v) { if (StringUtils.IsNullOrEmpty (v)) return; @@ -708,7 +719,7 @@ public void ParseRegistrar (string v) var split = v.Split ('='); var name = split [0]; var value = split.Length > 1 ? split [1] : string.Empty; - switch (name) { + switch (name.ToLowerInvariant ()) { case "static": Registrar = RegistrarMode.Static; break; @@ -720,12 +731,15 @@ public void ParseRegistrar (string v) break; case "partial": case "partial-static": + case "partialstatic": Registrar = RegistrarMode.PartialStatic; break; case "managed-static": + case "managedstatic": Registrar = RegistrarMode.ManagedStatic; break; case "trimmable-static": + case "trimmablestatic": Registrar = RegistrarMode.TrimmableStatic; break; default: @@ -1200,27 +1214,41 @@ public void SetDefaultHiddenWarnings () ErrorHelper.ParseWarningLevel (this, ErrorHelper.WarningLevel.Disable, "4190"); // The class '{0}' will not be registered because the {1} framework has been deprecated from the {2} SDK. } + IToolLog GetLog () + { +#if LEGACY_TOOLS + return ConsoleLog.Instance; +#else + return Configuration.Logger ?? ConsoleLog.Instance; +#endif + } + public void Log (string message) { - Console.WriteLine (message); + GetLog ().Log (message); } public void LogError (string message) { - Console.Error.WriteLine (message); + GetLog ().LogError (message); + } + + public void LogError (ProductException exception) + { + GetLog ().LogError (exception); } - public void LogError (Exception exception) + public void LogWarning (ProductException exception) { - ErrorHelper.Show (this, exception); + GetLog ().LogWarning (exception); } public void LogException (Exception exception) { - ErrorHelper.Show (this, exception); + GetLog ().LogException (exception); } - int verbosity = Driver.GetDefaultVerbosity (); + int verbosity = Driver.GetDefaultVerbosity (Driver.NAME); public int Verbosity { get => verbosity; set => verbosity = value; diff --git a/tools/common/Assembly.cs b/tools/common/Assembly.cs index 8e100d6d9609..c03e249d68d3 100644 --- a/tools/common/Assembly.cs +++ b/tools/common/Assembly.cs @@ -8,7 +8,6 @@ using System.Xml; using Mono.Cecil; using Mono.Tuner; -using MonoTouch.Tuner; using ObjCRuntime; using Xamarin; using Xamarin.Utils; @@ -58,6 +57,7 @@ public partial class Assembly { public AssemblyDefinition AssemblyDefinition; public bool? IsFrameworkAssembly { get { return is_framework_assembly; } } + public string FullPath { get { return full_path; @@ -66,7 +66,10 @@ public string FullPath { set { full_path = value; if (!is_framework_assembly.HasValue && !string.IsNullOrEmpty (full_path)) { -#if !LEGACY_TOOLS +#if ASSEMBLY_PREPARER + is_framework_assembly = false; // silence compiler warning + throw new InvalidOperationException (); +#elif !LEGACY_TOOLS is_framework_assembly = App.Configuration.FrameworkAssemblies.Contains (GetIdentity (full_path)); #else var real_full_path = Application.GetRealPath (App, full_path); @@ -126,7 +129,7 @@ public void LoadSymbols () symbols_loaded = false; try { var pdb = Path.ChangeExtension (FullPath, ".pdb"); - if (File.Exists (pdb)) { + if (File.Exists (pdb) && !string.IsNullOrEmpty (AssemblyDefinition.MainModule.FileName)) { AssemblyDefinition.MainModule.ReadSymbols (); symbols_loaded = true; } diff --git a/tools/common/CoreResolver.cs b/tools/common/CoreResolver.cs index b35f575c9fc1..d869736ee3ba 100644 --- a/tools/common/CoreResolver.cs +++ b/tools/common/CoreResolver.cs @@ -4,6 +4,8 @@ using Mono.Cecil; using Mono.Cecil.Cil; +using Xamarin.Utils; + #nullable enable namespace Xamarin.Bundler { diff --git a/tools/common/DerivedLinkContext.cs b/tools/common/DerivedLinkContext.cs index 0f639be2e7b5..e6f21acdc947 100644 --- a/tools/common/DerivedLinkContext.cs +++ b/tools/common/DerivedLinkContext.cs @@ -6,12 +6,13 @@ using Mono.Collections.Generic; using Registrar; + using Mono.Tuner; using Xamarin.Bundler; using Xamarin.Linker; using Xamarin.Utils; -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER using LinkContext = Xamarin.Bundler.DotNetLinkContext; #endif @@ -65,6 +66,11 @@ public DerivedLinkContext (LinkerConfiguration configuration, Application app) } AssemblyDefinition? corlib; + +#if !LEGACY_TOOLS + public RegistrarMode Registrar => App.Registrar; +#endif // !LEGACY_TOOLS + public AssemblyDefinition Corlib { get { if (corlib is null) { @@ -341,6 +347,15 @@ public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttri return false; } + public AssemblyDefinition GetProductAssembly () + { + var productAssemblyName = Driver.GetProductAssembly (App); + var rv = this.GetAssembly (productAssemblyName); + if (rv is null) + throw ErrorHelper.CreateError (1504, Errors.MX1504 /* Can not find the product assembly '{0}' in the list of loaded assemblies. */, productAssemblyName); + return rv; + } + class AttributeStorage : ICustomAttribute { public CustomAttribute Attribute { get; } public TypeReference AttributeType { get; set; } diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index fe912e00e4a8..37a4cbd8bf8d 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Text; +using Xamarin.Bundler; using Xamarin.MacDev; using Xamarin.Utils; @@ -80,14 +81,14 @@ static void ParseOptions (Application app, Mono.Options.OptionSet options, strin } #endif // !LEGACY_TOOLS - public static int GetDefaultVerbosity () + public static int GetDefaultVerbosity (string toolName) { var v = 0; - var fn = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), $".{NAME}-verbosity"); + var fn = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), $".{toolName}-verbosity"); if (File.Exists (fn)) { v = (int) new FileInfo (fn).Length; if (v == 0) - v = 4; // this is the magic verbosity level we give everybody. + v = 4; // this is the magic verbosity level we give everybody if the file exists, but has no size. } return v; } diff --git a/tools/common/ErrorHelper.tools.cs b/tools/common/ErrorHelper.tools.cs index e930f95731f6..7f29a8980733 100644 --- a/tools/common/ErrorHelper.tools.cs +++ b/tools/common/ErrorHelper.tools.cs @@ -38,6 +38,12 @@ public enum WarningLevel { static ConditionalWeakTable> warning_levels = new (); + public static Dictionary? GetWarningLevels (IToolLog log) + { + warning_levels.TryGetValue (log, out var log_warning_levels); + return log_warning_levels; + } + public static WarningLevel GetWarningLevel (IToolLog log, int code) { if (warning_levels.TryGetValue (log, out var log_warning_levels)) { diff --git a/tools/common/IToolLog.cs b/tools/common/IToolLog.cs index 758cb136eaf4..2f9fd150a3ac 100644 --- a/tools/common/IToolLog.cs +++ b/tools/common/IToolLog.cs @@ -1,3 +1,7 @@ +#if BGENERATOR +using ProductException = BindingException; +#endif + using Xamarin.Utils; namespace Xamarin.Bundler; @@ -8,7 +12,8 @@ public interface IToolLog { void Log (string message); void LogError (string message); // Log an error we raise ourselves (through an exception) - void LogError (Exception exception); + void LogError (ProductException exception); + void LogWarning (ProductException exception); // Log an unexpected exception void LogException (Exception exception); } @@ -42,8 +47,10 @@ public class ConsoleLog : IToolLog { #if TESTS int verbosity = 0; -#else +#elif BGENERATOR int verbosity = Driver.GetDefaultVerbosity (); +#else + int verbosity = Driver.GetDefaultVerbosity (Driver.NAME); #endif public int Verbosity { get => verbosity; } @@ -60,11 +67,16 @@ public void LogError (string message) Console.Error.WriteLine (message); } - public void LogError (Exception exception) + public void LogError (ProductException exception) { Console.Error.WriteLine (exception); } + public void LogWarning (ProductException exception) + { + Console.WriteLine (exception); + } + public void LogException (Exception exception) { Console.Error.WriteLine (exception); diff --git a/tools/common/Make.common b/tools/common/Make.common index a83dd8a8c63b..d2dddec4ea22 100644 --- a/tools/common/Make.common +++ b/tools/common/Make.common @@ -3,6 +3,7 @@ LAST_XCODE_BUMP:=$(shell git blame -- $(TOP)/Make.config HEAD | grep " XCODE_VERSION=" | sed 's/ .*//') XCODE_BUMP_COMMIT_DISTANCE:=$(shell git log "$(LAST_XCODE_BUMP)..HEAD" --oneline | wc -l | sed -e 's/ //g') +all-local:: $(abspath ../common/SdkVersions.cs) $(abspath ../common/SdkVersions.cs): ../common/SdkVersions.in.cs Makefile $(TOP)/Make.config $(TOP)/Make.versions $(Q_GEN) sed \ -e 's/@IOS_SDK_VERSION@/$(IOS_SDK_VERSION)/g' -e 's/@TVOS_SDK_VERSION@/$(TVOS_SDK_VERSION)/' -e 's/@MACOS_SDK_VERSION@/$(MACOS_SDK_VERSION)/' \ @@ -49,6 +50,7 @@ $(abspath ../common/SdkVersions.cs): ../common/SdkVersions.in.cs Makefile $(TOP) $(Q) if ! diff $@ $@.tmp >/dev/null; then $(CP) $@.tmp $@; git diff "$@"; echo "The file $(TOP)/tools/common/SdkVersions.cs has been automatically re-generated; please commit the changes."; exit 1; fi $(Q) touch $@ +all-local:: $(abspath ../common/ProductConstants.cs) $(abspath ../common/ProductConstants.cs): ../common/ProductConstants.in.cs Makefile $(TOP)/Make.config $(GIT_DIRECTORY)/index $(Q_GEN) sed \ $(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),-e 's/@$(platform)_REVISION@/$($(platform)_COMMIT_DISTANCE) ($(CURRENT_BRANCH_SED_ESCAPED): $(CURRENT_HASH))/g') \ @@ -60,3 +62,4 @@ $(abspath ../common/ProductConstants.cs): ../common/ProductConstants.in.cs Makef -e "s/@XCODE_VERSION@/$(XCODE_VERSION)/g" \ -e "s/@XCODE_BUMP_COMMIT_DISTANCE@/$(XCODE_BUMP_COMMIT_DISTANCE)/g" \ $< > $@ + diff --git a/tools/common/Makefile b/tools/common/Makefile new file mode 100644 index 000000000000..6a27eba55f77 --- /dev/null +++ b/tools/common/Makefile @@ -0,0 +1,5 @@ +TOP=../.. + +include $(TOP)/Make.config +include $(TOP)/mk/rules.mk +include ../common/Make.common diff --git a/tools/common/NullableAttributes.cs b/tools/common/NullableAttributes.cs index dc8b0d8591ed..3f54b47e7c72 100644 --- a/tools/common/NullableAttributes.cs +++ b/tools/common/NullableAttributes.cs @@ -44,6 +44,24 @@ internal sealed class MemberNotNullAttribute : Attribute { /// Gets field or property member names. public string [] Members { get; } } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage (AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute (bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute { } + } #endif // !NET diff --git a/tools/common/Optimizations.cs b/tools/common/Optimizations.cs index d243a9b39415..133bb0406d44 100644 --- a/tools/common/Optimizations.cs +++ b/tools/common/Optimizations.cs @@ -172,7 +172,7 @@ public void Initialize (Application app, out List messages) continue; // The remove-dynamic-registrar optimization is required when using NativeAOT - if (app.XamarinRuntime == XamarinRuntime.NativeAOT && (Opt) i == Opt.RemoveDynamicRegistrar && values [i] == false) { + if (app.XamarinRuntime == XamarinRuntime.NativeAOT && (Opt) i == Opt.RemoveDynamicRegistrar && value == false) { messages.Add (ErrorHelper.CreateWarning (2016, Errors.MX2016 /* Keeping the dynamic registrar (by passing '--optimize=-remove-dynamic-registrar') is not possible, because the dynamic registrar is not supported when using NativeAOT. Support for dynamic registration will still be removed. */)); values [i] = true; continue; diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index f6b55ec0a015..fc2f5d3f7da3 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -2735,7 +2735,7 @@ public CSToObjCMap GetTypeMapDictionary (List exceptions) public void Rewrite () { -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER if (App.Optimizations.RedirectClassHandles == true) { var exceptions = new List (); var map_dict = GetTypeMapDictionary (exceptions); @@ -3964,7 +3964,7 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List nslog_start.AppendLine (");"); } -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER // Generate the native trampoline to call the generated UnmanagedCallersOnly method if we're using the managed static registrar. if (LinkContext.App.Registrar == RegistrarMode.ManagedStatic || LinkContext.App.Registrar == RegistrarMode.TrimmableStatic) { GenerateCallToUnmanagedCallersOnlyMethod (sb, method, isCtor, isVoid, num_arg, descriptiveMethodName, exceptions); @@ -4194,7 +4194,7 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List } } -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER void GenerateCallToUnmanagedCallersOnlyMethod (AutoIndentStringBuilder sb, ObjCMethod method, bool isCtor, bool isVoid, int num_arg, string descriptiveMethodName, List exceptions) { // Generate the native trampoline to call the generated UnmanagedCallersOnly method. @@ -5170,7 +5170,7 @@ bool TryCreateTokenReferenceUncached (MemberReference member, TokenType implied_ { var token = member.MetadataToken; -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER if (App.Registrar == RegistrarMode.TrimmableStatic) throw ErrorHelper.CreateError (99, $"Can't create a token reference when using the trimmable static registrar (for: {member.FullName})"); @@ -5388,7 +5388,7 @@ public void Register (PlatformResolver? resolver, IEnumerable r.Args) + .OfType () + .Where (r => r.SenderName == "BinaryLogger" && r.Message?.StartsWith ("BinLogFilePath=") == true) + .Select (v => v.Message?.Substring ("BinLogFilePath=".Length) ?? "") + .SingleOrDefault (); + var originalBinlogDirectory = Path.GetDirectoryName (originalBinlogPath)!; + foreach (var record in records) { if (record is null) continue; @@ -60,14 +73,99 @@ static int Main (string [] args) if (record.Args is null) continue; - if (record.Args is TaskStartedEventArgs tsea && tsea.TaskName == "ILLink") { + if (record.Args is not TaskStartedEventArgs tsea) + continue; + + switch (tsea.TaskName) { + case "PrepareAssemblies": { + var taskId = tsea.BuildEventContext?.TaskId; + if (taskId is null) + continue; + var relevantRecords = records. + Select (v => v.Args). + Where (v => v is not null). + Where (v => v.BuildEventContext?.TaskId == taskId). + ToArray (); + + var taskParameters = relevantRecords.Where (v => v is TaskParameterEventArgs).Cast ().ToArray (); + + string? getProperty (string name) + { + var param = taskParameters.SingleOrDefault (v => v.ItemType == name); + if (param is null) + return null; + if (param.Items is null) + return null; + if (param.Items.Count != 1) + return null; + var item = param.Items [0]; + if (item is null) + return null; + return ((ITaskItem) item).ItemSpec; + } + ITaskItem []? getItems (string name) + { + var param = taskParameters.SingleOrDefault (v => v.ItemType == name); + if (param is null) + return null; + if (param.Items is null) + return null; + return param.Items.Cast ().ToArray (); + } + var outputDirectory = getProperty ("OutputDirectory"); + var optionsFile = getProperty ("OptionsFile"); + var targetFrameworkMoniker = getProperty ("TargetFrameworkMoniker"); + var makeReproPath = getProperty ("MakeReproPath"); + var inputAssemblies = getItems ("InputAssemblies"); + + if (string.IsNullOrEmpty (outputDirectory)) + throw new InvalidOperationException ("OutputDirectory is required"); + outputDirectory = Path.GetFullPath (outputDirectory, originalBinlogDirectory); + + if (string.IsNullOrEmpty (optionsFile)) + throw new InvalidOperationException ("OptionsFile is required"); + optionsFile = Path.GetFullPath (optionsFile, originalBinlogDirectory); + + if (string.IsNullOrEmpty (targetFrameworkMoniker)) + throw new InvalidOperationException ("TargetFrameworkMoniker is required"); + + if (inputAssemblies is null || inputAssemblies.Length == 0) + throw new InvalidOperationException ("InputAssemblies is required"); + + string GetAssemblyInfo (ITaskItem item) + { + var inputPath = Path.GetFullPath (item.ItemSpec, originalBinlogDirectory); + var outputPath = Path.Combine (outputDirectory, Path.GetFileName (inputPath)); + var metadataNames = item.MetadataNames.Cast ().Select (v => v.ToLowerInvariant ()).ToHashSet (); + var isTrimmableString = item.GetMetadata ("IsTrimmable"); + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = item.GetMetadata ("TrimMode"); + + return $"InputPath={inputPath}|OutputPath={outputPath}|IsTrimmable={isTrimmable}|TrimMode={trimMode}"; + } + + var launcherArgs = new List (); + launcherArgs.Add ("${workspaceFolder}/bin/Debug/ap-launcher.dll"); + if (!string.IsNullOrEmpty (makeReproPath)) + launcherArgs.Add ("--make-repro=" + makeReproPath); + if (!string.IsNullOrEmpty (optionsFile)) + launcherArgs.Add ("--options-file=" + optionsFile); + + foreach (var ia in inputAssemblies) { + launcherArgs.Add ("--input-assembly=" + GetAssemblyInfo (ia)); + } + + WriteApLauncherLaunchJson (CreateLaunchJson (rootDirectory, launcherArgs.ToArray ())); + + break; + } + case "ILLink": { if (skippedLinkerCommands > 0) { Console.WriteLine ($"Skipped an ILLink task invocation, {skippedLinkerCommands} left to skip..."); skippedLinkerCommands--; continue; } - var relevantRecords = records.Where (v => v?.Args?.BuildEventContext?.TaskId == tsea.BuildEventContext?.TaskId).Select (v => v.Args).ToArray (); var cla = relevantRecords.Where (v => v is BuildMessageEventArgs).Cast ().Where (v => v?.ToString ()?.Contains ("CommandLineArguments") == true).ToArray (); foreach (var rr in relevantRecords) { @@ -77,10 +175,12 @@ static int Main (string [] args) return 1; } - WriteLaunchJson (CreateLaunchJson (rootDirectory, arguments)); + WriteDotNetLinkerLaunchJson (CreateLaunchJson (rootDirectory, arguments)); return 0; } } + break; + } } } @@ -88,12 +188,22 @@ static int Main (string [] args) return 1; } - static void WriteLaunchJson (string contents) + static void WriteApLauncherLaunchJson (string contents) + { + WriteLaunchJson ("ap-launcher", contents); + } + + static void WriteDotNetLinkerLaunchJson (string contents) + { + WriteLaunchJson ("dotnet-linker", contents); + } + + static void WriteLaunchJson (string toolName, string contents) { var dir = Environment.CurrentDirectory!; - while (!Directory.Exists (Path.Combine (dir, "tools", "dotnet-linker"))) + while (!Directory.Exists (Path.Combine (dir, "tools", toolName))) dir = Path.GetDirectoryName (dir)!; - var path = Path.Combine (dir, "tools", "dotnet-linker", ".vscode", "launch.json"); + var path = Path.Combine (dir, "tools", toolName, ".vscode", "launch.json"); File.WriteAllText (path, contents); Console.WriteLine ($"Created {path}"); } diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index 1914b02994d7..e9257f6727a5 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -80,6 +80,8 @@ public AppBundleRewriter (LinkerConfiguration configuration) // Find corlib and the platform assemblies foreach (var asm in configuration.Assemblies) { if (asm.Name.Name == Driver.CorlibName) { + if (corlib_assembly is not null) + throw new InvalidOperationException ($"Already have a corlib assembly named {corlib_assembly.Name}"); corlib_assembly = asm; } else if (asm.Name.Name == configuration.PlatformAssembly) { platform_assembly = asm; @@ -250,6 +252,13 @@ public TypeReference System_Exception { return GetTypeReference (CorlibAssembly, "System.Exception", out var _); } } + + public TypeReference System_GC { + get { + return GetTypeReference (CorlibAssembly, "System.GC", out var _); + } + } + public TypeReference System_Int32 { get { return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.Int32); @@ -528,6 +537,17 @@ public MethodReference System_Console__WriteLine_String_Object { } } + public MethodReference System_GC__KeepAlive { + get { + return GetMethodReference (CorlibAssembly, System_GC, "KeepAlive", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && !v.HasGenericParameters); + } + } + public MethodReference System_Object__ctor { get { return GetMethodReference (CorlibAssembly, System_Object, ".ctor", (v) => v.IsDefaultConstructor ()); @@ -559,6 +579,12 @@ public MethodReference Nullable_Value { } } + public MethodReference Nullable_ctor { + get { + return GetMethodReference (CorlibAssembly, System_Nullable_1, ".ctor", isStatic: false, System_Nullable_1.GenericParameters [0]); + } + } + public MethodReference Type_GetTypeFromHandle { get { return GetMethodReference (CorlibAssembly, System_Type, "GetTypeFromHandle", isStatic: true, System_RuntimeTypeHandle); @@ -1403,7 +1429,6 @@ public MethodReference Unsafe_AsRef { } } -#if NET public bool TryGet_NSObject_RegisterToggleRef ([NotNullWhen (true)] out MethodDefinition? md) { // the NSObject.RegisterToggleRef method isn't present on all platforms (for example on Mac) @@ -1415,7 +1440,6 @@ public bool TryGet_NSObject_RegisterToggleRef ([NotNullWhen (true)] out MethodDe return false; } } -#endif public void SetCurrentAssembly (AssemblyDefinition value) { @@ -1434,6 +1458,7 @@ void SaveAssembly (AssemblyDefinition assembly) var annotations = configuration.Context.Annotations; var action = annotations.GetAction (assembly); if (action == AssemblyAction.Copy) { +#if !ASSEMBLY_PREPARER // Preserve TypeForwardedTo which would the linker sweep otherwise // Note that the linker will sweep type forwarders even if the assembly isn't trimmed: // https://github.com/dotnet/runtime/blob/9dd59af3aee2f403e63887afef50d98022a2e575/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs#L191-L200 @@ -1442,6 +1467,7 @@ void SaveAssembly (AssemblyDefinition assembly) annotations.Mark (type); } } +#endif // !ASSEMBLY_PREPARER annotations.SetAction (assembly, AssemblyAction.Save); } } @@ -1457,9 +1483,11 @@ public void ClearCurrentAssembly () public CustomAttribute CreateAttribute (MethodReference constructor) { +#if !ASSEMBLY_PREPARER // For some reason the trimmer doesn't mark attribute constructors // This is probably only needed when running as a custom linker step. configuration.Context.Annotations.Mark (constructor.Resolve ()); +#endif // !ASSEMBLY_PREPARER return new CustomAttribute (constructor); } @@ -1478,8 +1506,7 @@ public bool AddDynamicDependencyAttribute (MethodDefinition addToMethod, MethodD return false; if (addToMethod.DeclaringType == dependsOn.DeclaringType) { - var attribute = CreateAttribute (DynamicDependencyAttribute_ctor__String); - attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_String, DocumentationComments.GetSignature (dependsOn))); + var attribute = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (dependsOn)); return AddAttributeOnlyOnce (addToMethod, attribute); } else if (addToMethod.DeclaringType.Module == dependsOn.DeclaringType.Module) { var attribute = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (dependsOn), dependsOn.DeclaringType); @@ -1501,11 +1528,23 @@ public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, return attribute; } + public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature) + { + var attribute = CreateAttribute (DynamicDependencyAttribute_ctor__String); + attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_String, memberSignature)); + return attribute; + } + public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, TypeDefinition type, AssemblyDefinition assembly) { return CreateDynamicDependencyAttribute (memberSignature, DocumentationComments.GetSignature (type), assembly.Name.Name); } + public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, string typeName, AssemblyDefinition assembly) + { + return CreateDynamicDependencyAttribute (memberSignature, typeName, assembly.Name.Name); + } + public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, string typeName, string assemblyName) { var attribute = CreateAttribute (DynamicDependencyAttribute_ctor__String_String_String); @@ -1531,7 +1570,16 @@ public CustomAttribute CreateDynamicDependencyAttribute (DynamicallyAccessedMemb /// The method that is the target of the dynamic dependency. public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, MethodDefinition forMethod) { - var attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType, forMethod.Module.Assembly); + CustomAttribute attrib; + + if (onType == forMethod.DeclaringType) { + attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod)); + } else if (onType.Module == forMethod.DeclaringType.Module) { + attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType); + } else { + attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType, forMethod.Module.Assembly); + } + return AddAttributeToStaticConstructor (onType, attrib); } @@ -1580,13 +1628,6 @@ public bool AddAttributeToStaticConstructor (TypeDefinition onType, CustomAttrib { var cctor = GetOrCreateStaticConstructor (onType, out var modified); modified |= AddAttributeOnlyOnce (cctor, attribute); - - // Remove the BeforeFieldInit attribute from the type, otherwise the linker may trim away the static constructor, and taking our attributes with it. - if (onType.Attributes.HasFlag (TypeAttributes.BeforeFieldInit)) { - onType.Attributes &= ~TypeAttributes.BeforeFieldInit; - modified = true; - } - return modified; } @@ -1599,10 +1640,22 @@ public MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out b staticCtor = type.AddMethod (".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.Static, System_Void); staticCtor.CreateBody (out var il); il.Emit (OpCodes.Ret); + modified = true; + } + // Remove the BeforeFieldInit attribute from the type, otherwise the linker may trim away the static constructor, and taking our attributes with it. + if (type.Attributes.HasFlag (TypeAttributes.BeforeFieldInit)) { + type.Attributes &= ~TypeAttributes.BeforeFieldInit; modified = true; } + if (!staticCtor.Body.Instructions.Any (v => v.OpCode != OpCodes.Ret && v.OpCode != OpCodes.Nop)) { + // FIXME: improve workaround. + var body = staticCtor.Body; + body.Instructions.Insert (0, Instruction.Create (OpCodes.Call, this.System_GC__KeepAlive)); + body.Instructions.Insert (0, Instruction.Create (OpCodes.Ldnull)); + } + return staticCtor; } diff --git a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs index 1d4bc9520977..7135c70ffd90 100644 --- a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs +++ b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs @@ -12,7 +12,7 @@ #nullable enable namespace Xamarin.Linker.Steps { - +#if !ASSEMBLY_PREPARER public partial class ApplyPreserveAttribute : ConfigurationAwareSubStep, IApplyPreserveAttribute { ApplyPreserveAttributeImpl impl; @@ -64,6 +64,7 @@ bool IApplyPreserveAttribute.PreserveConditional (TypeDefinition onType, MethodD return true; } } +#endif public interface IApplyPreserveAttribute { bool PreserveType (TypeDefinition type, bool allMembers); diff --git a/tools/dotnet-linker/ApplyPreserveAttributeStep.cs b/tools/dotnet-linker/ApplyPreserveAttributeStep.cs index 18436ab2de88..f1d4025b7bfa 100644 --- a/tools/dotnet-linker/ApplyPreserveAttributeStep.cs +++ b/tools/dotnet-linker/ApplyPreserveAttributeStep.cs @@ -45,7 +45,11 @@ public bool CreateXmlDescriptionFile { } } +#if ASSEMBLY_PREPARER + public bool UseXmlDescriptionFile { get; set; } +#else public bool UseXmlDescriptionFile { get; set; } = true; +#endif public string XmlDescriptionPath { get; set; } = string.Empty; public ApplyPreserveAttributeStep () @@ -316,6 +320,7 @@ void WriteXmlDescription () Configuration.WriteOutputForMSBuild ("TrimmerRootDescriptor", items); } +#if !ASSEMBLY_PREPARER // The current linker run still needs these roots immediately. Writing the TrimmerRootDescriptor item only // makes the descriptor available to MSBuild after this step has already finished running. var applyXmlStepType = Context.GetType ().Assembly.GetType ("Mono.Linker.Steps.ResolveFromXmlStep"); @@ -326,6 +331,7 @@ void WriteXmlDescription () } else { throw ErrorHelper.CreateError (99, $"Unable to find Mono.Linker.Steps.ResolveFromXmlStep to apply the generated XML description file {xmlPath}"); } +#endif } } } diff --git a/tools/dotnet-linker/Compat.cs b/tools/dotnet-linker/Compat.cs index 491446a4a4b8..dfe1ab3f489d 100644 --- a/tools/dotnet-linker/Compat.cs +++ b/tools/dotnet-linker/Compat.cs @@ -100,7 +100,7 @@ public AnnotationStore Annotations { } } - public AssemblyDefinition GetAssembly (string name) + public AssemblyDefinition? GetAssembly (string name) { return LinkerConfiguration.Context.GetLoadedAssembly (name); } diff --git a/tools/dotnet-linker/DotNetGlobals.cs b/tools/dotnet-linker/DotNetGlobals.cs index 7642a580fb19..46c6a32dbe19 100644 --- a/tools/dotnet-linker/DotNetGlobals.cs +++ b/tools/dotnet-linker/DotNetGlobals.cs @@ -4,6 +4,7 @@ global using System; global using System.Collections.Generic; global using System.Diagnostics.CodeAnalysis; +global using System.Linq; global using System.Runtime.InteropServices; global using Foundation; diff --git a/tools/dotnet-linker/DotNetResolver.cs b/tools/dotnet-linker/DotNetResolver.cs index f71c2ec5b4cb..f989c68c5076 100644 --- a/tools/dotnet-linker/DotNetResolver.cs +++ b/tools/dotnet-linker/DotNetResolver.cs @@ -14,6 +14,12 @@ public DotNetResolver (Application app) public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters) { +#if ASSEMBLY_PREPARER + if (cache.TryGetValue (name.Name, out var assembly)) +#else + if (cache.TryGetValue (name.Name, out var assembly) && assembly.Name.FullName == name.FullName) +#endif + return assembly; throw new NotImplementedException ($"Unable to resolve the assembly reference {name}"); } } diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 1ad78a1a8313..8c7e6b148dd7 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -23,6 +23,7 @@ public class LinkerConfiguration { public Abi Abi = Abi.None; public string AOTCompiler = string.Empty; public string AOTOutputDirectory = string.Empty; + public string AssemblyPublishDir = string.Empty; public string DedupAssembly = string.Empty; public string CacheDirectory { get; private set; } = string.Empty; public Version? DeploymentTarget { get; private set; } @@ -43,10 +44,13 @@ public class LinkerConfiguration { public string PartialStaticRegistrarLibrary { get; set; } = string.Empty; public ApplePlatform Platform { get; private set; } public string PlatformAssembly { get; private set; } = string.Empty; + public bool PublishTrimmed { get; private set; } public string RelativeAppBundlePath { get; private set; } = string.Empty; public Version? SdkVersion { get; private set; } public string SdkRootDirectory { get; private set; } = string.Empty; public string TypeMapFilePath { get; set; } = string.Empty; + public string TrimMode { get; private set; } = string.Empty; + public string UnmanagedCallersOnlyMapPath { get; private set; } = string.Empty; public int Verbosity => Application.Verbosity; public string XamarinNativeLibraryDirectory { get; private set; } = string.Empty; @@ -54,17 +58,41 @@ public class LinkerConfiguration { public Application Application { get; private set; } + public IToolLog Logger { get; private set; } + public IList RegistrationMethods { get; set; } = new List (); public List NativeCodeToCompileAndLink { get; private set; } = new List (); +#if !ASSEMBLY_PREPARER public CompilerFlags CompilerFlags; +#endif + +#if ASSEMBLY_PREPARER + List exceptions = new List (); + public List Exceptions { + get { + return exceptions; + } + } + public DotNetResolver AssemblyResolver { get; private set; } + public IMetadataResolver MetadataResolver { get; private set; } +#endif +#if ASSEMBLY_PREPARER + public LinkContext Context { get => DerivedLinkContext; } +#else LinkContext? context; public LinkContext Context { get => context!; private set { context = value; } } +#endif public DerivedLinkContext DerivedLinkContext { get => Application.LinkContext; } public Profile Profile { get; private set; } +#if ASSEMBLY_PREPARER + public List Assemblies => Application.LinkContext.Assemblies; + public List<(string Path, AssemblyDefinition Assembly, string? OriginatingAssembly)> AddedAssemblies = new (); +#else // The list of assemblies is populated in CollectAssembliesStep. public List Assemblies = new List (); +#endif string? user_optimize_flags; @@ -93,23 +121,29 @@ public AssemblyDefinition EntryAssembly { // This dictionary contains information about the trampolines created for each assembly. public AssemblyTrampolineInfos AssemblyTrampolineInfos = new (); + // ASSEMBLY_PREPARER TODO move pinvoke wrapper generation out of ListExportedFields step (and remove the #pragma warning) +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null internal PInvokeWrapperGenerator? PInvokeWrapperGenerationState; +#pragma warning restore CS0649 public static bool TryGetInstance (LinkContext context, [NotNullWhen (true)] out LinkerConfiguration? configuration) { return configurations.TryGetValue (context, out configuration); } - public static LinkerConfiguration GetInstance (LinkContext context) { if (!TryGetInstance (context, out var instance)) { +#if ASSEMBLY_PREPARER + throw new InvalidOperationException ($"No LinkerConfiguration instance found for the given LinkContext."); +#else if (!context.TryGetCustomData ("LinkerOptionsFile", out var linker_options_file)) throw new Exception ($"No custom linker options file was passed to the linker (using --custom-data LinkerOptionsFile=..."); - instance = new LinkerConfiguration (linker_options_file) { + instance = new LinkerConfiguration (ConsoleLog.Instance, linker_options_file) { Context = context, }; configurations.Add (context, instance); +#endif } return instance; @@ -147,6 +181,7 @@ Configurator GetConfigurator (string linker_file) if (!TryParseOptionalBoolean (value, out result)) throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); }); + var loadWarningLevel = new Action ((key, value, level) => { try { ErrorHelper.ParseWarningLevel (Application, level, value); @@ -177,6 +212,11 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => Application.RootAssemblies.Add (value)), new SaveValue ((key, storage) => storage.AddRange (Application.RootAssemblies.Select (v => $"{key}={v}"))) )}, + { "AssemblyPublishDir", ( + // This is the AssemblyPublishDir MSBuild property for the main project + new LoadValue ((key, value) => AssemblyPublishDir = value), + new SaveValue ((key, storage) => saveNonEmpty (key, AssemblyPublishDir, storage)) + )}, { "AOTArgument", ( new LoadValue ((key, value) => { @@ -511,6 +551,10 @@ Configurator GetConfigurator (string linker_file) }), new SaveValue ((key, storage) => saveNonEmpty (key, Application.TargetFramework.ToString (), storage)) )}, + { "TrimMode", ( + new LoadValue ((key, value) => TrimMode = value), + new SaveValue ((key, storage) => saveNonEmpty (key, TrimMode, storage)) + )}, { "TypeMapAssemblyName", ( new LoadValue ((key, value) => Application.TypeMapAssemblyName = value), new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapAssemblyName, storage)) @@ -523,6 +567,10 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => Application.TypeMapOutputDirectory = value), new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapOutputDirectory, storage)) )}, + { "UnmanagedCallersOnlyMapPath", ( + new LoadValue ((key, value) => UnmanagedCallersOnlyMapPath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, UnmanagedCallersOnlyMapPath, storage)) + )}, { "UseLlvm", ( new LoadValue ((key, value) => { var use_llvm = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); @@ -580,28 +628,42 @@ Configurator GetConfigurator (string linker_file) return dict; } - LinkerConfiguration (string linker_file) + public LinkerConfiguration (IToolLog log, string linker_file, Configurator? customConfigurator = null) + : this (log, File.ReadAllLines (linker_file).ToList (), linker_file, customConfigurator) { - if (!File.Exists (linker_file)) - throw new FileNotFoundException ($"The custom linker file {linker_file} does not exist."); + } + + public LinkerConfiguration (IToolLog log, List lines, string linker_file, Configurator? customConfigurator = null) + { + this.Logger = log; LinkerFile = linker_file; Profile = new BaseProfile (this); Application = new Application (this); + +#if ASSEMBLY_PREPARER + AssemblyResolver = new DotNetResolver (Application); + MetadataResolver = new MetadataResolver (AssemblyResolver); + + configurations.Add (this.Context, this); +#endif + +#if !ASSEMBLY_PREPARER CompilerFlags = new CompilerFlags (Application); +#endif var configurator = GetConfigurator (linker_file); - var lines = File.ReadAllLines (linker_file); + var significantLines = new List (); // This is the input the cache uses to verify if the cache is still valid - for (var i = 0; i < lines.Length; i++) { + for (var i = 0; i < lines.Count; i++) { var line = lines [i].TrimStart (); if (line.Length == 0 || line [0] == '#') continue; // Allow comments var eq = line.IndexOf ('='); if (eq == -1) - throw new InvalidOperationException ($"Invalid syntax for line {i + 1} in {linker_file}: No equals sign."); + throw new InvalidOperationException ($"Invalid syntax for line {i + 1} ('{line}') in {linker_file}:{i + 1} : No equals sign."); significantLines.Add (line); @@ -613,6 +675,8 @@ Configurator GetConfigurator (string linker_file) if (configurator.TryGetValue (key, out var actions)) { actions.Load (key, value); + } else if (customConfigurator?.TryGetValue (key, out var customActions) == true) { + customActions.Load (key, value); } else { throw new InvalidOperationException ($"Unknown configuration key '{key}' in {linker_file} at line {i + 1}."); } @@ -626,7 +690,7 @@ Configurator GetConfigurator (string linker_file) } Application.CreateCache (significantLines.ToArray ()); - if (Application.Cache is not null) + if (Application.Cache is not null && !string.IsNullOrEmpty (CacheDirectory)) Application.Cache.SetLocation (Application, CacheDirectory); if (DeploymentTarget is not null) Application.DeploymentTarget = DeploymentTarget; @@ -665,12 +729,18 @@ Configurator GetConfigurator (string linker_file) Application.Initialize (); } - public void Save (List storage) + public void Save (List storage, Configurator? customConfigurator = null) { var configurator = GetConfigurator (LinkerFile); foreach (var kvp in configurator.OrderBy (v => v.Key)) { kvp.Value.Save (kvp.Key, storage); } + + if (customConfigurator is not null) { + foreach (var kvp in customConfigurator.OrderBy (v => v.Key)) { + kvp.Value.Save (kvp.Key, storage); + } + } } // Splits a string in three based on the split character. @@ -771,6 +841,7 @@ public void Write () Application.Log ($" TypeMapAssemblyName: {Application.TypeMapAssemblyName}"); Application.Log ($" TypeMapFilePath: {TypeMapFilePath}"); Application.Log ($" TypeMapOutputDirectory: {Application.TypeMapOutputDirectory}"); + Application.Log ($" UnmanagedCallersOnlyMapPath: {UnmanagedCallersOnlyMapPath}"); Application.Log ($" UseInterpreter: {Application.UseInterpreter}"); Application.Log ($" UseLlvm: {Application.IsLLVM}"); Application.Log ($" Verbosity: {Verbosity}"); @@ -779,10 +850,12 @@ public void Write () } } +#if !ASSEMBLY_PREPARER public string GetAssemblyFileName (AssemblyDefinition assembly) { return Context.GetAssemblyLocation (assembly); } +#endif public void WriteOutputForMSBuild (string itemName, List items) { @@ -821,12 +894,27 @@ public static void Report (LinkContext Context, params Exception [] exceptions) public static void Report (LinkContext context, IList exceptions) { + // Unwrap aggregate exceptions, and collect all exceptions into a single list. + var list = ErrorHelper.CollectExceptions (exceptions); +#if ASSEMBLY_PREPARER + var log = context.Configuration.Logger; + foreach (var ex in list) { + if (ex is ProductException pe) { + if (pe.IsError (context.Configuration.Application)) { + log.LogError (pe); + } else { + log.LogWarning (pe); + } + } else { + log.LogException (ex); + } + } +#else // We can't really use the linker's reporting facilities and keep our own error codes, because we'll // end up re-using the same error codes the linker already uses for its own purposes. So instead show // a generic error using the linker's Context.LogMessage API, and then print our own errors to stderr. // Since we print using a standard message format, msbuild will parse those error messages and show // them as msbuild errors. - var list = ErrorHelper.CollectExceptions (exceptions); if (!TryGetInstance (context, out var instance)) { // Something went very wrong. Just dump out everything. context.LogMessage (MessageContainer.CreateCustomErrorMessage ("No linker configuration available.", 7000)); @@ -844,6 +932,7 @@ public static void Report (LinkContext context, IList exceptions) } // ErrorHelper.Show will print our errors and warnings to stderr. ErrorHelper.Show (instance.Application, list); +#endif } public IEnumerable GetNonDeletedAssemblies (BaseStep step) @@ -854,6 +943,38 @@ public IEnumerable GetNonDeletedAssemblies (BaseStep step) yield return assembly; } } + + public void Log (string value) + { + Log (0, value); + } + + public void Log (string format, params object? [] args) + { + Log (0, format, args); + } + + public void Log (int min_verbosity, string value) + { + if (min_verbosity > Verbosity) + return; + + if (Logger is not null) { + Logger.Log (value); + return; + } + + Console.WriteLine (value); + } + + public void Log (int min_verbosity, string format, params object? [] args) + { + if (min_verbosity > Verbosity) + return; + + var value = string.Format (format, args); + Log (min_verbosity, value); + } } } diff --git a/tools/dotnet-linker/MarkIProtocolHandler.cs b/tools/dotnet-linker/MarkIProtocolHandler.cs index b295ae6d385c..772bca9a3086 100644 --- a/tools/dotnet-linker/MarkIProtocolHandler.cs +++ b/tools/dotnet-linker/MarkIProtocolHandler.cs @@ -16,7 +16,7 @@ public override void Initialize (LinkContext context, MarkContext markContext) { base.Initialize (context); - if (LinkContext.App.Registrar == Bundler.RegistrarMode.Dynamic) { + if (LinkContext.Registrar == Bundler.RegistrarMode.Dynamic) { markContext.RegisterMarkTypeAction (ProcessType); } } @@ -26,19 +26,27 @@ protected override void Process (TypeDefinition type) if (!type.HasInterfaces) return; + var anyAdded = false; foreach (var iface in type.Interfaces) { var resolvedInterfaceType = iface.InterfaceType.Resolve (); // If we're using the dynamic registrar, we need to mark interfaces that represent protocols // even if it doesn't look like the interfaces are used, since we need them at runtime. var isProtocol = type.IsNSObject (LinkContext) && resolvedInterfaceType.HasCustomAttribute (LinkContext, Namespaces.Foundation, "ProtocolAttribute"); if (isProtocol) { - // Mark only if not already marked. - // otherwise we might enqueue something everytime and never get an empty queue - if (!LinkContext.Annotations.IsMarked (resolvedInterfaceType)) { - LinkContext.Annotations.Mark (resolvedInterfaceType); - } + // Preserve the method and field on the static constructor of the type. + abr.AddDynamicDependencyAttributeToStaticConstructor (type, resolvedInterfaceType); + anyAdded = true; } } + +#if ASSEMBLY_PREPARER + if (anyAdded) { + abr.SetCurrentAssembly (type.Module.Assembly); + abr.SaveCurrentAssembly (); + } +#else + _ = anyAdded; +#endif } } } diff --git a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs index 25153aca76fa..298a3d74a896 100644 --- a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs +++ b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs @@ -32,7 +32,11 @@ public virtual void Initialize (LinkContext context) protected AnnotationStore Annotations => Context.Annotations; protected LinkerConfiguration Configuration => LinkerConfiguration.GetInstance (Context); + private protected AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } + +#if !ASSEMBLY_PREPARER protected Profile Profile => Configuration.Profile; +#endif protected Application App => Configuration.Application; diff --git a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs index 95913f88ead9..5393008d2c41 100644 --- a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs +++ b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs @@ -101,7 +101,7 @@ attrib.ConstructorArguments [1].Value is string libraryName && TypeDefinition GetDlfcnType (ModuleDefinition module, string @namespace, string? fieldLibraryName = null) { var frameworkOverride = !string.IsNullOrEmpty (fieldLibraryName) ? fieldLibraryName : current_framework; - var ns = string.IsNullOrEmpty (frameworkOverride) ? @namespace : frameworkOverride; + var ns = frameworkOverride ?? @namespace; var rv = abr.GetOrCreateType (module, ns, "Dlfcn", out var created); if (created) { if (!string.IsNullOrEmpty (frameworkOverride)) { diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index 4396c7319478..499ad0a43357 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -57,11 +57,28 @@ protected override void TryProcessAssembly (AssemblyDefinition assembly) return; abr.SetCurrentAssembly (assembly); + if (App.IsPostProcessingAssemblies) { + // We need to load what the PrepareAssemblies task did/produced + CollectRegistrarType (info, assembly); + } else { + CreateRegistrarType (info); + abr.SaveCurrentAssembly (); + } + abr.ClearCurrentAssembly (); + } - CreateRegistrarType (info); + void CollectRegistrarType (AssemblyTrampolineInfo info, AssemblyDefinition currentAssembly) + { + var registrarType = currentAssembly.MainModule.Types.SingleOrDefault (v => v.Is ("ObjCRuntime", "__Registrar__")); + if (registrarType is null) + throw ErrorHelper.CreateError (99, $"No __Registrar__ was found in the assembly {currentAssembly.Name.Name} after the PrepareAssemblies step, but none was found. This might be a sign that the PrepareAssemblies step didn't run, or didn't run correctly."); - abr.SaveCurrentAssembly (); - abr.ClearCurrentAssembly (); + info.RegistrarType = registrarType; + + // We don't care about getting the types, but we need the mapping to happen. + // None of the types in the generated table should be trimmed away by the trimmer, so sorting + // them when the table is generated, and then again after trimming (aka here), should result in the same order and thus the same mapping. + GetAndMapTypesToRegister (registrarType, info); } void CreateRegistrarType (AssemblyTrampolineInfo info) @@ -119,7 +136,7 @@ void CreateRegistrarType (AssemblyTrampolineInfo info) AddLoadTypeToModuleConstructor (registrarType); // Compute the list of types that we need to register - var types = GetTypesToRegister (registrarType, info); + var types = GetAndMapTypesToRegister (registrarType, info); GenerateLookupUnmanagedFunction (registrarType, sorted); GenerateLookupType (info, registrarType, types); @@ -164,7 +181,7 @@ void AddLoadTypeToModuleConstructor (TypeDefinition registrarType) Annotations.Mark (moduleConstructor); } - List GetTypesToRegister (TypeDefinition registrarType, AssemblyTrampolineInfo info) + List GetAndMapTypesToRegister (TypeDefinition registrarType, AssemblyTrampolineInfo info) { // Compute the list of types that we need to register var types = new List (); @@ -201,6 +218,9 @@ List GetTypesToRegister (TypeDefinition registrarType, AssemblyTrampol types.Add (new (wrapperType, wrapperType.Resolve ())); } + // Sort the types by their full name to make sure the generated code is deterministic. + types.Sort ((x, y) => string.Compare (x.Definition.FullName, y.Definition.FullName, StringComparison.Ordinal)); + // Now create a mapping from type to index for (var i = 0; i < types.Count; i++) info.RegisterType (types [i].Definition, (uint) i); @@ -228,7 +248,11 @@ IEnumerable GetRelevantTypes (Func isRelev bool IsTrimmed (MemberReference type) { +#if ASSEMBLY_PREPARER + return false; +#else return StaticRegistrar.IsTrimmed (type, Annotations); +#endif } void GenerateLookupTypeId (AssemblyTrampolineInfo infos, TypeDefinition registrarType, List types) diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 28d906d981db..9db0c5a5fb7a 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -84,6 +84,8 @@ public class ManagedRegistrarStep : ConfigurationAwareStep { AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } List exceptions = new List (); + Dictionary unmanagedCallersOnlyMap = new (); + void AddException (Exception exception) { if (exceptions is null) @@ -98,6 +100,23 @@ protected override void TryProcess () if (App.Registrar != RegistrarMode.ManagedStatic && App.Registrar != RegistrarMode.TrimmableStatic) return; + if (App.IsPostProcessingAssemblies) { + var ucoMapPath = Configuration.UnmanagedCallersOnlyMapPath; + if (File.Exists (ucoMapPath)) { + foreach (var line in File.ReadAllLines (ucoMapPath)) { + var parts = line.Split ('|'); + if (parts.Length != 2) { + Console.WriteLine ($"Warning: Invalid line in unmanaged_callers_only_map.txt: {line}"); + continue; + } + var methodFullName = parts [0]; + var ucoEntryPoint = parts [1]; + unmanagedCallersOnlyMap.Add (methodFullName, ucoEntryPoint); + } + File.Delete (ucoMapPath); + } + } + Configuration.Application.StaticRegistrar.Register (Configuration.GetNonDeletedAssemblies (this)); } @@ -113,10 +132,23 @@ protected override void TryEndProcess (out List? exceptions) // Report back any exceptions that occurred during the processing. exceptions = this.exceptions; + if (App.PrepareAssemblies && !App.InCustomTrimmerStep) { + var ucoMapPath = Configuration.UnmanagedCallersOnlyMapPath; + using (var writer = new StreamWriter (ucoMapPath, false)) { + foreach (var entry in unmanagedCallersOnlyMap.Select (kvp => $"{kvp.Key}|{kvp.Value}").OrderBy (v => v)) { + writer.WriteLine (entry); + } + } + } + +#if !ASSEMBLY_PREPARER // Mark some stuff we use later on. - abr.SetCurrentAssembly (abr.PlatformAssembly); - Annotations.Mark (abr.RegistrarHelper_Register.Resolve ()); - abr.ClearCurrentAssembly (); + if (App.InCustomTrimmerStep && App.PrepareAssemblies == false) { + abr.SetCurrentAssembly (abr.PlatformAssembly); + Annotations.Mark (abr.RegistrarHelper_Register.Resolve ()); + abr.ClearCurrentAssembly (); + } +#endif } protected override void TryProcessAssembly (AssemblyDefinition assembly) @@ -181,7 +213,7 @@ bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos, List proxyInterfaces) + { + if (!unmanagedCallersOnlyMap.TryGetValue (method.FullName, out var ucoName)) { + AddException (ErrorHelper.CreateWarning (App, 99, method, $"Couldn't find an entry in the unmanaged_callers_only_map for method {method.FullName}.")); + return; + } + + var callbackType = method.DeclaringType.NestedTypes.SingleOrDefault (v => v.Name == "__Registrar_Callbacks__"); + if (callbackType is null) { + AddException (ErrorHelper.CreateWarning (App, 99, method, $"Couldn't find the __Registrar_Callbacks__ nested type for method {method.FullName}.")); + return; + } + + var candidates = callbackType.Methods.Where (v => v.Name == ucoName).ToArray (); + if (candidates.Length != 1) { + AddException (ErrorHelper.CreateWarning (App, 99, method, $"Didn't find exactly one matching callback method in __Registrar_Callbacks__ for method {method.FullName}, found {candidates.Length}")); + return; + } + var callback = candidates [0]; + + var info = new TrampolineInfo (callback, method, ucoName); + if (this.App.Registrar == RegistrarMode.TrimmableStatic) { + // Don't set Id here, it's not used. + } else if (int.TryParse (ucoName.Split ('_') [1], NumberStyles.None, CultureInfo.InvariantCulture, out var id)) { + info.Id = id; + } else { + AddException (ErrorHelper.CreateError (App, 99, method, $"Failed to parse the ID from the DynamicDependencyAttribute for method {method.FullName}, the trampoline won't be registered correctly. The member signature was: {ucoName}")); + } + infos.Add (info); + } + int counter; void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineInfo infos, List proxyInterfaces) { @@ -306,6 +374,8 @@ void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineIn var placeholderType = abr.System_IntPtr; var name = $"callback_{counter++}_{Sanitize (method.DeclaringType.FullName)}_{Sanitize (method.Name)}"; + unmanagedCallersOnlyMap.Add (method.FullName, name); + var callbackType = method.DeclaringType.NestedTypes.SingleOrDefault (v => v.Name == "__Registrar_Callbacks__"); if (callbackType is null) { callbackType = new TypeDefinition (string.Empty, "__Registrar_Callbacks__", TypeAttributes.NestedPrivate | TypeAttributes.Sealed | TypeAttributes.Class); diff --git a/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs b/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs index 98194cad5928..b406122d0f26 100644 --- a/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs +++ b/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs @@ -2,7 +2,8 @@ using System.Linq; using Mono.Cecil; - +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; using Mono.Linker; using Mono.Linker.Steps; using Mono.Tuner; diff --git a/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs b/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs index 22f70f051ef0..e431f8e009bc 100644 --- a/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs +++ b/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs @@ -1,5 +1,6 @@ using Mono.Linker.Steps; using Xamarin.Linker; +using Xamarin.Linker.Steps; using Mono.Cecil; using Mono.Tuner; @@ -7,10 +8,27 @@ #nullable enable namespace Xamarin.Linker.Steps { +#if ASSEMBLY_PREPARER + public class SetBeforeFieldInitStep : AssemblyModifierStep { +#else public class SetBeforeFieldInitStep : ConfigurationAwareSubStep { +#endif protected override string Name { get; } = "Set BeforeFieldInit"; protected override int ErrorCode { get; } = 2380; +#if ASSEMBLY_PREPARER + protected override bool ModifyAssembly (AssemblyDefinition assembly) + { + if (Configuration.DerivedLinkContext.App.Optimizations.RegisterProtocols != true) + return false; + return base.ModifyAssembly (assembly); + } + + protected override bool ProcessType (TypeDefinition type) + { + return ProcessTypeImpl (type); + } +#else public override SubStepTargets Targets { get { return SubStepTargets.Type; @@ -19,6 +37,13 @@ public override SubStepTargets Targets { protected override void Process (TypeDefinition type) { + ProcessTypeImpl (type); + } +#endif + + bool ProcessTypeImpl (TypeDefinition type) + { + var modified = false; // If we're registering protocols, we want to remove the static // constructor on the protocol interface, because it's not needed // (because we've removing all the DynamicDependency attributes @@ -43,13 +68,17 @@ protected override void Process (TypeDefinition type) // the linker. if (Configuration.DerivedLinkContext.App.Optimizations.RegisterProtocols != true) - return; + return modified; if (!type.IsBeforeFieldInit && type.IsInterface && type.HasMethods) { var cctor = type.GetTypeConstructor (); - if (cctor is not null && cctor.IsBindingImplOptimizableCode (LinkContext)) + if (cctor is not null && cctor.IsBindingImplOptimizableCode (Configuration.DerivedLinkContext) && type.IsBeforeFieldInit == false) { type.IsBeforeFieldInit = true; + modified = true; + } } + + return modified; } } } diff --git a/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs b/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs index 3d0f5feb9281..fafe7d80ae18 100644 --- a/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs @@ -22,7 +22,7 @@ public class TrimmableRegistrarStep : ConfigurationAwareStep { protected override int ErrorCode { get; } = 2470; AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } - List addedAssemblies = new List (); + List<(string Path, AssemblyDefinition Assembly, string? OriginatingAssembly)> addedAssemblies = new (); List exceptions = new List (); void AddException (Exception exception) @@ -48,6 +48,7 @@ AssemblyDefinition CreateTypeMapRootAssembly (ModuleParameters moduleParameters, // .NET 10 doesn't support a separate root type map assembly, so we have to add these attributes to the entry assembly instead. var useEntryAssemblyAsRootTypeMapAssembly = App.TargetFramework.Version.Major <= 10; + var createdRootTypeMapAssemblyPath = Path.Combine (App.TypeMapOutputDirectory, App.TypeMapAssemblyName + ".dll"); if (useEntryAssemblyAsRootTypeMapAssembly) { rootTypeMapAssembly = Configuration.EntryAssembly; @@ -55,7 +56,7 @@ AssemblyDefinition CreateTypeMapRootAssembly (ModuleParameters moduleParameters, var rootTypeMapAssemblyName = new AssemblyNameDefinition (App.TypeMapAssemblyName, new Version (1, 0, 0, 0)); rootTypeMapAssembly = AssemblyDefinition.CreateAssembly (rootTypeMapAssemblyName, rootTypeMapAssemblyName.Name, moduleParameters); Annotations.SetAction (rootTypeMapAssembly, AssemblyAction.Link); - addedAssemblies.Add (rootTypeMapAssembly); + addedAssemblies.Add ((createdRootTypeMapAssemblyPath, rootTypeMapAssembly, Configuration.PlatformAssembly)); // We're running from inside the linker, but the TypeMapEntryAssembly property can only be set using a command-line // argument, so we need to cheat a bit here and use reflection to set it. This will go away once we're not running @@ -110,7 +111,7 @@ AssemblyDefinition CreateTypeMapRootAssembly (ModuleParameters moduleParameters, // We write the assembly here even if it hasn't changed, because otherwise we'll just end up re-creating // it again during the next incremental build. if (!useEntryAssemblyAsRootTypeMapAssembly) { - rootTypeMapAssembly.Write (Path.Combine (App.TypeMapOutputDirectory, rootTypeMapAssembly.Name.Name + ".dll")); + rootTypeMapAssembly.Write (createdRootTypeMapAssemblyPath); } return rootTypeMapAssembly; } @@ -239,9 +240,10 @@ void addPostAction (AssemblyDefinition assembly, Action acti var typeMapAssemblyName = new AssemblyNameDefinition ("_" + assembly.Name.Name + ".TypeMap", new Version (1, 0, 0, 0)); var typeMapAssembly = AssemblyDefinition.CreateAssembly (typeMapAssemblyName, typeMapAssemblyName.Name, assemblyParameters); + var typeMapAssemblyPath = Path.Combine (App.TypeMapOutputDirectory, typeMapAssembly.Name.Name + ".dll"); var existingAction = Annotations.GetAction (assembly); Annotations.SetAction (typeMapAssembly, existingAction); - addedAssemblies.Add (typeMapAssembly); + addedAssemblies.Add ((typeMapAssemblyPath, typeMapAssembly, assembly.MainModule.FileName)); var accessesAssemblies = new HashSet (); accessesAssemblies.Add (assembly); @@ -591,7 +593,7 @@ void addPostAction (AssemblyDefinition assembly, Action acti // We write the assembly here even if it hasn't changed, because otherwise we'll just end up re-creating // it again during the next incremental build. - typeMapAssembly.Write (Path.Combine (App.TypeMapOutputDirectory, typeMapAssembly.Name.Name + ".dll")); + typeMapAssembly.Write (typeMapAssemblyPath); } foreach (var kvp in postActionsByAssembly) { @@ -604,13 +606,17 @@ void addPostAction (AssemblyDefinition assembly, Action acti abr.ClearCurrentAssembly (); } +#if ASSEMBLY_PREPARER + Configuration.AddedAssemblies.AddRange (addedAssemblies); +#else // Since we're running inside the trimmer, we need to make sure the trimmer knows about the assemblies we've created. // This will go away once we're running outside of the trimmer. var managedAssemblyToLinkItems = new List (); var resolver = abr.PlatformAssembly.MainModule.AssemblyResolver; var getAssembly = resolver.GetType ().GetMethod ("GetAssembly", new Type [] { typeof (string) })!; var cacheAssembly = resolver.GetType ().GetMethod ("CacheAssembly", new Type [] { typeof (AssemblyDefinition) })!; - foreach (var asm in addedAssemblies) { + foreach (var aa in addedAssemblies) { + var asm = aa.Assembly; var fn = Path.Combine (App.TypeMapOutputDirectory, asm.Name.Name + ".dll"); var asmDef = (AssemblyDefinition) getAssembly.Invoke (resolver, [fn])!; cacheAssembly.Invoke (resolver, [asmDef]); @@ -624,6 +630,7 @@ void addPostAction (AssemblyDefinition assembly, Action acti } Configuration.WriteOutputForMSBuild ("ManagedAssemblyToLink", managedAssemblyToLinkItems); +#endif // Report back any exceptions that occurred during the processing. exceptions = this.exceptions; diff --git a/tools/linker/CoreTypeMapStep.cs b/tools/linker/CoreTypeMapStep.cs index c180cf3b43fc..e73b6b9b56b9 100644 --- a/tools/linker/CoreTypeMapStep.cs +++ b/tools/linker/CoreTypeMapStep.cs @@ -168,14 +168,14 @@ bool IsWrapperType (TypeDefinition type) // Cache the results of the IsCIFilter check in a dictionary. It makes this method slightly faster // (total time spent in IsCIFilter when linking monotouch-test went from 11 ms to 3ms). Dictionary ci_filter_types = new Dictionary (); - bool IsCIFilter (TypeReference type) + bool IsCIFilter (TypeReference? type) { if (type is null) return false; bool rv; if (!ci_filter_types.TryGetValue (type, out rv)) { - rv = type.Is (Namespaces.CoreImage, "CIFilter") || IsCIFilter (Context.Resolve (type).BaseType); + rv = type.Is (Namespaces.CoreImage, "CIFilter") || IsCIFilter (Context.Resolve (type)?.BaseType); ci_filter_types [type] = rv; } return rv; @@ -198,7 +198,7 @@ void SetIsDirectBindingValue (TypeDefinition type) var base_type = Context.Resolve (type.BaseType); while (base_type is not null && IsNSObject (base_type)) { isdirectbinding_value [base_type] = null; - base_type = Context.Resolve (base_type.BaseType); + base_type = LinkContext.Resolve (base_type.BaseType); } return; } diff --git a/tools/linker/MarkNSObjects.cs b/tools/linker/MarkNSObjects.cs index 02502154fd10..2dc41e1e7539 100644 --- a/tools/linker/MarkNSObjects.cs +++ b/tools/linker/MarkNSObjects.cs @@ -41,7 +41,7 @@ #nullable enable namespace Xamarin.Linker.Steps { - +#if !ASSEMBLY_PREPARER public class MarkNSObjects : ExceptionalSubStep, IMarkNSObjects { protected override string Name { get; } = "MarkNSObjects"; protected override int ErrorCode { get; } = 2080; @@ -86,6 +86,7 @@ public bool PreserveMethod (TypeDefinition onType, MethodDefinition method) return true; } } +#endif public interface IMarkNSObjects { bool PreserveType (TypeDefinition type, bool allMembers); diff --git a/tools/linker/MobileExtensions.cs b/tools/linker/MobileExtensions.cs index cba0f7f9997e..d1458fdbf60c 100644 --- a/tools/linker/MobileExtensions.cs +++ b/tools/linker/MobileExtensions.cs @@ -44,7 +44,7 @@ public static bool HasCustomAttribute (this ICustomAttributeProvider? provider, if (provider?.HasCustomAttribute (@namespace, name) == true) return true; - return context?.GetCustomAttributes (provider, @namespace, name)?.Count > 0; + return context?.GetCustomAttributes (provider, @namespace, name)?.Any () == true; } public static bool HasCustomAttribute (this ICustomAttributeProvider? provider, string @namespace, string name) diff --git a/tools/linker/MonoTouch.Tuner/Extensions.cs b/tools/linker/MonoTouch.Tuner/Extensions.cs index e1eac2745c0a..d0764f92518d 100644 --- a/tools/linker/MonoTouch.Tuner/Extensions.cs +++ b/tools/linker/MonoTouch.Tuner/Extensions.cs @@ -7,6 +7,10 @@ using Xamarin.Tuner; +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER +using LinkContext = Xamarin.Bundler.DotNetLinkContext; +#endif + namespace MonoTouch.Tuner { public static class Extensions { diff --git a/tools/linker/ObjCExtensions.cs b/tools/linker/ObjCExtensions.cs index 5482602a5bdf..3d8ab04c91c0 100644 --- a/tools/linker/ObjCExtensions.cs +++ b/tools/linker/ObjCExtensions.cs @@ -117,8 +117,10 @@ public static bool IsNSObject (this TypeDefinition? type, DerivedLinkContext? li return link_context.CachedIsNSObject.Contains (type); return type.Inherits (Namespaces.Foundation, "NSObject" -#if !LEGACY_TOOLS - , link_context.LinkerConfiguration.Context +#if ASSEMBLY_PREPARER + , link_context!.Configuration.MetadataResolver +#elif !LEGACY_TOOLS + , link_context!.LinkerConfiguration.Context #endif ); } diff --git a/tools/linker/OptimizeGeneratedCode.cs b/tools/linker/OptimizeGeneratedCode.cs index 46af059779e8..43bd328b189d 100644 --- a/tools/linker/OptimizeGeneratedCode.cs +++ b/tools/linker/OptimizeGeneratedCode.cs @@ -140,10 +140,13 @@ internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, case Code.Conv_I1: case Code.Conv_I2: case Code.Conv_I4: + case Code.Conv_I8: case Code.Conv_U: case Code.Sizeof: case Code.Ldfld: case Code.Ldflda: + case Code.Mul: + case Code.And: return null; // just to not hit the CWL below #endif default: @@ -1076,7 +1079,7 @@ static bool ProcessRuntimeArch (OptimizeGeneratedCodeData data, MethodDefinition static MethodReference GetBlockSetupImpl (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) { if (data.SetupBlockImplDefinition is null) { - var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); + var type = data.LinkContext.GetProductAssembly ().MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); foreach (var method in type.Methods) { if (method.Name != "SetupBlockImpl") continue; @@ -1093,7 +1096,7 @@ static MethodReference GetBlockSetupImpl (OptimizeGeneratedCodeData data, Method static MethodReference GetBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) { if (data.BlockCtorDefinition is null) { - var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); + var type = data.LinkContext.GetProductAssembly ().MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); foreach (var method in type.Methods) { if (!method.IsConstructor) continue; diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index 7c3f1861b4ab..750512fbca59 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -3353,6 +3353,15 @@ public static string MX1503 { } } + /// + /// Looks up a localized string similar to Can not find the product assembly '{0}' in the list of loaded assemblies.. + /// + public static string MX1504 { + get { + return ResourceManager.GetString("MX1504", resourceCulture); + } + } + /// /// Looks up a localized string similar to Not a Mach-O dynamic library (unknown header '0x{0}'): {1}.. /// diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index cc89125d1ff1..99beb38ac960 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -851,6 +851,10 @@ One or more reference(s) to type '{0}' still exists inside '{1}' after linking + + Can not find the product assembly '{0}' in the list of loaded assemblies. + + Not a Mach-O dynamic library (unknown header '0x{0}'): {1}. diff --git a/tools/mtouch/mtouch.cs b/tools/mtouch/mtouch.cs index 42ae6901f1a0..2e61df769f2f 100644 --- a/tools/mtouch/mtouch.cs +++ b/tools/mtouch/mtouch.cs @@ -11,6 +11,7 @@ using Mono.Options; using Xamarin.Linker; +using Xamarin.Utils; namespace Xamarin.Bundler { public partial class Driver { @@ -18,7 +19,7 @@ public partial class Driver { static int Main2 (string [] args) { - var app = new Application (new LinkerConfiguration ()); + var app = new Application (); var os = new OptionSet (); ParseOptions (app, os, args); diff --git a/tools/tools.slnx b/tools/tools.slnx index 140d0af4708c..d4fd9bbf97a3 100644 --- a/tools/tools.slnx +++ b/tools/tools.slnx @@ -1,4 +1,5 @@ + From 093554411d8423570a01acbafbbee7ff0c3fe867 Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Fri, 5 Jun 2026 08:17:55 +0000 Subject: [PATCH 94/97] Auto-format source code --- msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs index 99003df534c4..e50cee0ce94a 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs @@ -245,7 +245,7 @@ internal static bool ExecuteRemotely (T task) where T : Task, IHasSessionId } #if NET - internal static bool ExecuteRemotely (T task, [NotNullWhen (true)] out TaskRunner? taskRunner, Action? preprocessTaskRunner = null) where T: Task, IHasSessionId + internal static bool ExecuteRemotely (T task, [NotNullWhen (true)] out TaskRunner? taskRunner, Action? preprocessTaskRunner = null) where T : Task, IHasSessionId #else internal static bool ExecuteRemotely (T task, out TaskRunner taskRunner, Action? preprocessTaskRunner = null) where T : Task, IHasSessionId #endif From 8fc4a6fe34a308282b7a147a59b7304fd02e3b37 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 16:13:02 +0200 Subject: [PATCH 95/97] Fix build. --- tools/dotnet-linker/LinkerConfiguration.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 3d0b7fe3b6cb..162e8fd705dc 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -285,8 +285,8 @@ Configurator GetConfigurator (string linker_file) }) )}, { "DylibToConvertToFramework", ( - new LoadValue ((key, value) => Application.DylibsToConvertToFrameworks.Add (value), - new SaveValue ((key, storage) => storage.AddRange (Application.DylibsToConvertToFrameworks.Select (v => $"{key}={v}"))), + new LoadValue ((key, value) => Application.DylibsToConvertToFrameworks.Add (value)), + new SaveValue ((key, storage) => storage.AddRange (Application.DylibsToConvertToFrameworks.Select (v => $"{key}={v}"))) )}, { "EnableSGenConc", ( new LoadValue ((key, value) => Application.EnableSGenConc = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), @@ -443,7 +443,7 @@ Configurator GetConfigurator (string linker_file) new SaveValue ((key, storage) => saveNullableBool (key, Application.PublishReadyToRun, storage)) )}, { "PublishReadyToRunContainerFormat", ( - new LoadValue ((key, value) => Application.PublishReadyToRunContainerFormat = value, + new LoadValue ((key, value) => Application.PublishReadyToRunContainerFormat = value), new SaveValue ((key, storage) => saveNonEmpty (key, Application.PublishReadyToRunContainerFormat, storage)) )}, { "ReferenceNativeSymbol", ( From 6f74184fb331399d2a7c7c0acf230fd3ca3d737c Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 16:31:05 +0200 Subject: [PATCH 96/97] [assembly-preparer] Improve parallel make. --- tools/assembly-preparer/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/assembly-preparer/Makefile b/tools/assembly-preparer/Makefile index 587a1d6116f5..8eb3542bd40e 100644 --- a/tools/assembly-preparer/Makefile +++ b/tools/assembly-preparer/Makefile @@ -19,7 +19,7 @@ $(ASSEMBLIES): .build-stamp all-local:: .build-stamp -run-tests: +run-tests: .build-stamp $(Q_BUILD) $(DOTNET) test $(TOP)/tests/assembly-preparer/assembly-preparer-tests.csproj --logger "console;verbosity=detailed" $(DOTNET_BUILD_VERBOSITY) -bl:$@.binlog generated-files: $(abspath ../common/SdkVersions.cs) $(abspath ../common/ProductConstants.cs) From 64c5863bcb29c556629c88badc783bb9f408b52e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 17:24:24 +0200 Subject: [PATCH 97/97] Fix build problem. --- tests/xharness/Jenkins/TestVariationsFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/xharness/Jenkins/TestVariationsFactory.cs b/tests/xharness/Jenkins/TestVariationsFactory.cs index 40cb84a3f97e..59bc6cf3e427 100644 --- a/tests/xharness/Jenkins/TestVariationsFactory.cs +++ b/tests/xharness/Jenkins/TestVariationsFactory.cs @@ -63,7 +63,7 @@ IEnumerable GetTestData (RunTestTask test) yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Managed Static Registrar)", TestVariation = "coreclr|prepare-assemblies|managed-static-registrar", Ignored = ignore }; yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; } - if (supports_monovm) { + if (supports_mono) { yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Dynamic Registrar)", TestVariation = "monovm|prepare-assemblies|dynamic-registrar", Ignored = ignore }; yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Managed Static Registrar)", TestVariation = "monovm|prepare-assemblies|managed-static-registrar", Ignored = ignore }; if (jenkins.Harness.DotNetVersion.Major >= 11) { // on Mono, the trimmable static registrar only works in .NET 11 @@ -76,7 +76,7 @@ IEnumerable GetTestData (RunTestTask test) // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which only works on CoreCLR in .NET 10 yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; } - if (supports_monovm && jenkins.Harness.DotNetVersion.Major >= 11) { + if (supports_mono && jenkins.Harness.DotNetVersion.Major >= 11) { // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which, on Mono, only works in .NET 11 yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Trimmable Static Registrar)", TestVariation = "monovm|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; } @@ -86,7 +86,7 @@ IEnumerable GetTestData (RunTestTask test) // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which only works on CoreCLR in .NET 10 yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; } - if (supports_monovm && jenkins.Harness.DotNetVersion.Major >= 11) { + if (supports_mono && jenkins.Harness.DotNetVersion.Major >= 11) { // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which, on Mono, only works in .NET 11 yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Trimmable Static Registrar)", TestVariation = "monovm|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; }