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)}");
        }
    }
    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)));
        }
Beispiel #4
0
        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));
        }
Beispiel #5
0
        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);
        }
Beispiel #10
0
        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));
        }