// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.Reflection; using Cake.Core.Scripting; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Scripting; namespace Cake.Scripting.Roslyn { internal sealed class RoslynScriptSession : IScriptSession { private readonly IScriptHost _host; private readonly IAssemblyLoader _loader; private readonly ICakeLog _log; private readonly CakeOptions _options; public HashSet ReferencePaths { get; } public HashSet References { get; } public HashSet Namespaces { get; } public RoslynScriptSession(IScriptHost host, IAssemblyLoader loader, ICakeLog log, CakeOptions options) { _host = host; _loader = loader; _log = log; _options = options; ReferencePaths = new HashSet(PathComparer.Default); References = new HashSet(); Namespaces = new HashSet(StringComparer.Ordinal); } public void AddReference(Assembly assembly) { if (assembly == null) { throw new ArgumentNullException(nameof(assembly)); } _log.Debug("Adding assembly reference to {0}...", new FilePath(assembly.Location).GetFilename().FullPath); References.Add(assembly); } public void AddReference(FilePath path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } _log.Debug("Adding reference to {0}...", path.GetFilename().FullPath); #if NETCORE References.Add(_loader.Load(path, true)); #else ReferencePaths.Add(path); #endif } public void ImportNamespace(string @namespace) { if (!string.IsNullOrWhiteSpace(@namespace) && !Namespaces.Contains(@namespace)) { _log.Debug("Importing namespace {0}...", @namespace); Namespaces.Add(@namespace); } } public void Execute(Script script) { var assemblyPath = @"C:\temp\cake-cache\script.dll"; if (System.IO.File.Exists(assemblyPath)) { RunScriptAssembly(assemblyPath); return; } // Generate the script code. var generator = new RoslynCodeGenerator(); var code = generator.Generate(script); // Create the script options dynamically. var options = Microsoft.CodeAnalysis.Scripting.ScriptOptions.Default .AddImports(Namespaces) .AddReferences(References) .AddReferences(ReferencePaths.Select(r => r.FullPath)) .WithEmitDebugInformation(_options.PerformDebug) .WithMetadataResolver(Microsoft.CodeAnalysis.Scripting.ScriptMetadataResolver.Default); var roslynScript = CSharpScript.Create(code, options, _host.GetType()); /* var compilation = roslynScript.GetCompilation(); var diagnostics = compilation.GetDiagnostics(); */ var compilation = roslynScript.GetCompilation(); var emitResult = compilation.Emit(assemblyPath); if (emitResult.Success) { RunScriptAssembly(assemblyPath); } /* var errors = new List(); foreach (var diagnostic in diagnostics) { switch (diagnostic.Severity) { case DiagnosticSeverity.Info: _log.Information(diagnostic.ToString()); break; case DiagnosticSeverity.Warning: _log.Warning(diagnostic.ToString()); break; case DiagnosticSeverity.Error: _log.Error(diagnostic.ToString()); errors.Add(diagnostic); break; default: break; } } if (errors.Any()) { var errorMessages = string.Join(Environment.NewLine, errors.Select(x => x.ToString())); var message = string.Format(CultureInfo.InvariantCulture, "Error(s) occurred when compiling build script:{0}{1}", Environment.NewLine, errorMessages); throw new CakeException(message); } using (new ScriptAssemblyResolver(_log)) { roslynScript.RunAsync(_host).Wait(); } */ } private void RunScriptAssembly(string assemblyPath) { var assembly = Assembly.LoadFile(assemblyPath); var type = assembly.GetType("Submission#0"); var factoryMethod = type.GetMethod("", new[] { typeof(object[]) }); using (new ScriptAssemblyResolver(_log)) { try { var task = (System.Threading.Tasks.Task)factoryMethod.Invoke(null, new object[] { new object[] { _host, null } }); task.Wait(); } catch (TargetInvocationException ex) { throw ex.InnerException; } } } } }