public override void OnError(Exception ex) { string error = Utility.FlattenException(ex, s => { string baseAssemblyName = FunctionAssemblyLoader.GetAssemblyNameFromMetadata(Metadata, string.Empty); if (s != null && s.StartsWith(baseAssemblyName)) { return(Metadata.Name); } return(s); }); TraceError(error); }
private Compilation GetScriptCompilation(Script <object> script, FunctionMetadata functionMetadata) { Compilation compilation = script.GetCompilation(); if (_optimizationLevel == OptimizationLevel.Debug) { SyntaxTree scriptTree = compilation.SyntaxTrees.FirstOrDefault(t => string.IsNullOrEmpty(t.FilePath)); var debugTree = SyntaxFactory.SyntaxTree(scriptTree.GetRoot(), encoding: UTF8WithNoBOM, path: Path.GetFileName(functionMetadata.ScriptFile), options: new CSharpParseOptions(kind: SourceCodeKind.Script)); compilation = compilation .RemoveAllSyntaxTrees() .AddSyntaxTrees(debugTree); } return(compilation.WithOptions(compilation.Options.WithOptimizationLevel(_optimizationLevel)) .WithAssemblyName(FunctionAssemblyLoader.GetAssemblyNameFromMetadata(functionMetadata, compilation.AssemblyName))); }
public Task <IDotNetCompilation> GetFunctionCompilationAsync(FunctionMetadata functionMetadata) { // First use the C# compiler to resolve references, to get consistency with the C# Azure Functions programming model // Add the #r statements from the .fsx file to the resolver source string scriptSource = GetFunctionSource(functionMetadata); var resolverSourceBuilder = new StringBuilder(); using (StringReader sr = new StringReader(scriptSource)) { string line; while ((line = sr.ReadLine()) != null) { if (_hashRRegex.IsMatch(line)) { resolverSourceBuilder.AppendLine(line); } } } resolverSourceBuilder.AppendLine("using System;"); var resolverSource = resolverSourceBuilder.ToString(); Script <object> script = CodeAnalysis.CSharp.Scripting.CSharpScript.Create(resolverSource, options: _metadataResolver.CreateScriptOptions(), assemblyLoader: AssemblyLoader.Value); var compiler = new SimpleSourceCodeServices(msbuildEnabled: FSharpOption <bool> .Some(false)); FSharpErrorInfo[] errors = null; FSharpOption <Assembly> assemblyOption = null; string scriptPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(scriptPath); string scriptFilePath = Path.Combine(scriptPath, Path.GetFileName(functionMetadata.ScriptFile)); var assemblyName = FunctionAssemblyLoader.GetAssemblyNameFromMetadata(functionMetadata, Guid.NewGuid().ToString()); var assemblyFileName = Path.Combine(scriptPath, assemblyName + ".dll"); var pdbName = Path.ChangeExtension(assemblyFileName, PlatformHelper.IsMono ? "dll.mdb" : "pdb"); try { var scriptFileBuilder = new StringBuilder(); // Write an adjusted version of the script file, prefixing some 'open' declarations foreach (string import in script.Options.Imports) { scriptFileBuilder.AppendLine("open " + import); } // Suppress undesirable warnings scriptFileBuilder.AppendLine("#nowarn \"988\""); // Set the line to match the original script scriptFileBuilder.AppendLine("# 0 @\"" + functionMetadata.ScriptFile + "\""); // Add our original script scriptFileBuilder.AppendLine(scriptSource); File.WriteAllText(scriptFilePath, scriptFileBuilder.ToString()); var otherFlags = new List <string>(); otherFlags.Add("fsc.exe"); // The --noframework option is used because we will shortly add references to mscorlib and FSharp.Core // as dictated by the C# reference resolver, and F# doesn't like getting multiple references to those. otherFlags.Add("--noframework"); var references = script.GetCompilation().References .Where(m => !(m is UnresolvedMetadataReference)) .Select(m => "-r:" + m.Display) .Distinct(new FileNameEqualityComparer()); // Add the references as reported by the metadata resolver. otherFlags.AddRange(references); if (_optimizationLevel == OptimizationLevel.Debug) { otherFlags.Add("--optimize-"); otherFlags.Add("--debug+"); otherFlags.Add("--tailcalls-"); } if (PlatformHelper.IsMono) { var monoDir = Path.GetDirectoryName(typeof(string).Assembly.Location); var facadesDir = Path.Combine(monoDir, "Facades"); otherFlags.Add("--lib:" + facadesDir); } // If we have a private assembly folder, make sure the compiler uses it to resolve dependencies string privateAssembliesFolder = Path.Combine(Path.GetDirectoryName(functionMetadata.ScriptFile), DotNetConstants.PrivateAssembliesFolderName); if (Directory.Exists(privateAssembliesFolder)) { otherFlags.Add("--lib:" + Path.Combine(Path.GetDirectoryName(functionMetadata.ScriptFile), DotNetConstants.PrivateAssembliesFolderName)); } otherFlags.Add("--out:" + assemblyFileName); // Get the #load closure FSharpChecker checker = FSharpChecker.Create(null, null, null, msbuildEnabled: FSharpOption <bool> .Some(false)); var loadFileOptionsAsync = checker.GetProjectOptionsFromScript(functionMetadata.ScriptFile, scriptSource, null, null, null); var loadFileOptions = FSharp.Control.FSharpAsync.RunSynchronously(loadFileOptionsAsync, null, null); foreach (var loadedFileName in loadFileOptions.ProjectFileNames) { if (Path.GetFileName(loadedFileName) != Path.GetFileName(functionMetadata.ScriptFile)) { otherFlags.Add(loadedFileName); } } // Add the (adjusted) script file itself otherFlags.Add(scriptFilePath); // Compile the script to a static assembly var result = compiler.Compile(otherFlags.ToArray()); errors = result.Item1; var code = result.Item2; if (code == 0) { var assemblyBytes = File.ReadAllBytes(assemblyFileName); byte[] pdbBytes = null; if (File.Exists(pdbName)) { pdbBytes = File.ReadAllBytes(pdbName); } var assembly = Assembly.Load(assemblyBytes, pdbBytes); assemblyOption = FSharpOption <Assembly> .Some(assembly); } else { string message = $"F# compilation failed with arguments: {string.Join(" ", otherFlags)}"; _traceWriter.Verbose(message); _logger?.LogDebug(message); } } finally { DeleteDirectoryAsync(scriptPath, recursive: true) .ContinueWith( t => t.Exception.Handle(e => { string message = $"Unable to delete F# compilation file: {e.ToString()}"; _traceWriter.Warning(message); _logger?.LogWarning(message); return(true); }), TaskContinuationOptions.OnlyOnFaulted); } return(Task.FromResult <IDotNetCompilation>(new FSharpCompilation(errors, assemblyOption))); }
public ICompilation GetFunctionCompilation(FunctionMetadata functionMetadata) { // First use the C# compiler to resolve references, to get consistency with the C# Azure Functions programming model // Add the #r statements from the .fsx file to the resolver source string scriptSource = GetFunctionSource(functionMetadata); var resolverSourceBuilder = new StringBuilder(); using (StringReader sr = new StringReader(scriptSource)) { string line; while ((line = sr.ReadLine()) != null) { if (_hashRRegex.IsMatch(line)) { resolverSourceBuilder.AppendLine(line); } } } resolverSourceBuilder.AppendLine("using System;"); var resolverSource = resolverSourceBuilder.ToString(); Script <object> script = CodeAnalysis.CSharp.Scripting.CSharpScript.Create(resolverSource, options: _metadataResolver.CreateScriptOptions(), assemblyLoader: AssemblyLoader.Value); Compilation compilation = script.GetCompilation(); var compiler = new SimpleSourceCodeServices(msbuildEnabled: FSharpOption <bool> .Some(false)); FSharpErrorInfo[] errors = null; FSharpOption <Assembly> assemblyOption = null; string scriptFilePath = Path.Combine(Path.GetTempPath(), Path.GetFileName(functionMetadata.ScriptFile)); var asmName = FunctionAssemblyLoader.GetAssemblyNameFromMetadata(functionMetadata, compilation.AssemblyName); var dllName = Path.GetTempPath() + asmName + ".dll"; var pdbName = Path.ChangeExtension(dllName, "pdb"); try { var scriptFileBuilder = new StringBuilder(); // Write an adjusted version of the script file, prefixing some 'open' decarations foreach (string import in script.Options.Imports) { scriptFileBuilder.AppendLine("open " + import); } // Suppress undesirable warnings scriptFileBuilder.AppendLine("#nowarn \"988\""); // Set the line to match the original script scriptFileBuilder.AppendLine("# 0 @\"" + functionMetadata.ScriptFile + "\""); // Add our original script scriptFileBuilder.AppendLine(scriptSource); File.WriteAllText(scriptFilePath, scriptFileBuilder.ToString()); var otherFlags = new List <string>(); // For some reason CompileToDynamicAssembly wants "fsc.exe" as the first arg, it is ignored. otherFlags.Add("fsc.exe"); // The --noframework option is used because we will shortly add references to mscorlib and FSharp.Core // as dictated by the C# reference resolver, and F# doesn't like getting multiple references to those. otherFlags.Add("--noframework"); // Add the references as reported by the C# reference resolver. foreach (var mdr in compilation.References) { if (!mdr.Display.Contains("Unresolved ")) { otherFlags.Add("-r:" + mdr.Display); } } // Above we have used the C# reference resolver to get the basic set of DLL references for the compilation. // // However F# has its own view on default options. For scripts these should include the // following framework facade references. otherFlags.Add("-r:System.Linq.dll"); // System.Linq.Expressions.Expression<T> otherFlags.Add("-r:System.Reflection.dll"); // System.Reflection.ParameterInfo otherFlags.Add("-r:System.Linq.Expressions.dll"); // System.Linq.IQueryable<T> otherFlags.Add("-r:System.Threading.Tasks.dll"); // valuetype [System.Threading.Tasks]System.Threading.CancellationToken otherFlags.Add("-r:System.IO.dll"); // System.IO.TextWriter otherFlags.Add("-r:System.Net.Requests.dll"); // System.Net.WebResponse etc. otherFlags.Add("-r:System.Collections.dll"); // System.Collections.Generic.List<T> otherFlags.Add("-r:System.Runtime.Numerics.dll"); // BigInteger otherFlags.Add("-r:System.Threading.dll"); // OperationCanceledException otherFlags.Add("-r:System.Runtime.dll"); otherFlags.Add("-r:System.Numerics.dll"); if (_optimizationLevel == OptimizationLevel.Debug) { otherFlags.Add("--optimize-"); otherFlags.Add("--debug+"); otherFlags.Add("--tailcalls-"); } // If we have a private assembly folder, make sure the compiler uses it to resolve dependencies string privateAssembliesFolder = Path.Combine(Path.GetDirectoryName(functionMetadata.ScriptFile), DotNetConstants.PrivateAssembliesFolderName); if (Directory.Exists(privateAssembliesFolder)) { otherFlags.Add("--lib:" + Path.Combine(Path.GetDirectoryName(functionMetadata.ScriptFile), DotNetConstants.PrivateAssembliesFolderName)); } otherFlags.Add("--out:" + dllName); // Get the #load closure FSharpChecker checker = FSharpChecker.Create(null, null, null, msbuildEnabled: FSharpOption <bool> .Some(false)); var loadFileOptionsAsync = checker.GetProjectOptionsFromScript(functionMetadata.ScriptFile, scriptSource, null, null, null); var loadFileOptions = FSharp.Control.FSharpAsync.RunSynchronously(loadFileOptionsAsync, null, null); foreach (var loadedFileName in loadFileOptions.ProjectFileNames) { if (Path.GetFileName(loadedFileName) != Path.GetFileName(functionMetadata.ScriptFile)) { otherFlags.Add(loadedFileName); } } // Add the (adjusted) script file itself otherFlags.Add(scriptFilePath); // Compile the script to a static assembly var result = compiler.Compile(otherFlags.ToArray()); errors = result.Item1; var code = result.Item2; if (code == 0) { var assemblyBytes = File.ReadAllBytes(dllName); byte[] pdbBytes = null; if (File.Exists(pdbName)) { pdbBytes = File.ReadAllBytes(pdbName); } var assembly = Assembly.Load(assemblyBytes, pdbBytes); assemblyOption = FSharpOption <Assembly> .Some(assembly); } } finally { Task.WhenAll(DeleteIfExistsAsync(scriptFilePath), DeleteIfExistsAsync(dllName), DeleteIfExistsAsync(pdbName)) .ContinueWith(t => t.Exception.Handle(e => { // TODO: Trace return(true); }), TaskContinuationOptions.OnlyOnFaulted); } return(new FSharpCompilation(errors, assemblyOption)); }