diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index bcbd2bb6e4..8f6442c3d5 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -49,6 +49,8 @@ jobs: run: dotnet build --no-restore --verbosity=normal - name: Test run: ./.ci/gha-run-tests.ps1 + - name: AotTest + run: ./projects/AotCompatibility.TestApp/test-aot-compatibility.ps1 build: name: build/test on ubuntu-latest diff --git a/RabbitMQDotNetClient.sln b/RabbitMQDotNetClient.sln index 1b00ba7763..fc552cb7e3 100644 --- a/RabbitMQDotNetClient.sln +++ b/RabbitMQDotNetClient.sln @@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestApplications", "TestApp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OAuth2", "projects\TestApplications\OAuth2\OAuth2.csproj", "{07E203AC-9E4B-4BED-9445-E2B45E10E412}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AotCompatibility.TestApp", "projects\AotCompatibility.TestApp\AotCompatibility.TestApp.csproj", "{0B79BD0B-B35D-4626-ABCC-023B6726A531}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,6 +48,10 @@ Global {07E203AC-9E4B-4BED-9445-E2B45E10E412}.Debug|Any CPU.Build.0 = Debug|Any CPU {07E203AC-9E4B-4BED-9445-E2B45E10E412}.Release|Any CPU.ActiveCfg = Release|Any CPU {07E203AC-9E4B-4BED-9445-E2B45E10E412}.Release|Any CPU.Build.0 = Release|Any CPU + {0B79BD0B-B35D-4626-ABCC-023B6726A531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B79BD0B-B35D-4626-ABCC-023B6726A531}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B79BD0B-B35D-4626-ABCC-023B6726A531}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B79BD0B-B35D-4626-ABCC-023B6726A531}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/projects/AotCompatibility.TestApp/AotCompatibility.TestApp.csproj b/projects/AotCompatibility.TestApp/AotCompatibility.TestApp.csproj new file mode 100644 index 0000000000..5109eeebed --- /dev/null +++ b/projects/AotCompatibility.TestApp/AotCompatibility.TestApp.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + + true + false + + + + + + + + + diff --git a/projects/AotCompatibility.TestApp/Program.cs b/projects/AotCompatibility.TestApp/Program.cs new file mode 100644 index 0000000000..7109a11906 --- /dev/null +++ b/projects/AotCompatibility.TestApp/Program.cs @@ -0,0 +1,4 @@ +// publishing this app ensures all of the code in the referenced +// assemblies are trim/AOT compatible. + +Console.WriteLine("Hello, World!"); \ No newline at end of file diff --git a/projects/AotCompatibility.TestApp/test-aot-compatibility.ps1 b/projects/AotCompatibility.TestApp/test-aot-compatibility.ps1 new file mode 100644 index 0000000000..6ac50fda95 --- /dev/null +++ b/projects/AotCompatibility.TestApp/test-aot-compatibility.ps1 @@ -0,0 +1,59 @@ +$DebugPreference = "Continue" +$ErrorActionPreference = 'Stop' +# Set-PSDebug -Strict -Trace 1 +Set-PSDebug -Off +Set-StrictMode -Version 'Latest' -ErrorAction 'Stop' -Verbose + +New-Variable -Name rootDirectory -Option Constant -Value $PSScriptRoot +Write-Host "[INFO] rootDirectory: $rootDirectory" + +$runtime = $IsWindows ? "win-x64" : ($IsMacOS ? "macos-x64" : "linux-x64") +$app = $IsWindows ? "./AotCompatibility.TestApp.exe" : "./AotCompatibility.TestApp" + +$publishOutput = dotnet publish --runtime=$runtime $rootDirectory/AotCompatibility.TestApp.csproj -nodeReuse:false '/p:UseSharedCompilation=false' '/p:Configuration=Release' + +$actualWarningCount = 0 + +foreach ($line in $($publishOutput -split "`r`n")) +{ + if (($line -like "*analysis warning IL*") -or ($line -like "*analysis error IL*")) + { + Write-Host $line + $actualWarningCount += 1 + } +} + +Write-Host "Actual warning count is:", $actualWarningCount +$expectedWarningCount = 0 + +if ($LastExitCode -ne 0) +{ + Write-Error -ErrorAction Continue -Message "[ERROR] error while publishing AotCompatibility Test App, LastExitCode is $LastExitCode" + Write-Error -ErrorAction Continue -Message $publishOutput +} + +Push-Location "$rootDirectory/bin/Release/net6.0/$runtime" +try +{ + Write-Host "[INFO] executing: $app" + $app + Write-Host "[INFO] finished executing test app" + + if ($LastExitCode -ne 0) + { + Write-Error -ErrorAction Continue -Message "[ERROR] there was an error while executing AotCompatibility Test App. LastExitCode is: $LastExitCode" + } +} +finally +{ + Pop-Location +} + +$exitCode = 0 +if ($actualWarningCount -ne $expectedWarningCount) +{ + $exitCode = 1 + Write-Error -ErrorAction Continue -Message "Actual warning count: $actualWarningCount is not as expected, which is: $expectedWarningCount" +} + +Exit $exitCode diff --git a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj index 7851d56912..eb4266865a 100755 --- a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj +++ b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj @@ -5,8 +5,6 @@ true $(NoWarn);CS1591 true - true - true RabbitMQ Client Library for .NET VMware VMware, Inc. or its affiliates. diff --git a/projects/RabbitMQ.Client/client/api/ICredentialsRefresher.cs b/projects/RabbitMQ.Client/client/api/ICredentialsRefresher.cs index c3fa966519..696ecda39d 100644 --- a/projects/RabbitMQ.Client/client/api/ICredentialsRefresher.cs +++ b/projects/RabbitMQ.Client/client/api/ICredentialsRefresher.cs @@ -31,6 +31,7 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; using System.Timers; @@ -54,16 +55,12 @@ public class TimerBasedCredentialRefresherEventSource : EventSource [Event(2)] public void Unregistered(string name) => WriteEvent(2, "UnRegistered", name); [Event(3)] -#if NET6_0_OR_GREATER [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe")] -#endif public void ScheduledTimer(string name, double interval) => WriteEvent(3, "ScheduledTimer", name, interval); [Event(4)] public void TriggeredTimer(string name) => WriteEvent(4, "TriggeredTimer", name); [Event(5)] -#if NET6_0_OR_GREATER [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe")] -#endif public void RefreshedCredentials(string name, bool succesfully) => WriteEvent(5, "RefreshedCredentials", name, succesfully); [Event(6)] public void AlreadyRegistered(string name) => WriteEvent(6, "AlreadyRegistered", name); diff --git a/projects/RabbitMQ.Client/client/logging/RabbitMqClientEventSource.cs b/projects/RabbitMQ.Client/client/logging/RabbitMqClientEventSource.cs index 4481abcca1..38460b46bd 100644 --- a/projects/RabbitMQ.Client/client/logging/RabbitMqClientEventSource.cs +++ b/projects/RabbitMQ.Client/client/logging/RabbitMqClientEventSource.cs @@ -30,6 +30,7 @@ //--------------------------------------------------------------------------- using System; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; namespace RabbitMQ.Client.Logging @@ -66,19 +67,14 @@ public void Warn(string message) public void Error(string message, RabbitMqExceptionDetail ex) { if (IsEnabled()) - { -#if NET6_0_OR_GREATER WriteExceptionEvent(message, ex); + } - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The properties are preserved with the DynamicallyAccessedMembers attribute.")] - void WriteExceptionEvent<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string message, T ex) - { - WriteEvent(3, message, ex); - } -#else - WriteEvent(3, message, ex); -#endif - } + [NonEvent] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The properties are preserved with the DynamicallyAccessedMembers attribute.")] + private void WriteExceptionEvent<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string message, T ex) + { + WriteEvent(3, message, ex); } [NonEvent] diff --git a/projects/RabbitMQ.Client/util/TrimmingAttributes.cs b/projects/RabbitMQ.Client/util/TrimmingAttributes.cs new file mode 100644 index 0000000000..a7aaeebc94 --- /dev/null +++ b/projects/RabbitMQ.Client/util/TrimmingAttributes.cs @@ -0,0 +1,253 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +// taken from https://devblogs.microsoft.com/dotnet/creating-aot-compatible-libraries/#approach-2-define-the-attributes-internally + +namespace System.Diagnostics.CodeAnalysis +{ +#if !NET5_0_OR_GREATER + /// + /// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a + /// single code artifact. + /// + /// + /// is different than + /// in that it doesn't have a + /// . So it is always preserved in the compiled assembly. + /// + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] + internal sealed class UnconditionalSuppressMessageAttribute : Attribute + { + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category for the attribute. + /// The identifier of the analysis rule the attribute applies to. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + /// + /// The property describes the tool or tool analysis category + /// for which a message suppression attribute applies. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + /// + /// Concatenated together, the and + /// properties form a unique check identifier. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + /// + /// The Scope property is an optional argument that specifies the metadata scope for which + /// the attribute is relevant. + /// + public string Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + /// + /// The property is an optional argument identifying the analysis target + /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". + /// Because it is fully qualified, it can be long, particularly for targets such as parameters. + /// The analysis tool user interface should be capable of automatically formatting the parameter. + /// + public string Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + /// + /// The property is an optional argument that specifies additional + /// exclusion where the literal metadata target is not sufficiently precise. For example, + /// the cannot be applied within a method, + /// and it may be desirable to suppress a violation against a statement in the method that will + /// give a rule violation, but not against all statements in the method. + /// + public string MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string Justification { get; set; } + } + + /// + /// Indicates that certain members on a specified are accessed dynamically, + /// for example through . + /// + /// + /// This allows tools to understand which members are being accessed during the execution + /// of a program. + /// + /// This attribute is valid on members whose type is or . + /// + /// When this attribute is applied to a location of type , the assumption is + /// that the string represents a fully qualified type name. + /// + /// When this attribute is applied to a class, interface, or struct, the members specified + /// can be accessed dynamically on instances returned from calling + /// on instances of that class, interface, or struct. + /// + /// If the attribute is applied to a method it's treated as a special case and it implies + /// the attribute should be applied to the "this" parameter of the method. As such the attribute + /// should only be used on instance methods of types assignable to System.Type (or string, but no methods + /// will use it there). + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } + + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + /// + /// 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 members. + /// + All = ~None + } +#endif +} diff --git a/projects/RabbitMQ.Client/util/DebugUtil.cs b/projects/Unit/Helper/DebugUtil.cs similarity index 99% rename from projects/RabbitMQ.Client/util/DebugUtil.cs rename to projects/Unit/Helper/DebugUtil.cs index 3a6d50f57e..28f55fc677 100644 --- a/projects/RabbitMQ.Client/util/DebugUtil.cs +++ b/projects/Unit/Helper/DebugUtil.cs @@ -34,7 +34,7 @@ using System.IO; using System.Reflection; -namespace RabbitMQ.Util +namespace RabbitMQ.Client.Unit { ///Miscellaneous debugging and development utilities. /// diff --git a/projects/Unit/TestContentHeaderCodec.cs b/projects/Unit/TestContentHeaderCodec.cs index 7b6585c8e4..3655d5a301 100644 --- a/projects/Unit/TestContentHeaderCodec.cs +++ b/projects/Unit/TestContentHeaderCodec.cs @@ -34,7 +34,6 @@ using NUnit.Framework; using RabbitMQ.Client.Impl; -using RabbitMQ.Util; namespace RabbitMQ.Client.Unit { diff --git a/projects/Unit/TestMethodArgumentCodec.cs b/projects/Unit/TestMethodArgumentCodec.cs index a424199b4b..2eca16ecc1 100644 --- a/projects/Unit/TestMethodArgumentCodec.cs +++ b/projects/Unit/TestMethodArgumentCodec.cs @@ -36,7 +36,6 @@ using NUnit.Framework; using RabbitMQ.Client.Impl; -using RabbitMQ.Util; namespace RabbitMQ.Client.Unit { diff --git a/projects/Unit/WireFormattingFixture.cs b/projects/Unit/WireFormattingFixture.cs index dda5facae0..c290b7c2d3 100644 --- a/projects/Unit/WireFormattingFixture.cs +++ b/projects/Unit/WireFormattingFixture.cs @@ -30,12 +30,9 @@ //--------------------------------------------------------------------------- using System; -using System.IO; using NUnit.Framework; -using RabbitMQ.Util; - namespace RabbitMQ.Client.Unit { class WireFormattingFixture