protected override void Compile(string code, string[] references) { var sourcePath = Path.GetTempFileName() + ".fs"; File.WriteAllText(sourcePath, code); var compilerArgs = new[] { "fsc", sourcePath, $"--out:{FileName}", $"--pdb:{PdbName}", "--debug", "--target:library" } .Concat(GetStandardReferences().Concat(references).Select(r => $"--reference:{r}")) .ToArray(); var compiler = new SimpleSourceCodeServices(); var result = compiler.Compile(compilerArgs); if (result.Item2 != 0) { var errors = result.Item1 .Select(e => $"{e.FileName}({e.StartLineAlternate},{e.StartColumn}): {(e.Severity.IsError ? "error" : "warning")} {e.ErrorNumber}: {e.Message}"); throw new InvalidOperationException($"Compilation Failed:{Environment.NewLine}{string.Join(Environment.NewLine, errors)}"); } }
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; byte[] assemblyBytes = null; byte[] pdbBytes = null; string scriptPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(scriptPath); string scriptFilePath = Path.Combine(scriptPath, Path.GetFileName(functionMetadata.ScriptFile)); var assemblyName = Utility.GetAssemblyNameFromMetadata(functionMetadata, Guid.NewGuid().ToString()); var assemblyFileName = Path.Combine(scriptPath, assemblyName + ".dll"); var pdbName = Path.ChangeExtension(assemblyFileName, PlatformHelper.IsWindows ? "pdb" : "dll.mdb"); 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-"); } // TODO: FACAVAL verify if this still applies to core if (!PlatformHelper.IsWindows) { 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( filename: functionMetadata.ScriptFile, source: scriptSource, loadedTimeStamp: null, otherFlags: null, useFsiAuxLib: null, assumeDotNetFramework: null, extraProjectInfo: null); var loadFileOptions = FSharp.Control.FSharpAsync.RunSynchronously(loadFileOptionsAsync, null, null); foreach (var loadedFileName in loadFileOptions.Item1.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) { assemblyBytes = File.ReadAllBytes(assemblyFileName); pdbBytes = null; if (File.Exists(pdbName)) { pdbBytes = File.ReadAllBytes(pdbName); } } else { string message = $"F# compilation failed with arguments: {string.Join(" ", otherFlags)}"; _logger.LogDebug(message); } } finally { DeleteDirectoryAsync(scriptPath, recursive: true) .ContinueWith( t => t.Exception.Handle(e => { string message = $"Unable to delete F# compilation file: {e.ToString()}"; _logger.LogWarning(message); return(true); }), TaskContinuationOptions.OnlyOnFaulted); } return(Task.FromResult <IDotNetCompilation>(new FSharpCompilation(errors, assemblyBytes, pdbBytes))); }
public IDiagnosticResult Emit(string outputPath, bool emitPdb, bool emitDocFile, bool emitExe = false) { var tempBasePath = Path.Combine(outputPath, _project.Name, "obj"); var outputDll = Path.Combine(outputPath, _project.Name + (emitExe ? ".exe" : ".dll")); // csc /out:foo.dll / target:library Program.cs var fscArgBuilder = new List <string>(); fscArgBuilder.Add("fsc.exe"); fscArgBuilder.Add("--noframework"); fscArgBuilder.Add("--nologo"); fscArgBuilder.Add("--out:" + outputDll); fscArgBuilder.Add("--target:" + (emitExe ? "exe" : "library")); Directory.CreateDirectory(tempBasePath); if (emitPdb) { var pdb = Path.Combine(outputPath, _project.Name + ".pdb"); fscArgBuilder.Add("--debug"); fscArgBuilder.Add("--optimize-"); fscArgBuilder.Add("--tailcalls-"); fscArgBuilder.Add("--pdb:" + pdb); } if (emitDocFile) { var doc = Path.Combine(outputPath, _project.Name + ".xml"); fscArgBuilder.Add("--doc:" + doc); } // F# cares about order so assume that the files were listed in order fscArgBuilder.AddRange(_project.SourceFiles); var tempFiles = new List <string>(); // These are the metadata references being used by your project. // Everything in your project.json is resolved and normailzed here: // - Project references // - Package references are turned into the appropriate assemblies // - Assembly neutral references // Each IMetadaReference maps to an assembly foreach (var reference in _metadataReferences) { // Skip this project if (reference.Name == typeof(FSharpProjectReference).Assembly.GetName().Name) { continue; } // NuGet references var fileReference = reference as IMetadataFileReference; if (fileReference != null) { fscArgBuilder.Add(@"-r:" + fileReference.Path); } // Assembly neutral references var embeddedReference = reference as IMetadataEmbeddedReference; if (embeddedReference != null) { var tempEmbeddedPath = Path.Combine(tempBasePath, reference.Name + ".dll"); // Write the ANI to disk for csc File.WriteAllBytes(tempEmbeddedPath, embeddedReference.Contents); fscArgBuilder.Add("-r:" + tempEmbeddedPath); tempFiles.Add(tempEmbeddedPath); } var projectReference = reference as IMetadataProjectReference; if (projectReference != null) { // You can write the reference assembly to the stream // and add the reference to your compiler var tempProjectDll = Path.Combine(tempBasePath, reference.Name + ".dll"); using (var fs = File.OpenWrite(tempProjectDll)) { projectReference.EmitReferenceAssembly(fs); } fscArgBuilder.Add(@"-r:" + tempProjectDll); tempFiles.Add(tempProjectDll); } } // For debugging // Console.WriteLine(fscArgs.ToString()); var scs = new SimpleSourceCodeServices(); var result = scs.Compile(fscArgBuilder.ToArray()); var warnings = result.Item1.Where(i => i.Severity.IsWarning).Select(i => i.ToString()).ToArray(); var errors = result.Item1.Where(i => i.Severity.IsError).Select(i => i.ToString()).ToArray(); if (result.Item2 != 0) { return(new DiagnosticResult(success: false, warnings: warnings, errors: errors)); } // Nuke the temporary references on disk tempFiles.ForEach(File.Delete); Directory.Delete(tempBasePath); return(new DiagnosticResult(success: true, warnings: warnings, errors: errors)); }
public CompilationContext CompileProject( CompilationProjectContext projectContext, IEnumerable <IMetadataReference> incomingReferences, IEnumerable <ISourceReference> incomingSourceReferences, Func <IList <ResourceDescriptor> > resourcesResolver) { var path = projectContext.ProjectDirectory; var name = projectContext.Target.Name; var projectInfo = GetProjectInfo(projectContext.ProjectFilePath); if (_cacheContextAccessor.Current != null) { _cacheContextAccessor.Current.Monitor(new FileWriteTimeCacheDependency(projectContext.ProjectFilePath)); // Monitor the trigger {projectName}_BuildOutputs var buildOutputsName = name + "_BuildOutputs"; _cacheContextAccessor.Current.Monitor(_namedDependencyProvider.GetNamedDependency(buildOutputsName)); _cacheContextAccessor.Current.Monitor(_namedDependencyProvider.GetNamedDependency(name + "_Dependencies")); } Logger.TraceInformation("[{0}]: Compiling '{1}'", GetType().Name, name); var sw = Stopwatch.StartNew(); CompilationContext context; using (new ResolveHooker()) using (var files = new TempFiles()) { var outFileName = $"{name}.dll"; var outDir = files.CreateDir(); var outFile = Path.Combine(outDir, outFileName); var args = new List <string>(); args.Add("fsc.exe"); args.Add($"--out:{outFile}"); args.Add("--target:library"); args.Add("--noframework"); args.Add("--optimize-"); args.Add("--debug"); if (SupportsPdbGeneration) { args.Add($"--pdb:{Path.ChangeExtension(outFile, ".pdb")}"); } args.Add($"--doc:{Path.ChangeExtension(outFile, ".xml")}"); args.AddRange(projectInfo.Files); // These are the metadata references being used by your project. // Everything in your project.json is resolved and normailzed here: // - Project references // - Package references are turned into the appropriate assemblies // Each IMetadaReference maps to an assembly foreach (var reference in incomingReferences) { string fileName = null; var projectRef = reference as IMetadataProjectReference; if (projectRef != null) { var dir = files.CreateDir(); projectRef.EmitAssembly(dir); fileName = Path.Combine(dir, $"{projectRef.Name}.dll"); } var fileRef = reference as IMetadataFileReference; if (fileRef != null) { fileName = fileRef.Path; } else if (fileName == null) { throw new Exception($"Unknown reference type {reference.GetType()}"); } args.Add($"-r:{fileName}"); } //Console.WriteLine(string.Join(Environment.NewLine, args)); var scs = new SimpleSourceCodeServices(); var result = scs.Compile(args.ToArray()); var errors = result.Item1.Select(FSharpDiagnosticMessage.CompilationMessage); var resultCode = result.Item2; //System.Diagnostics.Debugger.Launch(); MemoryStream assembly = null; MemoryStream pdb = null; MemoryStream xml = null; if (resultCode == 0) { assembly = new MemoryStream(); xml = new MemoryStream(); using (var fs = File.OpenRead(outFile)) fs.CopyTo(assembly); var pdbFile = Path.ChangeExtension(outFile, ".pdb"); if (File.Exists(pdbFile)) { pdb = new MemoryStream(); using (var fs = File.OpenRead(pdbFile)) fs.CopyTo(pdb); } var xmlFile = Path.ChangeExtension(outFile, ".xml"); if (File.Exists(xmlFile)) { xml = new MemoryStream(); using (var fs = File.OpenRead(xmlFile)) fs.CopyTo(xml); } } context = new CompilationContext( projectContext, projectInfo, resultCode == 0, errors, assembly?.ToArray(), pdb?.ToArray(), xml?.ToArray()); assembly?.Dispose(); pdb?.Dispose(); xml?.Dispose(); } sw.Stop(); Logger.TraceInformation("[{0}]: Compiled '{1}' in {2}ms", GetType().Name, name, sw.ElapsedMilliseconds); return(context); }
public CompilationContext CompileProject( ICompilationProject project, ILibraryKey target, IEnumerable<IMetadataReference> incomingReferences, IEnumerable<ISourceReference> incomingSourceReferences, Func<IList<ResourceDescriptor>> resourcesResolver) { var path = project.ProjectDirectory; var name = project.Name; var fsproj = GetProjectInfo(path); _watcher.WatchProject(path); _watcher.WatchFile(project.ProjectFilePath); _watcher.WatchFile(fsproj.ProjectFilePath); foreach (var f in fsproj.Files) _watcher.WatchFile(f); if (_cacheContextAccessor.Current != null) { _cacheContextAccessor.Current.Monitor(new FileWriteTimeCacheDependency(fsproj.ProjectFilePath)); // Monitor the trigger {projectName}_BuildOutputs var buildOutputsName = project.Name + "_BuildOutputs"; _cacheContextAccessor.Current.Monitor(_namedDependencyProvider.GetNamedDependency(buildOutputsName)); _cacheContextAccessor.Current.Monitor(_namedDependencyProvider.GetNamedDependency(project.Name + "_Dependencies")); } Logger.TraceInformation("[{0}]: Compiling '{1}'", GetType().Name, name); var sw = Stopwatch.StartNew(); CompilationContext context; using (new ResolveHooker()) using (var files = new TempFiles()) { var outFileName = $"{name}.dll"; var outDir = files.CreateDir(); var outFile = Path.Combine(outDir, outFileName); var args = new List<string>(); args.Add("fsc.exe"); args.Add($"--out:{outFile}"); args.Add("--target:library"); args.Add("--noframework"); args.Add("--optimize-"); args.Add("--debug"); if (SupportsPdbGeneration) args.Add($"--pdb:{Path.ChangeExtension(outFile, ".pdb")}"); args.Add($"--doc:{Path.ChangeExtension(outFile, ".xml")}"); foreach (var source in fsproj.Files) args.Add(source); // These are the metadata references being used by your project. // Everything in your project.json is resolved and normailzed here: // - Project references // - Package references are turned into the appropriate assemblies // Each IMetadaReference maps to an assembly foreach (var reference in incomingReferences) { string fileName = null; var projectRef = reference as IMetadataProjectReference; if (projectRef != null) { var dir = files.CreateDir(); projectRef.EmitAssembly(dir); fileName = Path.Combine(dir, $"{projectRef.Name}.dll"); } var fileRef = reference as IMetadataFileReference; if (fileRef != null) fileName = fileRef.Path; else if (fileName == null) throw new Exception($"Unknown reference type {reference.GetType()}"); args.Add($"-r:{fileName}"); } //Console.WriteLine(string.Join(Environment.NewLine, args)); var scs = new SimpleSourceCodeServices(); var result = scs.Compile(args.ToArray()); var errors = result.Item1.Select(FSharpCompilationMessage.CompilationMessage); var resultCode = result.Item2; //System.Diagnostics.Debugger.Launch(); MemoryStream assembly = null; MemoryStream pdb = null; MemoryStream xml = null; if (resultCode == 0) { assembly = new MemoryStream(); xml = new MemoryStream(); using (var fs = File.OpenRead(outFile)) fs.CopyTo(assembly); var pdbFile = Path.ChangeExtension(outFile, ".pdb"); if (File.Exists(pdbFile)) { pdb = new MemoryStream(); using (var fs = File.OpenRead(pdbFile)) fs.CopyTo(pdb); } var xmlFile = Path.ChangeExtension(outFile, ".xml"); if (File.Exists(xmlFile)) { xml = new MemoryStream(); using (var fs = File.OpenRead(xmlFile)) fs.CopyTo(xml); } } context = new CompilationContext( project, fsproj, resultCode == 0, errors, assembly?.ToArray(), pdb?.ToArray(), xml?.ToArray()); assembly?.Dispose(); pdb?.Dispose(); xml?.Dispose(); } sw.Stop(); Logger.TraceInformation("[{0}]: Compiled '{1}' in {2}ms", GetType().Name, name, sw.ElapsedMilliseconds); return context; }
public IDiagnosticResult Emit(string outputPath, bool emitPdb, bool emitDocFile, bool emitExe = false) { var tempBasePath = Path.Combine(outputPath, _project.Name, "obj"); var outputDll = Path.Combine(outputPath, _project.Name + (emitExe ? ".exe" : ".dll")); // csc /out:foo.dll / target:library Program.cs var fscArgBuilder = new List<string>(); fscArgBuilder.Add("fsc.exe"); fscArgBuilder.Add("--noframework"); fscArgBuilder.Add("--nologo"); fscArgBuilder.Add("--out:" + outputDll); fscArgBuilder.Add("--target:" + (emitExe ? "exe" : "library")); Directory.CreateDirectory(tempBasePath); if (emitPdb) { var pdb = Path.Combine(outputPath, _project.Name + ".pdb"); fscArgBuilder.Add("--debug"); fscArgBuilder.Add("--optimize-"); fscArgBuilder.Add("--tailcalls-"); fscArgBuilder.Add("--pdb:" + pdb); } if (emitDocFile) { var doc = Path.Combine(outputPath, _project.Name + ".xml"); fscArgBuilder.Add("--doc:" + doc); } // F# cares about order so assume that the files were listed in order fscArgBuilder.AddRange(_project.SourceFiles); var tempFiles = new List<string>(); // These are the metadata references being used by your project. // Everything in your project.json is resolved and normailzed here: // - Project references // - Package references are turned into the appropriate assemblies // - Assembly neutral references // Each IMetadaReference maps to an assembly foreach (var reference in _metadataReferences) { // Skip this project if (reference.Name == typeof(FSharpProjectReference).Assembly.GetName().Name) { continue; } // NuGet references var fileReference = reference as IMetadataFileReference; if (fileReference != null) { fscArgBuilder.Add(@"-r:" + fileReference.Path); } // Assembly neutral references var embeddedReference = reference as IMetadataEmbeddedReference; if (embeddedReference != null) { var tempEmbeddedPath = Path.Combine(tempBasePath, reference.Name + ".dll"); // Write the ANI to disk for csc File.WriteAllBytes(tempEmbeddedPath, embeddedReference.Contents); fscArgBuilder.Add("-r:" + tempEmbeddedPath); tempFiles.Add(tempEmbeddedPath); } var projectReference = reference as IMetadataProjectReference; if (projectReference != null) { // You can write the reference assembly to the stream // and add the reference to your compiler var tempProjectDll = Path.Combine(tempBasePath, reference.Name + ".dll"); using (var fs = File.OpenWrite(tempProjectDll)) { projectReference.EmitReferenceAssembly(fs); } fscArgBuilder.Add(@"-r:" + tempProjectDll); tempFiles.Add(tempProjectDll); } } // For debugging // Console.WriteLine(fscArgs.ToString()); var scs = new SimpleSourceCodeServices(); var result = scs.Compile(fscArgBuilder.ToArray()); var warnings = result.Item1.Where(i => i.Severity.IsWarning).Select(i => i.ToString()).ToArray(); var errors = result.Item1.Where(i => i.Severity.IsError).Select(i => i.ToString()).ToArray(); if (result.Item2 != 0) { return new DiagnosticResult(success: false, warnings: warnings, errors: errors); } // Nuke the temporary references on disk tempFiles.ForEach(File.Delete); Directory.Delete(tempBasePath); return new DiagnosticResult(success: true, warnings: warnings, errors: errors); }
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); 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, "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>(); 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.Options.MetadataReferences .Cast<UnresolvedMetadataReference>() .Aggregate(new List<PortableExecutableReference>(), (a, r) => { a.AddRange(_metadataResolver.ResolveReference(r.Reference, string.Empty, r.Properties)); return a; }); // Add the references as reported by the metadata resolver. otherFlags.AddRange(references.Select(r => "-r:" + r.Display)); 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:" + 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 { _traceWriter.Verbose($"F# compilation failed with arguments: { string.Join(" ", otherFlags) }"); } } finally { DeleteDirectoryAsync(scriptPath, recursive: true) .ContinueWith(t => t.Exception.Handle(e => { _traceWriter.Warning($"Unable to delete F# compilation file: { e.ToString() }"); return true; }), TaskContinuationOptions.OnlyOnFaulted); } return new FSharpCompilation(errors, assemblyOption); }
public IScriptCompilerResults Compile(IEnumerable<CompilationUnit> compilationUnits, string resultingAssemblyDirectory = null) { _sourceCodeServices = _sourceCodeServices ?? new SimpleSourceCodeServices(); var assemblyName = "FS-" + Guid.NewGuid() + ".dll"; var assemblyDirectory = string.IsNullOrWhiteSpace(resultingAssemblyDirectory) ? Path.Combine(Environment.CurrentDirectory, FileConstants.AssembliesDirectory) : resultingAssemblyDirectory; if (!Directory.Exists(assemblyDirectory)) Directory.CreateDirectory(assemblyDirectory); var outputAssemblyPath = Path.Combine(assemblyDirectory, assemblyName); var referencesAndScript = new List<string>(); foreach (var reference in _references) { if (!string.IsNullOrWhiteSpace(reference)) referencesAndScript.Add(string.Format("--reference:{0}", reference)); } var enumerable = compilationUnits as CompilationUnit[] ?? compilationUnits.ToArray(); foreach (var compilationUnit in enumerable) { if (string.IsNullOrWhiteSpace(compilationUnit.Source)) throw new ArgumentException("scriptsource"); if (string.IsNullOrWhiteSpace(compilationUnit.SourceFilePath)) throw new ArgumentException("scriptsourceFilePath"); referencesAndScript.Add(compilationUnit.SourceFilePath); } var options = new[] { "fsc.exe", "-o", outputAssemblyPath, "-a", "--debug+", "--optimize-", "--noframework" }; string[] completeOptions = options.Concat(referencesAndScript).ToArray(); var errorsAndExitCode = _sourceCodeServices.Compile(completeOptions); var errorsWithoutWarnings = errorsAndExitCode.Item1.Where(x => x.Severity.IsError); var errors = errorsWithoutWarnings .Select(x => string.Format("{0} {1} {2} ", x.StartLineAlternate, x.StartColumn, x.Message)) .ToArray(); return new ScriptCompilerResults(!errors.Any(), errors, outputAssemblyPath); }
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)); }