Sharpmake/Assembler.cs (664 lines of code) (raw):

// Copyright (c) Ubisoft. All Rights Reserved. // Licensed under the Apache 2.0 License. See LICENSE.md in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; #if NET5_0 using BasicReferenceAssemblies = Basic.Reference.Assemblies.Net50; #elif NET6_0 using BasicReferenceAssemblies = Basic.Reference.Assemblies.Net60; #else #error unhandled framework version #endif namespace Sharpmake { public class Assembler { public const Options.CSharp.LanguageVersion SharpmakeScriptsCSharpVersion = Options.CSharp.LanguageVersion.CSharp10; #if NET5_0 public const DotNetFramework SharpmakeDotNetFramework = DotNetFramework.net5_0; #elif NET6_0 public const DotNetFramework SharpmakeDotNetFramework = DotNetFramework.net6_0; #else #error unhandled framework version #endif /// <summary> /// Extra user assembly to use while compiling /// </summary> public List<Assembly> Assemblies { get { return _assemblies; } } /// <summary> /// Extra user assembly file name to use while compiling/running /// </summary> public IReadOnlyList<string> References { get { return _references; } } /// <summary> /// Extra user assembly file name to use while compiling /// </summary> public IReadOnlyList<string> BuildReferences { get { return _buildReferences; } } private readonly HashSet<string> _defines; /// <summary> /// Source attribute parser to use to add configuration based on source code /// </summary> public List<ISourceAttributeParser> AttributeParsers { get { return _attributeParsers; } } /// <summary> /// Parsing flow parsers to use to add configuration based on source code /// </summary> public List<IParsingFlowParser> ParsingFlowParsers { get { return _parsingFlowParsers; } } public bool UseDefaultParsers = true; [Obsolete("Default references are always used.")] public bool UseDefaultReferences = true; public static readonly string[] DefaultReferences = BasicReferenceAssemblies.ReferenceInfos.All.Select(r => r.FileName).ToArray(); private class AssemblyInfo : IAssemblyInfo { public string Id { get; set; } public string DebugProjectName { get; set; } public Assembly Assembly { get; set; } public IReadOnlyCollection<string> SourceFiles => _sourceFiles; public IReadOnlyCollection<string> NoneFiles => _noneFiles; [Obsolete("Use RuntimeReference instead")] public IReadOnlyCollection<string> References => RuntimeReferences; public IReadOnlyCollection<string> RuntimeReferences => _runtimeReferences; public IReadOnlyCollection<string> BuildReferences => _buildReferences; public IReadOnlyDictionary<string, IAssemblyInfo> SourceReferences => _sourceReferences; public bool UseDefaultReferences { get; set; } public List<string> _sourceFiles = new List<string>(); public List<string> _noneFiles = new List<string>(); public List<string> _runtimeReferences = new List<string>(); public List<string> _buildReferences = new List<string>(); public Dictionary<string, IAssemblyInfo> _sourceReferences = new Dictionary<string, IAssemblyInfo>(); } public Assembler() : this(new HashSet<string>()) { } public Assembler(HashSet<string> defines) { _defines = defines; } public Assembly BuildAssembly(params string[] sourceFiles) { return BuildAssembly(null, sourceFiles).Assembly; } public IAssemblyInfo BuildAssembly(IBuilderContext context, params string[] sourceFiles) { // Always compile to a physic dll to be able to debug string tmpFile = GetNextTmpAssemblyFilePath(); return Build(context, tmpFile, sourceFiles); } public static TDelegate BuildDelegate<TDelegate>(string sourceFilePath, string fullFunctionName, Assembly[] assemblies) where TDelegate : class { FileInfo fileInfo = new FileInfo(sourceFilePath); if (!fileInfo.Exists) throw new Error("source file name not found: {0}", sourceFilePath); Type delegateType = typeof(TDelegate); Error.Valid(IsDelegate(delegateType), "BuildDelegate<FUNC_TYPE>(), FUNC_TYPE is not a delegate"); MethodInfo delegateMethodInfo = GetDelegateMethodInfo(delegateType); ParameterInfo[] delegateParameterInfos = delegateMethodInfo.GetParameters(); ParameterInfo delegateReturnInfos = delegateMethodInfo.ReturnParameter; Assembler assembler = new Assembler(); assembler.AddSharpmakeAssemblies(); assembler.Assemblies.AddRange(assemblies); Assembly assembly = assembler.BuildAssembly(fileInfo.FullName); List<MethodInfo> matchMethods = new List<MethodInfo>(); foreach (Type type in assembly.GetTypes()) { MethodInfo[] methodInfos = type.GetMethods(); foreach (MethodInfo methodInfo in methodInfos) { string fullName = methodInfo.DeclaringType.FullName + "." + methodInfo.Name; if (fullFunctionName == fullName && methodInfo.IsStatic && methodInfo.GetParameters().Length == delegateMethodInfo.GetParameters().Length) { ParameterInfo[] parameterInfos = methodInfo.GetParameters(); ParameterInfo returnInfos = methodInfo.ReturnParameter; bool equal = (returnInfos.GetType() == delegateReturnInfos.GetType() && parameterInfos.Length == delegateParameterInfos.Length); if (equal) { for (int i = 0; i < parameterInfos.Length; ++i) { if (parameterInfos[i].GetType() != delegateParameterInfos[i].GetType()) { equal = false; break; } } } if (equal) matchMethods.Add(methodInfo); } } } if (matchMethods.Count != 1) throw new Error("Cannot find method name {0} that match {1} in {2}", fullFunctionName, delegateMethodInfo.ToString(), sourceFilePath); MethodInfo method = matchMethods[0]; // bind the method Delegate returnDelegate; try { returnDelegate = method.CreateDelegate(delegateType); InternalError.Valid(returnDelegate != null); } catch (Exception e) { throw new InternalError(e); } TDelegate result = returnDelegate as TDelegate; InternalError.Valid(result != null, "Cannot cast built delegate into user delegate"); return result; } public static TDelegate BuildDelegate<TDelegate>(string functionBody, string functionNamespace, string[] usingNamespaces, Assembly[] assemblies) where TDelegate : class { Assembler assembler = new Assembler(); assembler.AddSharpmakeAssemblies(); assembler.Assemblies.AddRange(assemblies); const string className = "AssemblerBuildFunction_Class"; const string methodName = "AssemblerBuildFunction_Method"; // Fix : Bug with -> Path.GetTempFileName // http://msdn.microsoft.com/en-ca/library/windows/desktop/aa364991(v=vs.85).aspx // Limit of 65535 limit on files when generating the temp file. New temp file will use // a new Guid as filename and Sharpmake will clean the temporary files when done by aggregating // the temp files and deleting them. // eg. "C:\\fastbuild-work\\85f7d472c25d494ca09f2ea7fe282d50" //string sourceTmpFile = Path.GetTempFileName(); string sourceTmpFile = Path.Combine(Path.GetTempPath(), (Guid.NewGuid().ToString("N") + ".tmp.sharpmake.cs")); Type delegateType = typeof(TDelegate); Error.Valid(IsDelegate(delegateType), "BuildDelegate<TDelegate>(), TDelegate is not a delegate"); MethodInfo methodInfo = GetDelegateMethodInfo(delegateType); using (StreamWriter writer = new StreamWriter(sourceTmpFile)) { // add using namespace... foreach (string usingNamespace in usingNamespaces) writer.WriteLine("using {0};", usingNamespace); writer.WriteLine(); // namespace name writer.WriteLine("namespace {0}", functionNamespace); writer.WriteLine("{"); writer.WriteLine(" public static class {0}", className); writer.WriteLine(" {"); // write method signature string returnTypeName = methodInfo.ReturnType == typeof(void) ? "void" : methodInfo.ReturnType.FullName; writer.Write(" public static {0} {1}(", returnTypeName, methodName); ParameterInfo[] parameters = methodInfo.GetParameters(); for (int i = 0; i < parameters.Length; i++) { string parametersName = parameters[i].Name; string parametersType = (parameters[i].ParameterType == typeof(object)) ? "Object" : parameters[i].ParameterType.FullName; writer.Write("{0}{1} {2}", i == 0 ? "" : ", ", parametersType, parametersName); } writer.WriteLine(")"); // write method body writer.WriteLine(" {"); writer.WriteLine(" {0}" + Environment.NewLine, functionBody.Replace("\n", "\n ")); writer.WriteLine(" }"); writer.WriteLine(" }"); writer.WriteLine("}"); } // build in memory Assembly assembly = assembler.Build(builderContext: null, libraryFile: null, sources: sourceTmpFile).Assembly; InternalError.Valid(assembly != null); // Try to delete tmp file to prevent pollution, but useful while debugging //if (!System.Diagnostics.Debugger.IsAttached) Util.TryDeleteFile(sourceTmpFile); // Scan assembly to find our tmp class string fullClassName = functionNamespace + "." + className; Type buildedType = assembly.GetType(fullClassName); // get out method to bind into the delegate MethodInfo builtMethod = buildedType.GetMethod(methodName); InternalError.Valid(builtMethod != null); // bind the method Delegate returnDelegate; try { returnDelegate = builtMethod.CreateDelegate(delegateType); InternalError.Valid(returnDelegate != null); } catch (Exception e) { throw new InternalError(e); } TDelegate result = returnDelegate as TDelegate; InternalError.Valid(result != null, "Cannot cast built delegate into user delegate"); return result; } #region Internal internal delegate void OutputDelegate(string message, params object[] args); internal static event OutputDelegate EventOutputError; internal static event OutputDelegate EventOutputWarning; internal void AddSharpmakeAssemblies() { // Add sharpmake assembly Assemblies.Add(s_sharpmakeAssembly.Value); // Add generators and common platforms assemblies to be able to reference them from .sharpmake.cs files Assemblies.Add(s_sharpmakeGeneratorAssembly.Value); Assemblies.Add(s_sharpmakeCommonPlatformsAssembly.Value); } #endregion #region Private private List<Assembly> _assemblies = new List<Assembly>(); private List<string> _references = new List<string>(); private List<string> _buildReferences = new List<string>(); private List<ISourceAttributeParser> _attributeParsers = new List<ISourceAttributeParser>(); private List<IParsingFlowParser> _parsingFlowParsers = new List<IParsingFlowParser>(); private static readonly Lazy<Assembly> s_sharpmakeAssembly = new Lazy<Assembly>(() => Assembly.GetAssembly(typeof(Builder))); private static readonly Lazy<Assembly> s_sharpmakeGeneratorAssembly = new Lazy<Assembly>(() => { DirectoryInfo entryDirectoryInfo = new DirectoryInfo(Path.GetDirectoryName(s_sharpmakeAssembly.Value.Location)); string generatorsAssembly = Path.Combine(entryDirectoryInfo.FullName, "Sharpmake.Generators.dll"); return Assembly.LoadFrom(generatorsAssembly); }); private static readonly Lazy<Assembly> s_sharpmakeCommonPlatformsAssembly = new Lazy<Assembly>(() => { DirectoryInfo entryDirectoryInfo = new DirectoryInfo(Path.GetDirectoryName(s_sharpmakeAssembly.Value.Location)); string generatorsAssembly = Path.Combine(entryDirectoryInfo.FullName, "Sharpmake.CommonPlatforms.dll"); return Assembly.LoadFrom(generatorsAssembly); }); private static bool IsDelegate(Type delegateType) { if (delegateType.BaseType != typeof(MulticastDelegate)) return false; MethodInfo invoke = delegateType.GetMethod("Invoke"); return (invoke != null); } private static MethodInfo GetDelegateMethodInfo(Type delegateType) { if (!IsDelegate(delegateType)) throw new Error("not a delegate: {0}", delegateType); return delegateType.GetMethod("Invoke"); } private class AssemblerContext : IAssemblerContext { private readonly Assembler _assembler; private readonly AssemblyInfo _assemblyInfo; public IReadOnlyList<string> SourceFiles => _assemblyInfo.SourceFiles.ToList(); private Strings _visiting; public readonly List<IParsingFlowParser> AllParsingFlowParsers; public readonly List<ISourceAttributeParser> AllParsers; public List<ISourceAttributeParser> ImportedParsers = new List<ISourceAttributeParser>(); private readonly IBuilderContext _builderContext; public AssemblerContext(Assembler assembler, AssemblyInfo assemblyInfo, IBuilderContext builderContext, string[] sources) { _assembler = assembler; _assemblyInfo = assemblyInfo; _builderContext = builderContext; AllParsers = assembler.ComputeParsers(); AllParsingFlowParsers = assembler.ComputeParsingFlowParsers(); _assemblyInfo._sourceFiles.AddRange(sources); _visiting = new Strings(new FileSystemStringComparer(), sources); } public void AddSourceFile(string file) { if (!_visiting.Contains(file)) { _assemblyInfo._sourceFiles.Add(file); _visiting.Add(file); } } public void AddNoneFile(string file) { if (!_assemblyInfo._noneFiles.Contains(file)) _assemblyInfo._noneFiles.Add(file); } [Obsolete("Use AddRuntimeReference() instead")] public void AddReference(string file) => AddRuntimeReference(file); public void AddRuntimeReference(string file) { if (!_assemblyInfo._runtimeReferences.Contains(file)) { _assemblyInfo._runtimeReferences.Add(file); var loadInfo = _builderContext.LoadExtension(file); this.AddSourceAttributeParsers(loadInfo.Parsers); } } public void AddBuildReference(string file) { if (!_assemblyInfo._buildReferences.Contains(file)) { _assemblyInfo._buildReferences.Add(file); } } [Obsolete("Use AddRuntimeReference() instead")] public void AddReference(IAssemblyInfo info) => AddRuntimeReference(info); public void AddRuntimeReference(IAssemblyInfo info) { if (info.Assembly == null) { _assemblyInfo._sourceReferences.Add(info.Id, info); } else if (!_assemblyInfo._runtimeReferences.Contains(info.Id)) { _assemblyInfo._runtimeReferences.Add(info.Assembly.Location); _assemblyInfo._sourceReferences.Add(info.Id, info); } } public void AddSourceAttributeParser(ISourceAttributeParser parser) { AllParsers.Add(parser); ImportedParsers.Add(parser); } public IAssemblyInfo BuildAndLoadSharpmakeFiles(params string[] files) { if (_builderContext == null) throw new NotSupportedException("BuildAndLoadSharpmakeFiles is not supported on builds without a IBuilderContext"); var loadInfo = _builderContext.BuildAndLoadSharpmakeFiles(AllParsers, AllParsingFlowParsers, files); this.AddSourceAttributeParsers(loadInfo.Parsers); return loadInfo.AssemblyInfo; } public void SetDebugProjectName(string name) { _assemblyInfo.DebugProjectName = name; } public void AddDefine(string define) { _builderContext.AddDefine(define); } } private IAssemblyInfo Build(IBuilderContext builderContext, string libraryFile, params string[] sources) { var assemblyInfo = LoadAssemblyInfo(builderContext, sources); HashSet<string> references = GetReferencesForBuild(); assemblyInfo.Assembly = Compile(builderContext, assemblyInfo.SourceFiles.ToArray(), libraryFile, references); assemblyInfo.Id = assemblyInfo.Assembly.Location; return assemblyInfo; } private ConcurrentDictionary<string, string> _buildReferenceFullNames = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase); private HashSet<string> GetReferencesForBuild() { // First find the assembly Parallel.ForEach(_buildReferences, (buildAssemblyfile) => { string fullPath = AssemblyName.GetAssemblyName(buildAssemblyfile).FullName; _buildReferenceFullNames.TryAdd(fullPath, buildAssemblyfile); }); var references = new ConcurrentDictionary<string, bool>(); // Search if we have a more suitable build reference for each runtime reference Parallel.ForEach(_references, (assemblyFile) => { var assemblyFullName = AssemblyName.GetAssemblyName(assemblyFile).FullName; string buildAssemblyFile = null; _buildReferenceFullNames.TryGetValue(assemblyFullName, out buildAssemblyFile); references.TryAdd(buildAssemblyFile ?? assemblyFile, true); }); foreach (Assembly assembly in _assemblies) { if (!assembly.IsDynamic) references.TryAdd(assembly.Location, true); } return references.Keys.ToHashSet<string>(); } private SourceText ReadSourceCode(string path) { using (var stream = File.OpenRead(path)) return SourceText.From(stream, Encoding.Default); } private Assembly Compile(IBuilderContext builderContext, string[] files, string libraryFile, HashSet<string> references) { // Parse all files var syntaxTrees = new ConcurrentBag<SyntaxTree>(); var parseOptions = new CSharpParseOptions(ConvertSharpmakeOptionToLanguageVersion(SharpmakeScriptsCSharpVersion), DocumentationMode.None, preprocessorSymbols: _defines); Parallel.ForEach(files, f => { var sourceText = ReadSourceCode(f); syntaxTrees.Add(CSharpSyntaxTree.ParseText(sourceText, parseOptions, path: f)); }); return Compile(builderContext, syntaxTrees, libraryFile, references); } private Assembly Compile(IBuilderContext builderContext, IEnumerable<SyntaxTree> syntaxTrees, string libraryFile, HashSet<string> fileReferences) { // Add references var metadataReferences = new List<MetadataReference>(); foreach (var reference in fileReferences.Where(r => !string.IsNullOrEmpty(r))) { // Skip references that are already provided by the runtime if (BasicReferenceAssemblies.References.All.Any(a => string.Equals(Path.GetFileName(reference), a.FilePath, StringComparison.OrdinalIgnoreCase))) continue; metadataReferences.Add(MetadataReference.CreateFromFile(reference)); } metadataReferences.AddRange(BasicReferenceAssemblies.References.All); // suppress assembly redirect warnings // cf. https://github.com/dotnet/roslyn/issues/19640 var noWarn = new List<KeyValuePair<string, ReportDiagnostic>> { new KeyValuePair<string, ReportDiagnostic>("CS1701", ReportDiagnostic.Suppress), new KeyValuePair<string, ReportDiagnostic>("CS1702", ReportDiagnostic.Suppress), }; // Compile var compilationOptions = new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, optimizationLevel: (builderContext == null || builderContext.DebugScripts) ? OptimizationLevel.Debug : OptimizationLevel.Release, warningLevel: 4, specificDiagnosticOptions: noWarn, deterministic: true ); var assemblyName = libraryFile != null ? Path.GetFileNameWithoutExtension(libraryFile) : $"Sharpmake_{new Random().Next():X8}" + GetHashCode(); var compilation = CSharpCompilation.Create(assemblyName, syntaxTrees, metadataReferences, compilationOptions); string pdbFilePath = libraryFile != null ? Path.ChangeExtension(libraryFile, ".pdb") : null; using (var dllStream = new MemoryStream()) using (var pdbStream = new MemoryStream()) { EmitResult result = compilation.Emit( dllStream, pdbStream, options: new EmitOptions( debugInformationFormat: DebugInformationFormat.PortablePdb, pdbFilePath: pdbFilePath ) ); bool throwErrorException = builderContext == null || builderContext.CompileErrorBehavior == BuilderCompileErrorBehavior.ThrowException; LogCompilationResult(result, throwErrorException); if (result.Success) { if (libraryFile != null) { dllStream.Seek(0, SeekOrigin.Begin); using (var fileStream = new FileStream(libraryFile, FileMode.Create)) dllStream.CopyTo(fileStream); pdbStream.Seek(0, SeekOrigin.Begin); using (var pdbFileStream = new FileStream(pdbFilePath, FileMode.Create)) pdbStream.CopyTo(pdbFileStream); return Assembly.LoadFrom(libraryFile); } return Assembly.Load(dllStream.GetBuffer(), pdbStream.GetBuffer()); } } return null; } private void LogCompilationResult(EmitResult result, bool throwErrorException) { string errorMessage = ""; foreach (var diagnostic in result.Diagnostics) { if (diagnostic.Severity == DiagnosticSeverity.Error) EventOutputError?.Invoke("{0}" + Environment.NewLine, diagnostic.ToString()); else // catch everything else as warning EventOutputWarning?.Invoke("{0}" + Environment.NewLine, diagnostic.ToString()); errorMessage += diagnostic + Environment.NewLine; } if (!result.Success && throwErrorException) throw new Error(errorMessage); } private List<ISourceAttributeParser> ComputeParsers() { var parsers = AttributeParsers.ToList(); if (UseDefaultParsers) AddDefaultParsers(parsers); return parsers; } private List<IParsingFlowParser> ComputeParsingFlowParsers() { List<IParsingFlowParser> parsers = ParsingFlowParsers.ToList(); if (UseDefaultParsers) AddDefaultParsingFlowParsers(parsers); return parsers; } private AssemblyInfo LoadAssemblyInfo(IBuilderContext builderContext, string[] sources) { var assemblyInfo = new AssemblyInfo() { Id = string.Join(";", sources), }; var context = new AssemblerContext(this, assemblyInfo, builderContext, sources); AnalyseSourceFiles(context); _references.AddRange(assemblyInfo.RuntimeReferences); _buildReferences.AddRange(assemblyInfo.BuildReferences); return assemblyInfo; } internal IAssemblyInfo LoadUncompiledAssemblyInfo(IBuilderContext context, string[] sources) { return LoadAssemblyInfo(context, sources); } internal List<string> GetSourceFiles(IBuilderContext builderContext, string[] sources) { var assemblyInfo = new AssemblyInfo() { Id = string.Join(";", sources), }; var context = new AssemblerContext(this, assemblyInfo, builderContext, sources); AnalyseSourceFiles(context); return assemblyInfo.SourceFiles.ToList(); } private void AddDefaultParsers(ICollection<ISourceAttributeParser> parsers) { parsers.Add(new IncludeAttributeParser()); parsers.Add(new ReferenceAttributeParser()); parsers.Add(new PackageAttributeParser()); } private void AddDefaultParsingFlowParsers(ICollection<IParsingFlowParser> parsers) { parsers.Add(new PreprocessorConditionParser(_defines)); } private void AnalyseSourceFiles(AssemblerContext context) { var newParsers = Enumerable.Empty<ISourceAttributeParser>(); var allParsers = context.AllParsers.ToList(); // Copy, as it may be modified when parsing other files int partiallyParsedCount = 0; do { // Get all using namespace from sourceFiles for (int i = 0; i < context.SourceFiles.Count; ++i) { string sourceFile = context.SourceFiles[i]; if (File.Exists(sourceFile)) { AnalyseSourceFile(sourceFile, (i < partiallyParsedCount) ? newParsers : allParsers, context.AllParsingFlowParsers, context); } else { throw new Error("source file not found: " + sourceFile); } } // Get parsers discovered while parsing these files // We need to reparse all files currently in the list (partiallyParsedCount) again with the new parsers only, // and all files discovered after this with all the parsers. newParsers = context.ImportedParsers; context.ImportedParsers = new List<ISourceAttributeParser>(); allParsers.AddRange(newParsers); partiallyParsedCount = context.SourceFiles.Count; } while (newParsers.Any()); } internal void ParseSourceAttributesFromLine( string line, FileInfo sourceFilePath, int lineNumber, IAssemblerContext context ) { ParseSourceAttributesFromLine(line, sourceFilePath, lineNumber, ComputeParsers(), context); } internal void ParseSourceAttributesFromLine( string line, FileInfo sourceFilePath, int lineNumber, IEnumerable<ISourceAttributeParser> parsers, IAssemblerContext context ) { foreach (var parser in parsers) { parser.ParseLine(line, sourceFilePath, lineNumber, context); } } private void AnalyseSourceFile(string sourceFile, IEnumerable<ISourceAttributeParser> parsers, IEnumerable<IParsingFlowParser> flowParsers, IAssemblerContext context) { using (StreamReader reader = new StreamReader(sourceFile)) { FileInfo sourceFilePath = new FileInfo(sourceFile); List<IParsingFlowParser> flowParsersList = flowParsers.ToList(); foreach (IParsingFlowParser parsingFlowParser in flowParsersList) { parsingFlowParser.FileParsingBegin(sourceFile); } int lineNumber = 0; string line = reader.ReadLine()?.TrimStart(); while (line != null) { ++lineNumber; if (!string.IsNullOrEmpty(line) && line.StartsWith("namespace", StringComparison.Ordinal)) break; // First, update the parsing flow with the current line foreach (IParsingFlowParser parsingFlowParser in flowParsersList) { parsingFlowParser.ParseLine(line, sourceFilePath, lineNumber, context); } // We only want to parse the lines inside valid blocks if (!flowParsersList.Any() || flowParsersList.All(p => p.ShouldParseLine())) { ParseSourceAttributesFromLine(line, sourceFilePath, lineNumber, parsers, context); } line = reader.ReadLine()?.TrimStart(); } foreach (IParsingFlowParser parsingFlowParser in flowParsersList) { parsingFlowParser.FileParsingEnd(sourceFile); } } } [Obsolete] public static IEnumerable<string> EnumeratePathToDotNetFramework() { yield break; } private static LanguageVersion ConvertSharpmakeOptionToLanguageVersion(Options.CSharp.LanguageVersion languageVersion) { switch (languageVersion) { case Options.CSharp.LanguageVersion.LatestMajorVersion: return LanguageVersion.LatestMajor; case Options.CSharp.LanguageVersion.LatestMinorVersion: return LanguageVersion.Latest; case Options.CSharp.LanguageVersion.Preview: return LanguageVersion.Preview; case Options.CSharp.LanguageVersion.ISO1: return LanguageVersion.CSharp1; case Options.CSharp.LanguageVersion.ISO2: return LanguageVersion.CSharp2; case Options.CSharp.LanguageVersion.CSharp3: return LanguageVersion.CSharp3; case Options.CSharp.LanguageVersion.CSharp4: return LanguageVersion.CSharp4; case Options.CSharp.LanguageVersion.CSharp5: return LanguageVersion.CSharp5; case Options.CSharp.LanguageVersion.CSharp6: return LanguageVersion.CSharp6; case Options.CSharp.LanguageVersion.CSharp7: return LanguageVersion.CSharp7; case Options.CSharp.LanguageVersion.CSharp7_1: return LanguageVersion.CSharp7_1; case Options.CSharp.LanguageVersion.CSharp7_2: return LanguageVersion.CSharp7_2; case Options.CSharp.LanguageVersion.CSharp7_3: return LanguageVersion.CSharp7_3; case Options.CSharp.LanguageVersion.CSharp8: return LanguageVersion.CSharp8; case Options.CSharp.LanguageVersion.CSharp9: return LanguageVersion.CSharp9; case Options.CSharp.LanguageVersion.CSharp10: return LanguageVersion.CSharp10; default: throw new NotImplementedException($"Don't know how to convert sharpmake option {languageVersion} to language version"); } } [Obsolete] public static string GetAssemblyDllPath(string fileName) { foreach (string frameworkDirectory in EnumeratePathToDotNetFramework()) { string result = Path.Combine(frameworkDirectory, fileName); if (File.Exists(result)) return result; } return null; } /// <summary> /// Static constructor called at executable init time /// </summary> static Assembler() { CleanupTmpAssemblies(); } /// <summary> /// This method is intended to be called at executable init time. /// It let us avoid exceptions when executing sharpmake several times in loops(exception can occur in the cs compiler /// when it tries to create pdb files and some already exists. Maybe that previous sharpmake sometimes still has some handles to the file?). /// With this cleanup code active there is no exception anymore on my PC. Previously I had the exception almost 100% on the second or third iteration /// of a stability test(executing sharpmake in loop to insure it always generate the same thing). /// </summary> /// <remarks> /// Was previously having the following exception when running stability tests(on subsequents sharpmake execution runs): /// Unexpected error creating debug information file 'c:\Users\xxxx\AppData\Local\Temp\Sharpmake.Assembler_1.tmp.PDB' -- 'c:\Users\xxxx\AppData\Local\Temp\Sharpmake.Assembler_1.tmp.pdb: The process cannot access the file because it is being used by another process. /// </remarks> private static void CleanupTmpAssemblies() { // Erase any remaining file that has the prefix that will be used for temporary assemblies(dll, pdb, etc...) // This avoids exceptions occurring when executing sharpmake several times in loops(for example when running stability tests) string[] oldTmpFiles = Directory.GetFiles(GetTmpAssemblyBasePath(), GetTmpAssemblyFilePrefix() + "*.*", SearchOption.TopDirectoryOnly); foreach (string f in oldTmpFiles) { Util.TryDeleteFile(f); } } /// <summary> /// Get the base path of temporary assembly files. /// </summary> /// <returns>the base path</returns> private static string GetTmpAssemblyBasePath() { return Path.GetTempPath(); } /// <summary> /// Get the assembly files common prefixes for all temporary assemblies generated in this process. /// </summary> /// <returns>the prefix</returns> private static string GetTmpAssemblyFilePrefix() { // Now taking into account the working directory when setting the temporary assembly prefix. // That is useful to be able to run several sharpmake concurrently with /sharpmakemutexsuffix otherwise they can cause harm to each others. // Note: Util.BuildGuid is converting the argument to a MD5. string md5WorkingDir = Util.BuildGuid(Environment.CurrentDirectory).ToString().ToLower(); return $"Sharpmake_Assembly_{md5WorkingDir}_"; } private static int s_nextTempFile = 0; // Index of last assembly temporary file /// <summary> /// Get the next temporary assembly file path. /// </summary> /// <returns>path of next temporary assembly</returns> private string GetNextTmpAssemblyFilePath() { // try to re use the same file name to not pollute tmp directory string tmpFileBasePath = GetTmpAssemblyBasePath(); string tmpFileSuffix = ".tmp.dll"; int currentTempFile = Interlocked.Increment(ref s_nextTempFile); string tmpFile = Path.Combine(GetTmpAssemblyBasePath(), GetTmpAssemblyFilePrefix() + currentTempFile + tmpFileSuffix); return tmpFile; } #endregion } }