diff --git a/Source/Tools/Flax.Build/Build/Assembler.cs b/Source/Tools/Flax.Build/Build/Assembler.cs
index 0ac982667..3930d25f1 100644
--- a/Source/Tools/Flax.Build/Build/Assembler.cs
+++ b/Source/Tools/Flax.Build/Build/Assembler.cs
@@ -1,10 +1,13 @@
// Copyright (c) 2012-2020 Flax Engine. All rights reserved.
-using System;
-using System.CodeDom.Compiler;
-using System.Collections.Generic;
-using System.Linq;
+using Microsoft.CodeAnalysis;
+using System.Diagnostics;
using System.Reflection;
+using System.Runtime.Loader;
+using System.Text;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.Text;
namespace Flax.Build
{
@@ -18,7 +21,7 @@ namespace Flax.Build
///
public static readonly Assembly[] DefaultReferences =
{
- //typeof(IntPtr).Assembly, // mscorlib.dll
+ typeof(IntPtr).Assembly, // mscorlib.dll
typeof(Enumerable).Assembly, // System.Linq.dll
typeof(ISet<>).Assembly, // System.dll
typeof(Builder).Assembly, // Flax.Build.exe
@@ -29,6 +32,13 @@ namespace Flax.Build
///
public string OutputPath = null;
+ ///
+ ///
+ ///
+ public string CachePath = null;
+
+ private string CacheAssemblyPath = null;
+
///
/// The source files for compilation.
///
@@ -44,6 +54,13 @@ namespace Flax.Build
///
public readonly List References = new List();
+ public Assembler(List sourceFiles, string cachePath = null)
+ {
+ SourceFiles.AddRange(sourceFiles);
+ CachePath = cachePath;
+ CacheAssemblyPath = cachePath != null ? Path.Combine(Directory.GetParent(cachePath).FullName, "BuilderRulesCache.dll") : null;
+ }
+
///
/// Builds the assembly.
///
@@ -51,9 +68,28 @@ namespace Flax.Build
/// The created and loaded assembly.
public Assembly Build()
{
- Dictionary providerOptions = new Dictionary();
- providerOptions.Add("CompilerVersion", "v4.0");
- CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider(providerOptions);
+ DateTime recentWriteTime = DateTime.MinValue;
+ if (CachePath != null)
+ {
+ foreach (var sourceFile in SourceFiles)
+ {
+ // FIXME: compare and cache individual write times!
+ DateTime lastWriteTime = File.GetLastWriteTime(sourceFile);
+ if (lastWriteTime > recentWriteTime)
+ recentWriteTime = lastWriteTime;
+ }
+
+ DateTime cacheTime = File.Exists(CachePath)
+ ? DateTime.FromBinary(long.Parse(File.ReadAllText(CachePath)))
+ : DateTime.MinValue;
+ if (recentWriteTime <= cacheTime && File.Exists(CacheAssemblyPath))
+ {
+ Assembly cachedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(CacheAssemblyPath);
+ return cachedAssembly;
+ }
+ }
+
+ Stopwatch sw = Stopwatch.StartNew();
// Collect references
HashSet references = new HashSet();
@@ -66,49 +102,74 @@ namespace Flax.Build
if (!assembly.IsDynamic)
references.Add(assembly.Location);
}
-
- // Setup compilation options
- CompilerParameters cp = new CompilerParameters();
- cp.GenerateExecutable = false;
- cp.WarningLevel = 4;
- cp.TreatWarningsAsErrors = false;
- cp.ReferencedAssemblies.AddRange(references.ToArray());
- if (string.IsNullOrEmpty(OutputPath))
- {
- cp.GenerateInMemory = true;
- cp.IncludeDebugInformation = false;
- }
- else
- {
- cp.GenerateInMemory = false;
- cp.IncludeDebugInformation = true;
- cp.OutputAssembly = OutputPath;
- }
+ references.Add(Path.Combine(Directory.GetParent(DefaultReferences[0].Location).FullName, "System.dll"));
+ references.Add(Path.Combine(Directory.GetParent(DefaultReferences[0].Location).FullName, "System.Runtime.dll"));
+ references.Add(Path.Combine(Directory.GetParent(DefaultReferences[0].Location).FullName, "System.Collections.dll"));
+ references.Add(Path.Combine(Directory.GetParent(DefaultReferences[0].Location).FullName, "Microsoft.Win32.Registry.dll"));
// HACK: C# will give compilation errors if a LIB variable contains non-existing directories
Environment.SetEnvironmentVariable("LIB", null);
+ CSharpCompilationOptions defaultCompilationOptions =
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
+ .WithUsings(new[] { "System", })
+ .WithPlatform(Microsoft.CodeAnalysis.Platform.AnyCpu)
+ .WithOptimizationLevel(OptimizationLevel.Debug);
+
+ List defaultReferences = new List();
+ foreach (var r in references)
+ defaultReferences.Add(MetadataReference.CreateFromFile(r));
+
// Run the compilation
- CompilerResults cr = provider.CompileAssemblyFromFile(cp, SourceFiles.ToArray());
+ using var memoryStream = new MemoryStream();
+
+ var syntaxTrees = new List();
+ foreach (var sourceFile in SourceFiles)
+ {
+ var stringText = SourceText.From(File.ReadAllText(sourceFile), Encoding.UTF8);
+ var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(stringText,
+ CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9), sourceFile);
+ syntaxTrees.Add(parsedSyntaxTree);
+ }
+
+ var compilation = CSharpCompilation.Create("BuilderRulesCache.dll", syntaxTrees.ToArray(), defaultReferences, defaultCompilationOptions);
+ EmitResult emitResult = compilation.Emit(memoryStream);
// Process warnings and errors
- bool hasError = false;
- foreach (CompilerError ce in cr.Errors)
+ foreach (var diagnostic in emitResult.Diagnostics)
{
- if (ce.IsWarning)
- {
- Log.Warning(string.Format("{0} at {1}: {2}", ce.FileName, ce.Line, ce.ErrorText));
- }
- else
- {
- Log.Error(string.Format("{0} at line {1}: {2}", ce.FileName, ce.Line, ce.ErrorText));
- hasError = true;
- }
+ var msg = diagnostic.ToString();
+ if (diagnostic.Severity == DiagnosticSeverity.Warning)
+ Log.Warning(msg);
+ else if (diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error)
+ Log.Error(msg);
}
- if (hasError)
+ if (!emitResult.Success)
throw new Exception("Failed to build assembly.");
- return cr.CompiledAssembly;
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ Assembly compiledAssembly = AssemblyLoadContext.Default.LoadFromStream(memoryStream);
+
+ if (CachePath != null && CacheAssemblyPath != null)
+ {
+ memoryStream.Seek(0, SeekOrigin.Begin);
+
+ var cacheDirectory = Path.GetDirectoryName(CacheAssemblyPath);
+ if (!Directory.Exists(cacheDirectory))
+ Directory.CreateDirectory(cacheDirectory);
+
+ using (FileStream fileStream = File.Open(CacheAssemblyPath, FileMode.Create, FileAccess.Write))
+ {
+ memoryStream.CopyTo(fileStream);
+ fileStream.Close();
+ }
+
+ File.WriteAllText(CachePath, recentWriteTime.ToBinary().ToString());
+ }
+
+ sw.Stop();
+ Log.Info("Assembler time: " + sw.Elapsed.TotalSeconds.ToString() + "s");
+ return compiledAssembly;
}
}
}
diff --git a/Source/Tools/Flax.Build/Build/Builder.Rules.cs b/Source/Tools/Flax.Build/Build/Builder.Rules.cs
index 9470e3e19..43666837d 100644
--- a/Source/Tools/Flax.Build/Build/Builder.Rules.cs
+++ b/Source/Tools/Flax.Build/Build/Builder.Rules.cs
@@ -188,8 +188,8 @@ namespace Flax.Build
Assembly assembly;
using (new ProfileEventScope("CompileRules"))
{
- var assembler = new Assembler();
- assembler.SourceFiles.AddRange(files);
+ var assembler = new Assembler(files, Path.Combine(Globals.Root, Configuration.IntermediateFolder, "BuilderRules.cache"));
+ //var assembler = new Assembler(files);
assembly = assembler.Build();
}