diff --git a/Compiler/Compiler.cs b/Compiler/Compiler.cs index 35e45851d..558ec0ed0 100644 --- a/Compiler/Compiler.cs +++ b/Compiler/Compiler.cs @@ -2637,7 +2637,7 @@ namespace PascalABCCompiler } } - private string GetReferenceFileName(string FileName, SyntaxTree.SourceContext sc, string curr_path) + private string GetReferenceFileName(string FileName, SyntaxTree.SourceContext sc, string curr_path, bool overwrite) { if (standart_assembly_dict.ContainsKey(FileName)) return standart_assembly_dict[FileName]; @@ -2658,7 +2658,14 @@ namespace PascalABCCompiler if (System.IO.File.Exists(FullFileName)) { var NewFileName = Path.Combine(compilerOptions.OutputDirectory, Path.GetFileName(FullFileName)); - if (FullFileName != NewFileName) File.Copy(FullFileName, NewFileName, true); + if (FullFileName != NewFileName) + { + if (overwrite) + File.Copy(FullFileName, NewFileName, true); + else if (!File.Exists(NewFileName)) + File.Copy(FullFileName, NewFileName, false); + } + return NewFileName; } else @@ -2811,16 +2818,20 @@ namespace PascalABCCompiler } } + private Assembly PreloadReference(TreeRealization.compiler_directive reference) + { + var sc = GetSourceContext(reference); + var fileName = GetReferenceFileName(reference.directive, sc, Path.GetDirectoryName(reference.source_file), true); + return NetHelper.NetHelper.PreloadAssembly(fileName); + } + private CompilationUnit CompileReference(PascalABCCompiler.TreeRealization.unit_node_list Units, TreeRealization.compiler_directive cd) { - TreeRealization.location loc = cd.location; - SyntaxTree.SourceContext sc = null; - if (loc != null) - sc = new SyntaxTree.SourceContext(loc.begin_line_num, loc.begin_column_num, loc.end_line_num, loc.end_column_num, 0, 0); + var sc = GetSourceContext(cd); string UnitName = null; try { - UnitName = GetReferenceFileName(cd.directive, sc, Path.GetDirectoryName(cd.source_file)); + UnitName = GetReferenceFileName(cd.directive, sc, Path.GetDirectoryName(cd.source_file), false); } catch (AssemblyNotFound ex) { @@ -2843,6 +2854,14 @@ namespace PascalABCCompiler //throw new DLLReadingError(UnitName); throw new AssemblyReadingError(CurrentCompilationUnit.SyntaxTree.file_name, UnitName, sc); } + + private SyntaxTree.SourceContext GetSourceContext(TreeRealization.compiler_directive cd) + { + var loc = cd.location; + if (loc == null) return null; + return new SyntaxTree.SourceContext(loc.begin_line_num, loc.begin_column_num, loc.end_line_num, + loc.end_column_num, 0, 0); + } private bool HasIncludeNamespacesDirective(CompilationUnit Unit) { @@ -3030,6 +3049,8 @@ namespace PascalABCCompiler } } + + var referenceDirectives = new List(); foreach (TreeRealization.compiler_directive cd in directives) { if (cd.name.ToLower() == TreeConverter.compiler_string_consts.compiler_directive_reference) @@ -3037,16 +3058,33 @@ namespace PascalABCCompiler if (string.IsNullOrEmpty(cd.directive)) throw new TreeConverter.SimpleSemanticError(cd.location, "EXPECTED_ASSEMBLY_NAME"); else - CompileReference(res, cd); + referenceDirectives.Add(cd); } } if (CompilerOptions.ProjectCompiled) { foreach (ReferenceInfo ri in project.references) { - CompileReference(res, new TreeRealization.compiler_directive("reference", ri.full_assembly_name, null, project.MainFile)); + referenceDirectives.Add(new TreeRealization.compiler_directive("reference", ri.full_assembly_name, null, project.MainFile)); } } + + // It's important to preload all the referenced assemblies before starting the compilation. During the + // compilation, we need to access types from every referenced assembly. An attempt to access them could fail + // if a transitively dependent assembly is not loaded, yet. + // + // It's not always possible to solve by re-ordering the references, since there are cases of + // mutually-dependent assemblies (i.e. dependency loops) in the wild. + var preloadedAssemblies = new List(); + foreach (var reference in referenceDirectives) + preloadedAssemblies.Add(PreloadReference(reference)); + + using (new NetHelper.AssemblyResolveScope(AppDomain.CurrentDomain, preloadedAssemblies)) + { + foreach (var reference in referenceDirectives) + CompileReference(res, reference); + } + return res; } diff --git a/TreeConverter/NetWrappers/AssemblyResolveScope.cs b/TreeConverter/NetWrappers/AssemblyResolveScope.cs new file mode 100644 index 000000000..282dfeb3a --- /dev/null +++ b/TreeConverter/NetWrappers/AssemblyResolveScope.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace PascalABCCompiler.NetHelper +{ + /// + /// Will force the application domain to load passed assemblies when requested, while ignoring the assembly + /// versions. Handles until disposed. + /// + public class AssemblyResolveScope : IDisposable + { + private readonly AppDomain _appDomain; + private readonly Dictionary _assemblies = new Dictionary(); + + public AssemblyResolveScope(AppDomain appDomain, IEnumerable assemblies) + { + foreach (var assembly in assemblies) + { + // NOTE: assemblies with identical simple names will overwrite each other + _assemblies[assembly.GetName().Name] = assembly; + } + + _appDomain = appDomain; + _appDomain.AssemblyResolve += OnAssemblyResolve; + } + + public void Dispose() + { + _appDomain.AssemblyResolve -= OnAssemblyResolve; + } + + private Assembly OnAssemblyResolve(object _, ResolveEventArgs args) + { + var requestedName = new AssemblyName(args.Name); + if (_assemblies.TryGetValue(requestedName.Name, out var assembly)) + return assembly; + + return null; + } + } +} \ No newline at end of file diff --git a/TreeConverter/NetWrappers/NetHelper.cs b/TreeConverter/NetWrappers/NetHelper.cs index caaae12eb..95c1ed6fe 100644 --- a/TreeConverter/NetWrappers/NetHelper.cs +++ b/TreeConverter/NetWrappers/NetHelper.cs @@ -4,6 +4,7 @@ using System; using PascalABCCompiler.SemanticTree; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Reflection; using System.Threading; @@ -359,6 +360,12 @@ namespace PascalABCCompiler.NetHelper else return false; } + + public static Assembly PreloadAssembly(string name) + { + var bytes = File.ReadAllBytes(name); + return Assembly.Load(bytes); + } public static Assembly LoadAssembly(string name) { @@ -408,20 +415,10 @@ namespace PascalABCCompiler.NetHelper } } //if (!System.IO.Path.GetFileName(name).ToLower().Contains("microsoft.directx")) - try + try { - System.IO.FileStream fs = System.IO.File.OpenRead(name); - byte[] buf = new byte[fs.Length]; - fs.Read(buf, 0, (int)fs.Length); - fs.Close(); - curr_inited_assm_path = name; - a = System.Reflection.Assembly.Load(buf); - - a.GetTypes(); - buf = null; - //Thread th = new Thread(new ThreadStart(collect_internal)); - //th.Start(); - } + a = PreloadAssembly(name); + } catch (Exception ex) { a = System.Reflection.Assembly.LoadFrom(name); diff --git a/TreeConverter/TreeConverter.csproj b/TreeConverter/TreeConverter.csproj index 0988c2c6d..810ea530d 100644 --- a/TreeConverter/TreeConverter.csproj +++ b/TreeConverter/TreeConverter.csproj @@ -110,6 +110,7 @@ +