1 using System; 2 using System.CodeDom.Compiler; 3 using System.Collections.Generic; 4 using System.IO; 5 using System.Linq; 6 using System.Text; 7 using Mono.Linker.Tests.Extensions; 8 9 namespace Mono.Linker.Tests.TestCasesRunner { 10 public class TestCaseCompiler { 11 protected readonly TestCaseMetadaProvider _metadataProvider; 12 protected readonly TestCaseSandbox _sandbox; 13 protected readonly ILCompiler _ilCompiler; 14 TestCaseCompiler(TestCaseSandbox sandbox, TestCaseMetadaProvider metadataProvider)15 public TestCaseCompiler (TestCaseSandbox sandbox, TestCaseMetadaProvider metadataProvider) 16 : this(sandbox, metadataProvider, new ILCompiler ()) 17 { 18 } 19 TestCaseCompiler(TestCaseSandbox sandbox, TestCaseMetadaProvider metadataProvider, ILCompiler ilCompiler)20 public TestCaseCompiler (TestCaseSandbox sandbox, TestCaseMetadaProvider metadataProvider, ILCompiler ilCompiler) 21 { 22 _ilCompiler = ilCompiler; 23 _sandbox = sandbox; 24 _metadataProvider = metadataProvider; 25 } 26 CompileTestIn(NPath outputDirectory, string outputName, IEnumerable<string> sourceFiles, IEnumerable<string> references, IEnumerable<string> defines, NPath[] resources, string[] additionalArguments)27 public NPath CompileTestIn (NPath outputDirectory, string outputName, IEnumerable<string> sourceFiles, IEnumerable<string> references, IEnumerable<string> defines, NPath[] resources, string[] additionalArguments) 28 { 29 var originalReferences = references.Select (r => r.ToNPath ()).ToArray (); 30 var originalDefines = defines?.ToArray () ?? new string [0]; 31 32 Prepare (outputDirectory); 33 34 var compiledReferences = CompileBeforeTestCaseAssemblies (outputDirectory, originalReferences, originalDefines).ToArray (); 35 var allTestCaseReferences = originalReferences.Concat (compiledReferences).ToArray (); 36 37 var options = CreateOptionsForTestCase ( 38 outputDirectory.Combine (outputName), 39 sourceFiles.Select (s => s.ToNPath ()).ToArray (), 40 allTestCaseReferences, 41 originalDefines, 42 resources, 43 additionalArguments); 44 var testAssembly = CompileAssembly (options); 45 46 47 // The compile after step is used by tests to mess around with the input to the linker. Generally speaking, it doesn't seem like we would ever want to mess with the 48 // expectations assemblies because this would undermine our ability to inspect them for expected results during ResultChecking. The UnityLinker UnresolvedHandling tests depend on this 49 // behavior of skipping the after test compile 50 if (outputDirectory != _sandbox.ExpectationsDirectory) 51 CompileAfterTestCaseAssemblies (outputDirectory, originalReferences, originalDefines); 52 53 return testAssembly; 54 } 55 Prepare(NPath outputDirectory)56 protected virtual void Prepare (NPath outputDirectory) 57 { 58 } 59 CreateOptionsForTestCase(NPath outputPath, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources, string[] additionalArguments)60 protected virtual CompilerOptions CreateOptionsForTestCase (NPath outputPath, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources, string[] additionalArguments) 61 { 62 return new CompilerOptions 63 { 64 OutputPath = outputPath, 65 SourceFiles = sourceFiles, 66 References = references, 67 Defines = defines.Concat (_metadataProvider.GetDefines ()).ToArray (), 68 Resources = resources, 69 AdditionalArguments = additionalArguments 70 }; 71 } 72 CreateOptionsForSupportingAssembly(SetupCompileInfo setupCompileInfo, NPath outputDirectory, NPath[] sourceFiles, NPath[] references, string[] defines)73 protected virtual CompilerOptions CreateOptionsForSupportingAssembly (SetupCompileInfo setupCompileInfo, NPath outputDirectory, NPath[] sourceFiles, NPath[] references, string[] defines) 74 { 75 var allDefines = defines.Concat (setupCompileInfo.Defines ?? new string [0]).ToArray (); 76 var allReferences = references.Concat (setupCompileInfo.References?.Select (p => MakeSupportingAssemblyReferencePathAbsolute (outputDirectory, p)) ?? new NPath [0]).ToArray (); 77 return new CompilerOptions 78 { 79 OutputPath = outputDirectory.Combine (setupCompileInfo.OutputName), 80 SourceFiles = sourceFiles, 81 References = allReferences, 82 Defines = allDefines 83 }; 84 } 85 CompileBeforeTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines)86 private IEnumerable<NPath> CompileBeforeTestCaseAssemblies (NPath outputDirectory, NPath[] references, string[] defines) 87 { 88 foreach (var setupCompileInfo in _metadataProvider.GetSetupCompileAssembliesBefore ()) 89 { 90 var options = CreateOptionsForSupportingAssembly (setupCompileInfo, outputDirectory, CollectSetupBeforeSourcesFiles (setupCompileInfo), references, defines); 91 var output = CompileAssembly (options); 92 if (setupCompileInfo.AddAsReference) 93 yield return output; 94 } 95 } 96 CompileAfterTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines)97 private void CompileAfterTestCaseAssemblies (NPath outputDirectory, NPath[] references, string[] defines) 98 { 99 foreach (var setupCompileInfo in _metadataProvider.GetSetupCompileAssembliesAfter ()) 100 { 101 var options = CreateOptionsForSupportingAssembly (setupCompileInfo, outputDirectory, CollectSetupAfterSourcesFiles (setupCompileInfo), references, defines); 102 CompileAssembly (options); 103 } 104 } 105 CollectSetupBeforeSourcesFiles(SetupCompileInfo info)106 private NPath[] CollectSetupBeforeSourcesFiles (SetupCompileInfo info) 107 { 108 return CollectSourceFilesFrom (_sandbox.BeforeReferenceSourceDirectoryFor (info.OutputName)); 109 } 110 CollectSetupAfterSourcesFiles(SetupCompileInfo info)111 private NPath[] CollectSetupAfterSourcesFiles (SetupCompileInfo info) 112 { 113 return CollectSourceFilesFrom (_sandbox.AfterReferenceSourceDirectoryFor (info.OutputName)); 114 } 115 CollectSourceFilesFrom(NPath directory)116 private static NPath[] CollectSourceFilesFrom (NPath directory) 117 { 118 var sourceFiles = directory.Files ("*.cs").ToArray (); 119 if (sourceFiles.Length > 0) 120 return sourceFiles; 121 122 sourceFiles = directory.Files ("*.il").ToArray (); 123 if (sourceFiles.Length > 0) 124 return sourceFiles; 125 126 throw new FileNotFoundException ($"Didn't find any sources files in {directory}"); 127 } 128 MakeSupportingAssemblyReferencePathAbsolute(NPath outputDirectory, string referenceFileName)129 protected static NPath MakeSupportingAssemblyReferencePathAbsolute (NPath outputDirectory, string referenceFileName) 130 { 131 // Not a good idea to use a full path in a test, but maybe someone is trying to quickly test something locally 132 if (Path.IsPathRooted (referenceFileName)) 133 return referenceFileName.ToNPath (); 134 135 var possiblePath = outputDirectory.Combine (referenceFileName); 136 if (possiblePath.FileExists ()) 137 return possiblePath; 138 139 return referenceFileName.ToNPath(); 140 } 141 CompileAssembly(CompilerOptions options)142 protected NPath CompileAssembly (CompilerOptions options) 143 { 144 if (options.SourceFiles.Any (path => path.ExtensionWithDot == ".cs")) 145 return CompileCSharpAssembly (options); 146 147 if (options.SourceFiles.Any (path => path.ExtensionWithDot == ".il")) 148 return CompileIlAssembly (options); 149 150 throw new NotSupportedException ($"Unable to compile sources files with extension `{options.SourceFiles.First ().ExtensionWithDot}`"); 151 } 152 CompileCSharpAssembly(CompilerOptions options)153 protected virtual NPath CompileCSharpAssembly (CompilerOptions options) 154 { 155 var compilerOptions = CreateCodeDomCompilerOptions (options); 156 var provider = CodeDomProvider.CreateProvider ("C#"); 157 var result = provider.CompileAssemblyFromFile (compilerOptions, options.SourceFiles.Select (p => p.ToString ()).ToArray ()); 158 if (!result.Errors.HasErrors) 159 return compilerOptions.OutputAssembly.ToNPath (); 160 161 var errors = new StringBuilder (); 162 foreach (var error in result.Errors) 163 errors.AppendLine (error.ToString ()); 164 throw new Exception ("Compilation errors: " + errors); 165 } 166 CompileIlAssembly(CompilerOptions options)167 protected NPath CompileIlAssembly (CompilerOptions options) 168 { 169 return _ilCompiler.Compile (options); 170 } 171 CreateCodeDomCompilerOptions(CompilerOptions options)172 private CompilerParameters CreateCodeDomCompilerOptions (CompilerOptions options) 173 { 174 var compilerParameters = new CompilerParameters 175 { 176 OutputAssembly = options.OutputPath.ToString (), 177 GenerateExecutable = options.OutputPath.FileName.EndsWith (".exe") 178 }; 179 180 compilerParameters.CompilerOptions = options.Defines?.Aggregate (string.Empty, (buff, arg) => $"{buff} /define:{arg}"); 181 182 compilerParameters.ReferencedAssemblies.AddRange (options.References.Select (r => r.ToString ()).ToArray ()); 183 184 if (options.Resources != null) 185 compilerParameters.EmbeddedResources.AddRange (options.Resources.Select (r => r.ToString ()).ToArray ()); 186 187 if (options.AdditionalArguments != null) { 188 var combinedValues = options.AdditionalArguments.Aggregate (string.Empty, (buff, arg) => $"{buff} {arg}"); 189 compilerParameters.CompilerOptions = $"{compilerParameters.CompilerOptions} {combinedValues}"; 190 } 191 192 return compilerParameters; 193 } 194 } 195 }