diff --git a/.github/workflows/net-workflow.yml b/.github/workflows/net-workflow.yml index 8e8b232..a03cbe4 100644 --- a/.github/workflows/net-workflow.yml +++ b/.github/workflows/net-workflow.yml @@ -13,14 +13,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 3.1.x - 8.0.x + 8.0.x + 10.0.x - name: Restore dependencies run: dotnet restore @@ -30,3 +31,21 @@ jobs: - name: Test run: dotnet test --no-build --verbosity normal + + aot-smoke: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET 10 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Publish AOT smoke (win-x64) + run: dotnet publish tests/NetArchTest.AotSmoke -c Release -r win-x64 + + - name: Run AOT smoke exe + run: tests/NetArchTest.AotSmoke/bin/Release/net10.0/win-x64/publish/NetArchTest.AotSmoke.exe diff --git a/NetArchTest.eNhancedEdition.sln b/NetArchTest.eNhancedEdition.sln index 0b409e9..5891a24 100644 --- a/NetArchTest.eNhancedEdition.sln +++ b/NetArchTest.eNhancedEdition.sln @@ -43,64 +43,216 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp.ModuleOmega", "sa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp.SharedKernel", "samples\SampleApp.SharedKernel\SampleApp.SharedKernel.csproj", "{674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetArchTest.AotSmoke.Fixture", "tests\NetArchTest.AotSmoke.Fixture\NetArchTest.AotSmoke.Fixture.csproj", "{885BB5D9-0E9E-4C85-89C2-211D26415525}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetArchTest.AotSmoke", "tests\NetArchTest.AotSmoke\NetArchTest.AotSmoke.csproj", "{6DD6F733-3B11-4393-B3E4-634EB081F28C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sources", "sources", "{C16B2082-E6A6-C480-36D0-FC08AA18D453}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetArchTest.Benchmarks", "tests\NetArchTest.Benchmarks\NetArchTest.Benchmarks.csproj", "{EAB0C316-E83A-47FB-B6CF-C12C098AECF6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Debug|x64.ActiveCfg = Debug|Any CPU + {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Debug|x64.Build.0 = Debug|Any CPU + {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Debug|x86.ActiveCfg = Debug|Any CPU + {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Debug|x86.Build.0 = Debug|Any CPU {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Release|Any CPU.ActiveCfg = Release|Any CPU {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Release|Any CPU.Build.0 = Release|Any CPU + {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Release|x64.ActiveCfg = Release|Any CPU + {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Release|x64.Build.0 = Release|Any CPU + {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Release|x86.ActiveCfg = Release|Any CPU + {2FDD6DBD-F203-4EC9-9F9B-6771713CC353}.Release|x86.Build.0 = Release|Any CPU {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Debug|x64.Build.0 = Debug|Any CPU + {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Debug|x86.ActiveCfg = Debug|Any CPU + {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Debug|x86.Build.0 = Debug|Any CPU {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Release|Any CPU.ActiveCfg = Release|Any CPU {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Release|Any CPU.Build.0 = Release|Any CPU + {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Release|x64.ActiveCfg = Release|Any CPU + {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Release|x64.Build.0 = Release|Any CPU + {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Release|x86.ActiveCfg = Release|Any CPU + {D56F6954-7CCA-41D6-BA81-850F0C81FE3A}.Release|x86.Build.0 = Release|Any CPU {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Debug|x64.ActiveCfg = Debug|Any CPU + {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Debug|x64.Build.0 = Debug|Any CPU + {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Debug|x86.ActiveCfg = Debug|Any CPU + {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Debug|x86.Build.0 = Debug|Any CPU {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Release|Any CPU.Build.0 = Release|Any CPU + {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Release|x64.ActiveCfg = Release|Any CPU + {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Release|x64.Build.0 = Release|Any CPU + {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Release|x86.ActiveCfg = Release|Any CPU + {D91C182D-DC97-4F9B-AFFE-8C7A62501DA6}.Release|x86.Build.0 = Release|Any CPU {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Debug|x64.Build.0 = Debug|Any CPU + {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Debug|x86.Build.0 = Debug|Any CPU {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Release|Any CPU.Build.0 = Release|Any CPU + {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Release|x64.ActiveCfg = Release|Any CPU + {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Release|x64.Build.0 = Release|Any CPU + {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Release|x86.ActiveCfg = Release|Any CPU + {4C5B170E-CAA1-421E-9FA5-9C2D6BEDB27C}.Release|x86.Build.0 = Release|Any CPU {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Debug|x64.ActiveCfg = Debug|Any CPU + {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Debug|x64.Build.0 = Debug|Any CPU + {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Debug|x86.ActiveCfg = Debug|Any CPU + {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Debug|x86.Build.0 = Debug|Any CPU {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Release|Any CPU.Build.0 = Release|Any CPU + {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Release|x64.ActiveCfg = Release|Any CPU + {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Release|x64.Build.0 = Release|Any CPU + {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Release|x86.ActiveCfg = Release|Any CPU + {7123A8AE-678D-4D14-82E5-EB5607ABDC4A}.Release|x86.Build.0 = Release|Any CPU {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Debug|x64.ActiveCfg = Debug|Any CPU + {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Debug|x64.Build.0 = Debug|Any CPU + {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Debug|x86.ActiveCfg = Debug|Any CPU + {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Debug|x86.Build.0 = Debug|Any CPU {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Release|Any CPU.ActiveCfg = Release|Any CPU {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Release|Any CPU.Build.0 = Release|Any CPU + {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Release|x64.ActiveCfg = Release|Any CPU + {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Release|x64.Build.0 = Release|Any CPU + {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Release|x86.ActiveCfg = Release|Any CPU + {70B787F2-8B79-4AA5-8C73-26682A605B6B}.Release|x86.Build.0 = Release|Any CPU {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Debug|x64.ActiveCfg = Debug|Any CPU + {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Debug|x64.Build.0 = Debug|Any CPU + {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Debug|x86.ActiveCfg = Debug|Any CPU + {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Debug|x86.Build.0 = Debug|Any CPU {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Release|Any CPU.Build.0 = Release|Any CPU + {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Release|x64.ActiveCfg = Release|Any CPU + {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Release|x64.Build.0 = Release|Any CPU + {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Release|x86.ActiveCfg = Release|Any CPU + {28692D43-3E08-43E4-BCBB-5940B9F741C6}.Release|x86.Build.0 = Release|Any CPU {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Debug|x64.ActiveCfg = Debug|Any CPU + {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Debug|x64.Build.0 = Debug|Any CPU + {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Debug|x86.ActiveCfg = Debug|Any CPU + {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Debug|x86.Build.0 = Debug|Any CPU {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Release|Any CPU.ActiveCfg = Release|Any CPU {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Release|Any CPU.Build.0 = Release|Any CPU + {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Release|x64.ActiveCfg = Release|Any CPU + {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Release|x64.Build.0 = Release|Any CPU + {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Release|x86.ActiveCfg = Release|Any CPU + {C86E2D57-4566-4713-88DF-0AA265B0EDAC}.Release|x86.Build.0 = Release|Any CPU {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Debug|x64.ActiveCfg = Debug|Any CPU + {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Debug|x64.Build.0 = Debug|Any CPU + {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Debug|x86.ActiveCfg = Debug|Any CPU + {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Debug|x86.Build.0 = Debug|Any CPU {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Release|Any CPU.Build.0 = Release|Any CPU + {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Release|x64.ActiveCfg = Release|Any CPU + {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Release|x64.Build.0 = Release|Any CPU + {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Release|x86.ActiveCfg = Release|Any CPU + {4E5A47CB-2F0B-4F7B-B898-D8BD2B8797C2}.Release|x86.Build.0 = Release|Any CPU {F54503C5-B053-45EF-967C-B625B876ED88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F54503C5-B053-45EF-967C-B625B876ED88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F54503C5-B053-45EF-967C-B625B876ED88}.Debug|x64.ActiveCfg = Debug|Any CPU + {F54503C5-B053-45EF-967C-B625B876ED88}.Debug|x64.Build.0 = Debug|Any CPU + {F54503C5-B053-45EF-967C-B625B876ED88}.Debug|x86.ActiveCfg = Debug|Any CPU + {F54503C5-B053-45EF-967C-B625B876ED88}.Debug|x86.Build.0 = Debug|Any CPU {F54503C5-B053-45EF-967C-B625B876ED88}.Release|Any CPU.ActiveCfg = Release|Any CPU {F54503C5-B053-45EF-967C-B625B876ED88}.Release|Any CPU.Build.0 = Release|Any CPU + {F54503C5-B053-45EF-967C-B625B876ED88}.Release|x64.ActiveCfg = Release|Any CPU + {F54503C5-B053-45EF-967C-B625B876ED88}.Release|x64.Build.0 = Release|Any CPU + {F54503C5-B053-45EF-967C-B625B876ED88}.Release|x86.ActiveCfg = Release|Any CPU + {F54503C5-B053-45EF-967C-B625B876ED88}.Release|x86.Build.0 = Release|Any CPU {D46D6289-81C2-41D1-B252-0B88BF825660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D46D6289-81C2-41D1-B252-0B88BF825660}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D46D6289-81C2-41D1-B252-0B88BF825660}.Debug|x64.ActiveCfg = Debug|Any CPU + {D46D6289-81C2-41D1-B252-0B88BF825660}.Debug|x64.Build.0 = Debug|Any CPU + {D46D6289-81C2-41D1-B252-0B88BF825660}.Debug|x86.ActiveCfg = Debug|Any CPU + {D46D6289-81C2-41D1-B252-0B88BF825660}.Debug|x86.Build.0 = Debug|Any CPU {D46D6289-81C2-41D1-B252-0B88BF825660}.Release|Any CPU.ActiveCfg = Release|Any CPU {D46D6289-81C2-41D1-B252-0B88BF825660}.Release|Any CPU.Build.0 = Release|Any CPU + {D46D6289-81C2-41D1-B252-0B88BF825660}.Release|x64.ActiveCfg = Release|Any CPU + {D46D6289-81C2-41D1-B252-0B88BF825660}.Release|x64.Build.0 = Release|Any CPU + {D46D6289-81C2-41D1-B252-0B88BF825660}.Release|x86.ActiveCfg = Release|Any CPU + {D46D6289-81C2-41D1-B252-0B88BF825660}.Release|x86.Build.0 = Release|Any CPU {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Debug|x64.ActiveCfg = Debug|Any CPU + {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Debug|x64.Build.0 = Debug|Any CPU + {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Debug|x86.ActiveCfg = Debug|Any CPU + {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Debug|x86.Build.0 = Debug|Any CPU {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Release|Any CPU.Build.0 = Release|Any CPU + {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Release|x64.ActiveCfg = Release|Any CPU + {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Release|x64.Build.0 = Release|Any CPU + {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Release|x86.ActiveCfg = Release|Any CPU + {22600143-A90D-42B5-8FA0-9FEC0E962BCC}.Release|x86.Build.0 = Release|Any CPU {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Debug|x64.ActiveCfg = Debug|Any CPU + {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Debug|x64.Build.0 = Debug|Any CPU + {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Debug|x86.ActiveCfg = Debug|Any CPU + {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Debug|x86.Build.0 = Debug|Any CPU {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Release|Any CPU.Build.0 = Release|Any CPU + {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Release|x64.ActiveCfg = Release|Any CPU + {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Release|x64.Build.0 = Release|Any CPU + {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Release|x86.ActiveCfg = Release|Any CPU + {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D}.Release|x86.Build.0 = Release|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Debug|Any CPU.Build.0 = Debug|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Debug|x64.ActiveCfg = Debug|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Debug|x64.Build.0 = Debug|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Debug|x86.ActiveCfg = Debug|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Debug|x86.Build.0 = Debug|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Release|Any CPU.ActiveCfg = Release|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Release|Any CPU.Build.0 = Release|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Release|x64.ActiveCfg = Release|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Release|x64.Build.0 = Release|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Release|x86.ActiveCfg = Release|Any CPU + {885BB5D9-0E9E-4C85-89C2-211D26415525}.Release|x86.Build.0 = Release|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Debug|x64.ActiveCfg = Debug|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Debug|x64.Build.0 = Debug|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Debug|x86.ActiveCfg = Debug|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Debug|x86.Build.0 = Debug|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Release|Any CPU.Build.0 = Release|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Release|x64.ActiveCfg = Release|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Release|x64.Build.0 = Release|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Release|x86.ActiveCfg = Release|Any CPU + {6DD6F733-3B11-4393-B3E4-634EB081F28C}.Release|x86.Build.0 = Release|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Debug|x64.ActiveCfg = Debug|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Debug|x64.Build.0 = Debug|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Debug|x86.ActiveCfg = Debug|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Debug|x86.Build.0 = Debug|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Release|Any CPU.Build.0 = Release|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Release|x64.ActiveCfg = Release|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Release|x64.Build.0 = Release|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Release|x86.ActiveCfg = Release|Any CPU + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -119,6 +271,9 @@ Global {D46D6289-81C2-41D1-B252-0B88BF825660} = {FD532FD5-BB0C-4731-87A5-747FE1A32FBD} {22600143-A90D-42B5-8FA0-9FEC0E962BCC} = {FD532FD5-BB0C-4731-87A5-747FE1A32FBD} {674DBCB9-5F7E-45A1-9862-1CDA64C83B3D} = {FD532FD5-BB0C-4731-87A5-747FE1A32FBD} + {885BB5D9-0E9E-4C85-89C2-211D26415525} = {660FEA1B-C886-4B72-AD70-1D7DD248CD76} + {6DD6F733-3B11-4393-B3E4-634EB081F28C} = {660FEA1B-C886-4B72-AD70-1D7DD248CD76} + {EAB0C316-E83A-47FB-B6CF-C12C098AECF6} = {660FEA1B-C886-4B72-AD70-1D7DD248CD76} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4388262D-A716-4918-A629-6072E4F8B668} diff --git a/sources/NetArchTest/Assemblies/PublicUse/TypeContainer.cs b/sources/NetArchTest/Assemblies/PublicUse/TypeContainer.cs index 8da6966..6e70bc5 100644 --- a/sources/NetArchTest/Assemblies/PublicUse/TypeContainer.cs +++ b/sources/NetArchTest/Assemblies/PublicUse/TypeContainer.cs @@ -1,5 +1,8 @@ using System; using System.Diagnostics; +#if NET10_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif using Mono.Cecil; using NetArchTest.Rules; @@ -23,23 +26,42 @@ internal sealed class TypeContainer : IType public string SourceFilePath => _sourceFilePath.Value; +#if NET10_0_OR_GREATER + [UnconditionalSuppressMessage("Trim analysis", "IL2026", + Justification = "Lazy body delegates to ResolveReflectionType (annotated). Tier 1 paths never read ReflectionType.")] + [UnconditionalSuppressMessage("AOT analysis", "IL3050", + Justification = "Lazy body delegates to ResolveReflectionType (annotated). Tier 1 paths never read ReflectionType.")] +#endif internal TypeContainer(TypeDefinition monoTypeDefinition, string explanation) { _monoTypeDefinition = monoTypeDefinition; - _reflactionType = new Lazy(() => - { - try - { - return _monoTypeDefinition.ToType(); - } - catch - { - } - return null; - }); + _reflactionType = new Lazy(() => ResolveReflectionType(_monoTypeDefinition)); _sourceFilePath = new Lazy(() => _monoTypeDefinition.GetFilePath()); Explanation = explanation; - } + } + + + /// + /// Extracted Lazy initializer body so the trim analyzer can attribute the + /// reflection-into-Mono.Cecil call cleanly. Tier 1 paths never read + /// , so this method is unreachable for AOT consumers + /// who only use Tier 1. + /// +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif + private static Type ResolveReflectionType(TypeDefinition monoTypeDefinition) + { + try + { + return monoTypeDefinition.ToType(); + } + catch + { + } + return null; + } public static implicit operator Type(TypeContainer type) diff --git a/sources/NetArchTest/Condition_Dependencies.cs b/sources/NetArchTest/Condition_Dependencies.cs index d8f173e..5cd8f3d 100644 --- a/sources/NetArchTest/Condition_Dependencies.cs +++ b/sources/NetArchTest/Condition_Dependencies.cs @@ -1,4 +1,7 @@ using NetArchTest.Functions; +#if NET10_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif namespace NetArchTest.Rules { @@ -9,6 +12,10 @@ public sealed partial class Condition /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public ConditionList HaveDependencyOnAny(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAny(context, inputTypes, dependencies, true)); @@ -20,6 +27,10 @@ public ConditionList HaveDependencyOnAny(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public ConditionList HaveDependencyOnAll(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAll(context, inputTypes, dependencies, true)); @@ -31,6 +42,10 @@ public ConditionList HaveDependencyOnAll(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public ConditionList OnlyHaveDependencyOn(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(context, inputTypes, dependencies, true)); @@ -42,6 +57,10 @@ public ConditionList OnlyHaveDependencyOn(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public ConditionList NotHaveDependencyOnAny(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAny(context, inputTypes, dependencies, false)); @@ -53,6 +72,10 @@ public ConditionList NotHaveDependencyOnAny(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public ConditionList NotHaveDependencyOnAll(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAll(context, inputTypes, dependencies, false)); @@ -64,6 +87,10 @@ public ConditionList NotHaveDependencyOnAll(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public ConditionList HaveDependencyOtherThan(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(context, inputTypes, dependencies, false)); @@ -76,6 +103,10 @@ public ConditionList HaveDependencyOtherThan(params string[] dependencies) /// /// The types to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public ConditionList BeUsedByAny(params string[] users) { AddFunctionCall((context, inputTypes) => FunctionDelegates.AreUsedByAny(context, inputTypes, users, true)); @@ -87,6 +118,10 @@ public ConditionList BeUsedByAny(params string[] users) /// /// The types to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public ConditionList NotBeUsedByAny(params string[] users) { AddFunctionCall((context, inputTypes) => FunctionDelegates.AreUsedByAny(context, inputTypes, users, false)); diff --git a/sources/NetArchTest/Dependencies/DataStructures/NamespaceTree.cs b/sources/NetArchTest/Dependencies/DataStructures/NamespaceTree.cs index b96f907..5a6ac63 100644 --- a/sources/NetArchTest/Dependencies/DataStructures/NamespaceTree.cs +++ b/sources/NetArchTest/Dependencies/DataStructures/NamespaceTree.cs @@ -2,9 +2,22 @@ { using System; using System.Collections.Generic; +#if NET10_0_OR_GREATER + using System.Diagnostics.CodeAnalysis; +#endif using System.Diagnostics; using System.Text; using Mono.Cecil; +#if NET10_0_OR_GREATER + using NetArchTest.Rules; +#endif +#if NET8_0_OR_GREATER + using System.Buffers; + using System.Collections.Frozen; +#endif +#if NET9_0_OR_GREATER + using System.Runtime.InteropServices; +#endif /// @@ -37,8 +50,24 @@ internal class NamespaceTree : ISearchTree [DebuggerDisplay("Node (nodes : {Nodes.Count})")] private sealed class Node { +#if NET8_0_OR_GREATER + private Dictionary _mutableNodes = new Dictionary(StringComparer.Ordinal); + private FrozenDictionary _frozenNodes; + + private IReadOnlyDictionary Nodes => + (IReadOnlyDictionary)_frozenNodes ?? _mutableNodes; + + internal void Freeze() + { + _frozenNodes = _mutableNodes.ToFrozenDictionary(StringComparer.Ordinal); + _mutableNodes = null; + foreach (var child in _frozenNodes.Values) + child.Freeze(); + } +#else /// Maps child namespace to its root node. private Dictionary Nodes { get; } = new Dictionary(); +#endif public bool IsTerminated { @@ -59,6 +88,22 @@ public Node GetOrAddNode(string name) { name = NormalizeString(name); +#if NET8_0_OR_GREATER + Debug.Assert(_mutableNodes != null, "GetOrAddNode called after Freeze()"); +#if NET9_0_OR_GREATER + ref Node value = ref CollectionsMarshal.GetValueRefOrAddDefault(_mutableNodes, name, out bool exists); + if (!exists) value = new Node(); + return value; +#else + // NET8_0_OR_GREATER but not NET9_0_OR_GREATER: standard insert on mutable dict. + if (!_mutableNodes.TryGetValue(name, out var result)) + { + result = new Node(); + _mutableNodes.Add(name, result); + } + return result; +#endif +#else Node result; if (!Nodes.TryGetValue(name, out result)) { @@ -66,6 +111,7 @@ public Node GetOrAddNode(string name) Nodes.Add(name, result); } return result; +#endif } /// @@ -76,8 +122,31 @@ public Node GetOrAddNode(string name) /// True, if child node with given name exists; otherwise, false. public bool TryGetNode(string name, out Node node) { - return Nodes.TryGetValue(NormalizeString(name), out node) && node != null; + name = NormalizeString(name); +#if NET8_0_OR_GREATER + if (_frozenNodes != null) + return _frozenNodes.TryGetValue(name, out node); + return _mutableNodes.TryGetValue(name, out node); +#else + return Nodes.TryGetValue(name, out node) && node != null; +#endif } + +#if NET9_0_OR_GREATER + // Zero-allocation span-based lookup. Only valid after Freeze(). + // Callers must ensure the segment is NFC-normalized (true for all + // .NET assembly type names, which are ASCII alphanumeric). + public bool TryGetNode(ReadOnlySpan name, out Node node) + { + if (_frozenNodes != null) + { + var lookup = _frozenNodes.GetAlternateLookup>(); + return lookup.TryGetValue(name, out node); + } + // Fallback during build phase (should not be reached in practice). + return TryGetNode(name.ToString(), out node); + } +#endif public void Terminate(string fullName) { @@ -87,7 +156,9 @@ public void Terminate(string fullName) private static string NormalizeString(string str) { - return str.Normalize(NormalizationForm.FormC); + // IsNormalized() is allocation-free; Normalize() always allocates. + // .NET type names are virtually always already NFC (ASCII alphanumeric). + return str.IsNormalized(NormalizationForm.FormC) ? str : str.Normalize(NormalizationForm.FormC); } } @@ -96,6 +167,10 @@ private static string NormalizeString(string str) private static readonly char[] _namespaceSeparators = new char[] { '.', ':', '/', '+' }; +#if NET8_0_OR_GREATER + private static readonly SearchValues _namespaceSeparatorValues = SearchValues.Create(".:/+"); +#endif + /// /// Initially fills the tree with given names. /// @@ -107,6 +182,9 @@ public NamespaceTree(IEnumerable fullNames, bool parseNames = false) { Add(fullName, parseNames); } +#if NET8_0_OR_GREATER + _root.Freeze(); +#endif } /// @@ -114,6 +192,12 @@ public NamespaceTree(IEnumerable fullNames, bool parseNames = false) /// /// Can be empty, but not null. /// if names should be parsed by mono parser +#if NET10_0_OR_GREATER + [UnconditionalSuppressMessage("Trim analysis", "IL2026", + Justification = "ParseTokensRequiringReflection is only invoked when parseNames=true; Tier 1 callers pass false.")] + [UnconditionalSuppressMessage("AOT analysis", "IL3050", + Justification = "ParseTokensRequiringReflection is only invoked when parseNames=true; Tier 1 callers pass false.")] +#endif private void Add(string fullName, bool parseNames) { if (fullName == null) @@ -122,7 +206,10 @@ private void Add(string fullName, bool parseNames) } var deepestNode = _root; - foreach (var token in TypeParser.Parse(fullName, parseNames)) + var tokens = parseNames + ? ParseTokensRequiringReflection(fullName) + : new[] { fullName }; + foreach (var token in tokens) { int subnameEndIndex = -1; while (subnameEndIndex != token.Length) @@ -140,6 +227,21 @@ private void Add(string fullName, bool parseNames) } } + /// + /// Extracted parse path that flows through 's private reflection + /// into Mono.Cecil internals. Isolated into its own method so the trim analyzer attributes + /// it correctly, and so the parseNames=false branch in remains + /// trim-/AOT-clean (the analyzer can't follow the conditional otherwise). + /// +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif + private static IEnumerable ParseTokensRequiringReflection(string fullName) + { + return TypeParser.Parse(fullName, parseNames: true); + } + /// Count of terminated nodes in the tree. public int TerminatedNodesCount { get; private set; } = 0; @@ -158,6 +260,25 @@ public IEnumerable GetAllMatchingNames(string fullName) { var deepestNode = _root; +#if NET9_0_OR_GREATER + // SearchValues SIMD scan via implicit string→span in GetSubnameEndIndex. + // Span is passed inline as a method argument — not stored as a local — so it is + // safe across yield boundaries (CS4007 only fires on *local* ref-struct variables). + int subnameEndIndex = -1; + while (subnameEndIndex != fullName.Length) + { + int subnameStartIndex = subnameEndIndex + 1; + subnameEndIndex = GetSubnameEndIndex(fullName, subnameStartIndex); + if (!deepestNode.TryGetNode(fullName.AsSpan(subnameStartIndex, subnameEndIndex - subnameStartIndex), out deepestNode)) + { + yield break; + } + if (deepestNode.IsTerminated) + { + yield return deepestNode.FullName; + } + } +#else int subnameEndIndex = -1; while (subnameEndIndex != fullName.Length) { @@ -175,6 +296,7 @@ public IEnumerable GetAllMatchingNames(string fullName) yield return deepestNode.FullName; } } +#endif } public IEnumerable GetAllMatchingNames(TypeReference reference) @@ -189,8 +311,14 @@ public IEnumerable GetAllMatchingNames(TypeReference reference) int subnameStartIndex = subnameEndIndex + 1; subnameEndIndex = GetSubnameEndIndex(token, subnameStartIndex); +#if NET9_0_OR_GREATER + // Inline span avoids Substring allocation and the NormalizeString check. + // Not stored as a local — safe across yield boundaries (CS4007). + if (!deepestNode.TryGetNode(token.AsSpan(subnameStartIndex, subnameEndIndex - subnameStartIndex), out deepestNode)) +#else string name = token.Substring(subnameStartIndex, subnameEndIndex - subnameStartIndex); if (!deepestNode.TryGetNode(name, out deepestNode)) +#endif { yield break; } @@ -258,6 +386,13 @@ private IEnumerable GetTokens(TypeReference reference) } +#if NET8_0_OR_GREATER + private static int GetSubnameEndIndex(ReadOnlySpan namespaceFullName, int subnameStartIndex) + { + int idx = namespaceFullName.Slice(subnameStartIndex).IndexOfAny(_namespaceSeparatorValues); + return idx < 0 ? namespaceFullName.Length : subnameStartIndex + idx; + } +#else private static int GetSubnameEndIndex(string namespaceFullName, int subnameStartIndex) { int nextSeparatorIndex = namespaceFullName.IndexOfAny(_namespaceSeparators, subnameStartIndex); @@ -268,5 +403,6 @@ private static int GetSubnameEndIndex(string namespaceFullName, int subnameStart return nextSeparatorIndex; } +#endif } } \ No newline at end of file diff --git a/sources/NetArchTest/Dependencies/TypeParser.cs b/sources/NetArchTest/Dependencies/TypeParser.cs index cc6501d..6f40527 100644 --- a/sources/NetArchTest/Dependencies/TypeParser.cs +++ b/sources/NetArchTest/Dependencies/TypeParser.cs @@ -2,9 +2,30 @@ { using System; using System.Collections.Generic; +#if NET10_0_OR_GREATER + using System.Diagnostics.CodeAnalysis; +#endif using System.Reflection; - using System.Text; + using System.Text; +#if NET10_0_OR_GREATER + using NetArchTest.Rules; +#endif +#if NET10_0_OR_GREATER + // TypeParser uses unbounded reflection against Mono.Cecil internals + // (Mono.Cecil.TypeParser is private to the Cecil assembly). Under + // PublishAot/PublishTrimmed the IL2057/IL2080 warnings on these static + // field initializers are unavoidable. Suppress at the type level — every + // public entry point on the class is itself annotated with + // [RequiresUnreferencedCode] + [RequiresDynamicCode], so callers are + // correctly warned at use time. + [UnconditionalSuppressMessage("Trim analysis", "IL2057", + Justification = "Internal Mono.Cecil reflection; entry points are RequiresUnreferencedCode.")] + [UnconditionalSuppressMessage("Trim analysis", "IL2077", + Justification = "Internal Mono.Cecil reflection; entry points are RequiresUnreferencedCode.")] + [UnconditionalSuppressMessage("Trim analysis", "IL2080", + Justification = "Internal Mono.Cecil reflection; entry points are RequiresUnreferencedCode.")] +#endif internal static class TypeParser { static readonly Type mono_TypeParserType = Type.GetType("Mono.Cecil.TypeParser, " + typeof(Mono.Cecil.TypeReference).Assembly); @@ -16,6 +37,10 @@ internal static class TypeParser static readonly FieldInfo mono_specsField = mono_TypeType.GetField("specs", BindingFlags.Instance | BindingFlags.Public); +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public static IEnumerable Parse(string fullName, bool parseNames) { if (parseNames == false) @@ -87,6 +112,10 @@ private static IEnumerable WalkThroughMonoType(object monoType) +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public static string ParseReflectionNameToRuntimeName(string fullName) { var monoTypeParser = Activator.CreateInstance(mono_TypeParserType, BindingFlags.Instance | BindingFlags.NonPublic, null, args: new object[] { fullName }, null); diff --git a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs index a22e47f..7cb098d 100644 --- a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs +++ b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs @@ -1,8 +1,14 @@ using System; using System.CodeDom.Compiler; using System.Collections.Generic; +#if NET10_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif using System.Linq; using System.Runtime.CompilerServices; +#if NET10_0_OR_GREATER +using NetArchTest.Rules; +#endif namespace Mono.Cecil { @@ -58,6 +64,10 @@ internal static bool IsAlmostEqualTo(this TypeReference child, TypeDefinition pa /// /// The type definition to convert. /// The equivalent object instance. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public static Type ToType(this TypeDefinition typeDefinition) { var fullName = typeDefinition.FullName.RuntimeNameToReflectionName(); diff --git a/sources/NetArchTest/Functions/FunctionDelegates_Dependencies.cs b/sources/NetArchTest/Functions/FunctionDelegates_Dependencies.cs index 1c0f951..60e8a65 100644 --- a/sources/NetArchTest/Functions/FunctionDelegates_Dependencies.cs +++ b/sources/NetArchTest/Functions/FunctionDelegates_Dependencies.cs @@ -2,13 +2,21 @@ using System.Linq; using NetArchTest.Assemblies; using NetArchTest.Dependencies; +using NetArchTest.Rules; using NetArchTest.RuleEngine; +#if NET10_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif namespace NetArchTest.Functions { internal static partial class FunctionDelegates { /// Function for finding types that have a dependency on any of the supplied types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif internal static IEnumerable HaveDependencyOnAny(FunctionSequenceExecutionContext context, IEnumerable input, IEnumerable dependencies, bool condition) { // Get the types that contain the dependencies @@ -18,6 +26,10 @@ internal static IEnumerable HaveDependencyOnAny(FunctionSequenceExecut } /// Function for finding types that have a dependency on all of the supplied types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif internal static IEnumerable HaveDependencyOnAll(FunctionSequenceExecutionContext context, IEnumerable input, IEnumerable dependencies, bool condition) { // Get the types that contain the dependencies @@ -28,6 +40,10 @@ internal static IEnumerable HaveDependencyOnAll(FunctionSequenceExecut } /// Function for finding types that have a dependency on type other than one of the supplied types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif internal static IEnumerable OnlyHaveDependenciesOnAnyOrNone(FunctionSequenceExecutionContext context, IEnumerable input, IEnumerable dependencies, bool condition) { var search = new DependencySearch(context.IsFailPathRun, context.UserOptions.SerachForDependencyInFieldConstant, context.DependencyFilter); @@ -38,6 +54,10 @@ internal static IEnumerable OnlyHaveDependenciesOnAnyOrNone(FunctionSe +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif internal static IEnumerable AreUsedByAny(FunctionSequenceExecutionContext context, IEnumerable input, IEnumerable dependencies, bool condition) { var search = new DependencySearch(context.IsFailPathRun, context.UserOptions.SerachForDependencyInFieldConstant, context.DependencyFilter); diff --git a/sources/NetArchTest/NetArchTest.csproj b/sources/NetArchTest/NetArchTest.csproj index ba45336..93715c2 100644 --- a/sources/NetArchTest/NetArchTest.csproj +++ b/sources/NetArchTest/NetArchTest.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.0;net10.0 1.4.5 NeVeSpl NetArchTest.eNhancedEdition @@ -16,7 +16,8 @@ true NetArchTest.eNhancedEdition MIT - 11 + latest + 11 README.md True xKey.snk @@ -29,15 +30,21 @@ + + - - True - \ - + diff --git a/sources/NetArchTest/NetArchTestAotMessages.cs b/sources/NetArchTest/NetArchTestAotMessages.cs new file mode 100644 index 0000000..fea56aa --- /dev/null +++ b/sources/NetArchTest/NetArchTestAotMessages.cs @@ -0,0 +1,20 @@ +namespace NetArchTest.Rules +{ + /// + /// Centralized AOT/trim warning messages used by + /// and annotations. + /// + /// + /// Annotations are gated by #if NET10_0_OR_GREATER; netstandard2.0 consumers see no behavioral or + /// public-surface change. Tier definitions (kept in sync with PR description): + /// + /// DependencySearch — methods that flow through TypeParser's private reflection into Mono.Cecil. + /// + /// + internal static class NetArchTestAotMessages + { + public const string DependencySearch = + "Dependency-search rules use private reflection into Mono.Cecil internals via TypeParser " + + "and may behave incorrectly under trimming or NativeAOT."; + } +} diff --git a/sources/NetArchTest/Predicate_Dependencies.cs b/sources/NetArchTest/Predicate_Dependencies.cs index 77f4ecf..c2a4b1b 100644 --- a/sources/NetArchTest/Predicate_Dependencies.cs +++ b/sources/NetArchTest/Predicate_Dependencies.cs @@ -1,4 +1,7 @@ using NetArchTest.Functions; +#if NET10_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif namespace NetArchTest.Rules { @@ -9,6 +12,10 @@ public sealed partial class Predicate /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public PredicateList HaveDependencyOnAny(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAny(context, inputTypes, dependencies, true)); @@ -20,6 +27,10 @@ public PredicateList HaveDependencyOnAny(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public PredicateList HaveDependencyOnAll(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAll(context, inputTypes, dependencies, true)); @@ -31,6 +42,10 @@ public PredicateList HaveDependencyOnAll(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public PredicateList OnlyHaveDependencyOn(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(context, inputTypes, dependencies, true)); @@ -42,6 +57,10 @@ public PredicateList OnlyHaveDependencyOn(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public PredicateList DoNotHaveDependencyOnAny(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAny(context, inputTypes, dependencies, false)); @@ -53,6 +72,10 @@ public PredicateList DoNotHaveDependencyOnAny(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public PredicateList DoNotHaveDependencyOnAll(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAll(context, inputTypes, dependencies, false)); @@ -64,6 +87,10 @@ public PredicateList DoNotHaveDependencyOnAll(params string[] dependencies) /// /// The dependencies to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public PredicateList HaveDependencyOtherThan(params string[] dependencies) { AddFunctionCall((context, inputTypes) => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(context, inputTypes, dependencies, false)); @@ -75,6 +102,10 @@ public PredicateList HaveDependencyOtherThan(params string[] dependencies) /// /// The types to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public PredicateList AreUsedByAny(params string[] users) { AddFunctionCall((context, inputTypes) => FunctionDelegates.AreUsedByAny(context, inputTypes, users, true)); @@ -86,6 +117,10 @@ public PredicateList AreUsedByAny(params string[] users) /// /// The types to match against. These can be namespaces or specific types. /// An updated set of conditions that can be applied to a list of types. +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public PredicateList AreNotUsedByAny(params string[] users) { AddFunctionCall((context, inputTypes) => FunctionDelegates.AreUsedByAny(context, inputTypes, users, false)); diff --git a/sources/NetArchTest/Slices/SliceCondition.cs b/sources/NetArchTest/Slices/SliceCondition.cs index 773e8e0..6648856 100644 --- a/sources/NetArchTest/Slices/SliceCondition.cs +++ b/sources/NetArchTest/Slices/SliceCondition.cs @@ -1,5 +1,9 @@ using NetArchTest.Assemblies; +using NetArchTest.Rules; using NetArchTest.Slices.Model; +#if NET10_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif namespace NetArchTest.Slices { @@ -21,6 +25,10 @@ internal SliceCondition(SliceContext sliceContext, bool should) /// /// Selects types that have some dependencies on types from other slices. /// +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public SliceConditionList HaveDependenciesBetweenSlices() { return new SliceConditionList(sliceContext, new HaveDependenciesBetweenSlices(), should); @@ -29,6 +37,10 @@ public SliceConditionList HaveDependenciesBetweenSlices() /// /// Selects types that do not have dependencies on types from other slices. /// +#if NET10_0_OR_GREATER + [RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)] + [RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)] +#endif public SliceConditionList NotHaveDependenciesBetweenSlices() { return new SliceConditionList(sliceContext, new HaveDependenciesBetweenSlices(), !should); diff --git a/tests/NetArchTest.AotSmoke.Fixture/FixtureTypes.cs b/tests/NetArchTest.AotSmoke.Fixture/FixtureTypes.cs new file mode 100644 index 0000000..73a75f3 --- /dev/null +++ b/tests/NetArchTest.AotSmoke.Fixture/FixtureTypes.cs @@ -0,0 +1,26 @@ +namespace NetArchTest.AotSmoke.Fixture.Services +{ + public class GreetingService + { + public string Greet(string name) => $"Hello, {name}!"; + } + + public class CounterService + { + private int _count; + public int Increment() => ++_count; + } +} + +namespace NetArchTest.AotSmoke.Fixture.Models +{ + public class Customer + { + public string? Name { get; set; } + } + + public class Order + { + public int Id { get; set; } + } +} diff --git a/tests/NetArchTest.AotSmoke.Fixture/NetArchTest.AotSmoke.Fixture.csproj b/tests/NetArchTest.AotSmoke.Fixture/NetArchTest.AotSmoke.Fixture.csproj new file mode 100644 index 0000000..8f69995 --- /dev/null +++ b/tests/NetArchTest.AotSmoke.Fixture/NetArchTest.AotSmoke.Fixture.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + 11 + enable + NetArchTest.AotSmoke.Fixture + false + + + diff --git a/tests/NetArchTest.AotSmoke/NetArchTest.AotSmoke.csproj b/tests/NetArchTest.AotSmoke/NetArchTest.AotSmoke.csproj new file mode 100644 index 0000000..edda3e5 --- /dev/null +++ b/tests/NetArchTest.AotSmoke/NetArchTest.AotSmoke.csproj @@ -0,0 +1,60 @@ + + + + Exe + net10.0 + enable + enable + NetArchTest.AotSmoke + false + true + + + true + true + true + + + + + + + false + false + + + + + + $(NoWarn);IL2104 + + + + + + + + + + <_FixtureDll Include="..\NetArchTest.AotSmoke.Fixture\bin\$(Configuration)\netstandard2.0\NetArchTest.AotSmoke.Fixture.dll" /> + + + + + + + <_FixtureDllPub Include="..\NetArchTest.AotSmoke.Fixture\bin\$(Configuration)\netstandard2.0\NetArchTest.AotSmoke.Fixture.dll" /> + + + + + diff --git a/tests/NetArchTest.AotSmoke/Program.cs b/tests/NetArchTest.AotSmoke/Program.cs new file mode 100644 index 0000000..8c87b53 --- /dev/null +++ b/tests/NetArchTest.AotSmoke/Program.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Linq; +using NetArchTest.Rules; + +// AOT smoke test for NetArchTest.eNhancedEdition. +// +// Goal: prove the library's documented AOT-safe surface (Tier 1 — type loading +// from a path, name/namespace/structural predicates) compiles cleanly with +// PublishAot=true + TreatWarningsAsErrors=true AND executes correctly against +// a real fixture assembly after NativeAOT publish. +// +// Methods that require unreferenced/dynamic code (dependency-search, slices, +// AreUsedByAny, etc.) are intentionally NOT exercised here — their +// [RequiresUnreferencedCode] / [RequiresDynamicCode] annotations would surface +// as IL2026 / IL3050 errors here, which is the contract. + +string fixtureDir = AppContext.BaseDirectory; +string fixtureDll = Path.Combine(fixtureDir, "NetArchTest.AotSmoke.Fixture.dll"); + +if (!File.Exists(fixtureDll)) +{ + Console.Error.WriteLine($"FAIL: fixture DLL not found at '{fixtureDll}'."); + return 1; +} + +var allTypes = Types.FromPath(fixtureDir).GetTypes().ToList(); +if (allTypes.Count == 0) +{ + Console.Error.WriteLine("FAIL: Types.FromPath returned no types."); + return 1; +} + +// Passing rule: classes with suffix "Service" should live in the Services namespace. +var servicesRule = Types.FromPath(fixtureDir) + .That() + .AreClasses() + .And().HaveNameEndingWith("Service") + .Should() + .ResideInNamespace("NetArchTest.AotSmoke.Fixture.Services") + .GetResult(); + +if (!servicesRule.IsSuccessful) +{ + Console.Error.WriteLine("FAIL: 'Service-suffixed classes live in Services namespace' rule failed."); + if (servicesRule.FailingTypes != null) + { + foreach (var t in servicesRule.FailingTypes) + { + Console.Error.WriteLine($" - {t.FullName}"); + } + } + return 1; +} + +// Sanity-check that rule evaluation isn't being short-circuited. +var shouldFail = Types.FromPath(fixtureDir) + .That() + .AreClasses() + .Should() + .HaveNameStartingWith("ZZZ_NoTypeStartsWithThis_") + .GetResult(); + +if (shouldFail.IsSuccessful) +{ + Console.Error.WriteLine("FAIL: deliberately-failing sanity rule unexpectedly passed."); + return 1; +} + +Console.WriteLine($"OK: NetArchTest AOT smoke passed. Loaded {allTypes.Count} types; both rules evaluated as expected."); +return 0; diff --git a/tests/NetArchTest.Benchmarks/DependencySearchBenchmarks.cs b/tests/NetArchTest.Benchmarks/DependencySearchBenchmarks.cs new file mode 100644 index 0000000..e11a3f3 --- /dev/null +++ b/tests/NetArchTest.Benchmarks/DependencySearchBenchmarks.cs @@ -0,0 +1,42 @@ +namespace NetArchTest.Benchmarks; + +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using NetArchTest.Rules; + +[SimpleJob(RuntimeMoniker.Net80)] +[SimpleJob(RuntimeMoniker.Net100)] +[MemoryDiagnoser] +public class DependencySearchBenchmarks +{ + private Types? _types; + + [GlobalSetup] + public void Setup() + { + _types = Types.InAssembly(typeof(NetArchTest.Rules.Types).Assembly); + } + + [Benchmark] + public bool HaveNoDependenciesOnSystem() + { + return _types! + .That() + .ResideInNamespace("NetArchTest") + .Should() + .NotHaveDependencyOnAny("System.Reflection", "System.IO") + .GetResult() + .IsSuccessful; + } + + [Benchmark] + public int CountTypesWithLinqDependency() + { + return _types! + .That() + .HaveDependencyOn("System.Linq") + .GetTypes() + .Count(); + } +} diff --git a/tests/NetArchTest.Benchmarks/NetArchTest.Benchmarks.csproj b/tests/NetArchTest.Benchmarks/NetArchTest.Benchmarks.csproj new file mode 100644 index 0000000..1c791ac --- /dev/null +++ b/tests/NetArchTest.Benchmarks/NetArchTest.Benchmarks.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0;net10.0 + enable + true + true + + + + + + + + + + + diff --git a/tests/NetArchTest.Benchmarks/Program.cs b/tests/NetArchTest.Benchmarks/Program.cs new file mode 100644 index 0000000..3d3b73d --- /dev/null +++ b/tests/NetArchTest.Benchmarks/Program.cs @@ -0,0 +1,3 @@ +using BenchmarkDotNet.Running; + +BenchmarkRunner.Run(); diff --git a/tests/NetArchTest.Rules.UnitTests/NetArchTest.UnitTests.csproj b/tests/NetArchTest.Rules.UnitTests/NetArchTest.UnitTests.csproj index 408cc95..6d32b46 100644 --- a/tests/NetArchTest.Rules.UnitTests/NetArchTest.UnitTests.csproj +++ b/tests/NetArchTest.Rules.UnitTests/NetArchTest.UnitTests.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net10.0 false 12 True