1414using BenchmarkDotNet . Loggers ;
1515using BenchmarkDotNet . Running ;
1616using BenchmarkDotNet . Toolchains . DotNetCli ;
17+ using BenchmarkDotNet . Toolchains . Mono ;
1718using BenchmarkDotNet . Toolchains . Results ;
1819using JetBrains . Annotations ;
1920
@@ -71,16 +72,29 @@ protected override void GenerateBuildScript(BuildPartition buildPartition, Artif
7172
7273 var content = new StringBuilder ( 300 )
7374 . AppendLine ( $ "call { CliPath ?? "dotnet" } { DotNetCliCommand . GetRestoreCommand ( artifactsPaths , buildPartition , projectFilePath ) } ")
74- . AppendLine ( $ "call { CliPath ?? "dotnet" } { DotNetCliCommand . GetPublishCommand ( artifactsPaths , buildPartition , projectFilePath , TargetFrameworkMoniker ) } ")
75+ . AppendLine ( $ "call { CliPath ?? "dotnet" } { DotNetCliCommand . GetBuildCommand ( artifactsPaths , buildPartition , projectFilePath , TargetFrameworkMoniker ) } ")
7576 . AppendLine ( $ "call { CliPath ?? "dotnet" } { DotNetCliCommand . GetRestoreCommand ( artifactsPaths , buildPartition , artifactsPaths . ProjectFilePath ) } ")
76- . AppendLine ( $ "call { CliPath ?? "dotnet" } { DotNetCliCommand . GetPublishCommand ( artifactsPaths , buildPartition , artifactsPaths . ProjectFilePath , TargetFrameworkMoniker ) } ")
77+ . AppendLine ( $ "call { CliPath ?? "dotnet" } { DotNetCliCommand . GetBuildCommand ( artifactsPaths , buildPartition , artifactsPaths . ProjectFilePath , TargetFrameworkMoniker ) } ")
7778 . ToString ( ) ;
7879
7980 File . WriteAllText ( artifactsPaths . BuildScriptFilePath , content ) ;
8081 }
8182
8283 [ SuppressMessage ( "ReSharper" , "StringLiteralTypo" ) ] // R# complains about $variables$
8384 protected override void GenerateProject ( BuildPartition buildPartition , ArtifactsPaths artifactsPaths , ILogger logger )
85+ {
86+ File . WriteAllText ( artifactsPaths . ProjectFilePath ,
87+ GenerateBuildProject ( buildPartition , artifactsPaths , logger )
88+ ) ;
89+
90+ // Integration tests are built without dependencies, so we skip gathering dlls.
91+ if ( ! buildPartition . ForcedNoDependenciesForIntegrationTests )
92+ {
93+ GatherReferences ( buildPartition , artifactsPaths , logger ) ;
94+ }
95+ }
96+
97+ private string GenerateBuildProject ( BuildPartition buildPartition , ArtifactsPaths artifactsPaths , ILogger logger )
8498 {
8599 var benchmark = buildPartition . RepresentativeBenchmarkCase ;
86100 var projectFile = GetProjectFilePath ( benchmark . Descriptor . Type , logger ) ;
@@ -89,7 +103,7 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
89103 xmlDoc . Load ( projectFile . FullName ) ;
90104 var ( customProperties , sdkName ) = GetSettingsThatNeedToBeCopied ( xmlDoc , projectFile ) ;
91105
92- var content = new StringBuilder ( ResourceHelper . LoadTemplate ( "CsProj.txt" ) )
106+ return new StringBuilder ( ResourceHelper . LoadTemplate ( "CsProj.txt" ) )
93107 . Replace ( "$PLATFORM$" , buildPartition . Platform . ToConfig ( ) )
94108 . Replace ( "$CODEFILENAME$" , Path . GetFileName ( artifactsPaths . ProgramCodePath ) )
95109 . Replace ( "$CSPROJPATH$" , projectFile . FullName )
@@ -99,43 +113,62 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
99113 . Replace ( "$COPIEDSETTINGS$" , customProperties )
100114 . Replace ( "$SDKNAME$" , sdkName )
101115 . ToString ( ) ;
102-
103- File . WriteAllText ( artifactsPaths . ProjectFilePath , content ) ;
104-
105- // Integration tests are built without dependencies, so we skip gathering dlls.
106- if ( ! buildPartition . ForcedNoDependenciesForIntegrationTests )
107- {
108- GatherReferences ( projectFile . FullName , buildPartition , artifactsPaths , logger ) ;
109- }
110116 }
111117
112- protected void GatherReferences ( string projectFilePath , BuildPartition buildPartition , ArtifactsPaths artifactsPaths , ILogger logger )
118+ private static string GetDllGathererPath ( string filePath )
119+ => Path . Combine ( Path . GetDirectoryName ( filePath ) , $ "DllGatherer{ Path . GetExtension ( filePath ) } ") ;
120+
121+ protected void GatherReferences ( BuildPartition buildPartition , ArtifactsPaths artifactsPaths , ILogger logger )
113122 {
114- // Build the original project then reference all of the built dlls.
115- BuildResult buildResult = BuildProject ( TargetFrameworkMoniker ) ;
123+ // Create a project using the default template to build the original project for all necessary runtime dlls.
124+ // We can't just build the original project directly because it could be a library project, so we need an exe project to reference it.
125+ var xmlDoc = new XmlDocument ( ) ;
126+ xmlDoc . LoadXml ( GenerateBuildProject ( buildPartition , artifactsPaths , logger ) ) ;
127+ var projectElement = xmlDoc . DocumentElement ;
128+
129+ // Replace the default C# file with an empty Main method to satisfy the exe build.
130+ var compileNode = projectElement . SelectSingleNode ( "ItemGroup/Compile" ) ;
131+ string emptyMainFile = GetDllGathererPath ( artifactsPaths . ProgramCodePath ) ;
132+ compileNode . Attributes [ "Include" ] . Value = emptyMainFile ;
133+ string gathererProject = GetDllGathererPath ( artifactsPaths . ProjectFilePath ) ;
134+ xmlDoc . Save ( gathererProject ) ;
135+
136+ File . WriteAllText ( emptyMainFile , """
137+ namespace BenchmarkDotNet.Autogenerated
138+ {
139+ public class UniqueProgramName
140+ {
141+ public static int Main(string[] args)
142+ {
143+ return 0;
144+ }
145+ }
146+ }
147+ """ ) ;
116148
117- // The build could fail because the project doesn't have a tfm that matches the runtime, e.g. netstandard2.0 vs net10.0,
118- // So we try to get the actual tfm of the assembly and build again.
119- if ( ! buildResult . IsBuildSuccess
120- && FrameworkVersionHelper . GetTfm ( buildPartition . RepresentativeBenchmarkCase . Descriptor . Type . Assembly ) is { } actualTfm
121- && actualTfm != TargetFrameworkMoniker )
122- {
123- buildResult = BuildProject ( actualTfm ) ;
124- }
149+ // Build the original project then reference all of the built dlls.
150+ BuildResult buildResult = new DotNetCliCommand (
151+ CliPath ,
152+ gathererProject ,
153+ TargetFrameworkMoniker ,
154+ null ,
155+ GenerateResult . Success ( artifactsPaths , [ ] ) ,
156+ logger ,
157+ buildPartition ,
158+ [ ] ,
159+ buildPartition . Timeout
160+ ) . RestoreThenBuild ( ) ;
125161
126162 if ( ! buildResult . IsBuildSuccess )
127163 {
128- if ( ! buildResult . TryToExplainFailureReason ( out string reason ) )
129- {
130- reason = buildResult . ErrorMessage ;
131- }
132- logger . WriteLineWarning ( $ "Failed to build source project to obtain dll references. Moving forward without it. Reason: { reason } ") ;
133- return ;
164+ throw buildResult . TryToExplainFailureReason ( out string reason )
165+ ? new Exception ( reason )
166+ : new Exception ( buildResult . ErrorMessage ) ;
134167 }
135168
136- var xmlDoc = new XmlDocument ( ) ;
169+ xmlDoc = new XmlDocument ( ) ;
137170 xmlDoc . Load ( artifactsPaths . ProjectFilePath ) ;
138- XmlElement projectElement = xmlDoc . DocumentElement ;
171+ projectElement = xmlDoc . DocumentElement ;
139172 var itemGroup = xmlDoc . CreateElement ( "ItemGroup" ) ;
140173 projectElement . AppendChild ( itemGroup ) ;
141174 foreach ( var assemblyFile in Directory . GetFiles ( artifactsPaths . BinariesDirectoryPath , "*.dll" ) )
@@ -151,26 +184,13 @@ protected void GatherReferences(string projectFilePath, BuildPartition buildPart
151184 }
152185
153186 // Mono80IsSupported test fails when BenchmarkDotNet is restored for net9.0 if we don't remove the ProjectReference.
154- if ( XUnitHelper . IsIntegrationTest . Value )
187+ // We still need to preserve the ProjectReference in every other case for disassembly, though.
188+ if ( XUnitHelper . IsIntegrationTest . Value && this is MonoGenerator )
155189 {
156190 projectElement . RemoveChild ( projectElement . SelectSingleNode ( "ItemGroup/ProjectReference" ) . ParentNode ) ;
157191 }
158192
159193 xmlDoc . Save ( artifactsPaths . ProjectFilePath ) ;
160-
161- BuildResult BuildProject ( string tfm )
162- => new DotNetCliCommand (
163- CliPath ,
164- projectFilePath ,
165- tfm ,
166- null ,
167- GenerateResult . Success ( artifactsPaths , [ ] ) ,
168- logger ,
169- buildPartition ,
170- [ ] ,
171- buildPartition . Timeout
172- )
173- . RestoreThenBuild ( ) ;
174194 }
175195
176196 /// <summary>
0 commit comments