public override bool Compile(ProjectContext context, CompilerCommandApp args) { // Set up Output Paths var outputPaths = context.GetOutputPaths(args.ConfigValue, args.BuildBasePathValue); var outputPath = outputPaths.CompilationOutputPath; var intermediateOutputPath = outputPaths.IntermediateOutputDirectoryPath; Directory.CreateDirectory(outputPath); Directory.CreateDirectory(intermediateOutputPath); // Create the library exporter var exporter = context.CreateExporter(args.ConfigValue, args.BuildBasePathValue); // Gather exports for the project var dependencies = exporter.GetDependencies().ToList(); Reporter.Output.WriteLine($"Compiling {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}"); var sw = Stopwatch.StartNew(); var diagnostics = new List<DiagnosticMessage>(); var missingFrameworkDiagnostics = new List<DiagnosticMessage>(); // Collect dependency diagnostics foreach (var diag in context.LibraryManager.GetAllDiagnostics()) { if (diag.ErrorCode == ErrorCodes.DOTNET1011 || diag.ErrorCode == ErrorCodes.DOTNET1012) { missingFrameworkDiagnostics.Add(diag); } diagnostics.Add(diag); } if (missingFrameworkDiagnostics.Count > 0) { // The framework isn't installed so we should short circuit the rest of the compilation // so we don't get flooded with errors PrintSummary(missingFrameworkDiagnostics, sw); return false; } // Get compilation options var outputName = outputPaths.CompilationFiles.Assembly; // Assemble args var compilerArgs = new List<string>() { $"--temp-output:{intermediateOutputPath}", $"--out:{outputName}" }; var compilationOptions = context.ResolveCompilationOptions(args.ConfigValue); var languageId = CompilerUtil.ResolveLanguageId(context); var references = new List<string>(); // Add compilation options to the args compilerArgs.AddRange(compilationOptions.SerializeToArgs()); // Add metadata options compilerArgs.AddRange(AssemblyInfoOptions.SerializeToArgs(AssemblyInfoOptions.CreateForProject(context))); foreach (var dependency in dependencies) { references.AddRange(dependency.CompilationAssemblies.Select(r => r.ResolvedPath)); compilerArgs.AddRange(dependency.SourceReferences.Select(s => s.GetTransformedFile(intermediateOutputPath))); foreach (var resourceFile in dependency.EmbeddedResources) { var transformedResource = resourceFile.GetTransformedFile(intermediateOutputPath); var resourceName = ResourceManifestName.CreateManifestName( Path.GetFileName(resourceFile.ResolvedPath), compilationOptions.OutputName); compilerArgs.Add($"--resource:\"{transformedResource}\",{resourceName}"); } // Add analyzer references compilerArgs.AddRange(dependency.AnalyzerReferences .Where(a => a.AnalyzerLanguage == languageId) .Select(a => $"--analyzer:{a.AssemblyPath}")); } compilerArgs.AddRange(references.Select(r => $"--reference:{r}")); if (compilationOptions.PreserveCompilationContext == true) { var allExports = exporter.GetAllExports().ToList(); var dependencyContext = new DependencyContextBuilder().Build(compilationOptions, allExports, allExports, false, // For now, just assume non-portable mode in the legacy deps file (this is going away soon anyway) context.TargetFramework, context.RuntimeIdentifier ?? string.Empty); var writer = new DependencyContextWriter(); var depsJsonFile = Path.Combine(intermediateOutputPath, compilationOptions.OutputName + "dotnet-compile.deps.json"); using (var fileStream = File.Create(depsJsonFile)) { writer.Write(dependencyContext, fileStream); } compilerArgs.Add($"--resource:\"{depsJsonFile}\",{compilationOptions.OutputName}.deps.json"); } if (!AddNonCultureResources(context.ProjectFile, compilerArgs, intermediateOutputPath)) { return false; } // Add project source files var sourceFiles = CompilerUtil.GetCompilationSources(context); compilerArgs.AddRange(sourceFiles); var compilerName = context.ProjectFile.CompilerName; // Write RSP file var rsp = Path.Combine(intermediateOutputPath, $"dotnet-compile.rsp"); File.WriteAllLines(rsp, compilerArgs); // Run pre-compile event var contextVariables = new Dictionary<string, string>() { { "compile:TargetFramework", context.TargetFramework.GetShortFolderName() }, { "compile:FullTargetFramework", context.TargetFramework.DotNetFrameworkName }, { "compile:Configuration", args.ConfigValue }, { "compile:OutputFile", outputName }, { "compile:OutputDir", outputPath.TrimEnd('\\', '/') }, { "compile:ResponseFile", rsp } }; if (context.ProjectFile.HasRuntimeOutput(args.ConfigValue)) { var runtimeContext = context.CreateRuntimeContext(args.GetRuntimes()); var runtimeOutputPath = runtimeContext.GetOutputPaths(args.ConfigValue, args.BuildBasePathValue, args.OutputValue); contextVariables.Add( "compile:RuntimeOutputDir", runtimeOutputPath.RuntimeOutputPath.TrimEnd('\\', '/')); contextVariables.Add( "compile:RuntimeIdentifier", runtimeContext.RuntimeIdentifier); } _scriptRunner.RunScripts(context, ScriptNames.PreCompile, contextVariables); var result = _commandFactory.Create($"compile-{compilerName}", new[] { "@" + $"{rsp}" }) .OnErrorLine(line => { var diagnostic = ParseDiagnostic(context.ProjectDirectory, line); if (diagnostic != null) { diagnostics.Add(diagnostic); } else { Reporter.Error.WriteLine(line); } }) .OnOutputLine(line => { var diagnostic = ParseDiagnostic(context.ProjectDirectory, line); if (diagnostic != null) { diagnostics.Add(diagnostic); } else { Reporter.Output.WriteLine(line); } }).Execute(); // Run post-compile event contextVariables["compile:CompilerExitCode"] = result.ExitCode.ToString(); _scriptRunner.RunScripts(context, ScriptNames.PostCompile, contextVariables); var success = result.ExitCode == 0; if (!success) { Reporter.Error.WriteLine($"{result.StartInfo.FileName} {result.StartInfo.Arguments} returned Exit Code {result.ExitCode}"); } if (success) { success &= GenerateCultureResourceAssemblies(context.ProjectFile, dependencies, intermediateOutputPath, outputPath); } return PrintSummary(diagnostics, sw, success); }
// computes all the inputs and outputs that would be used in the compilation of a project // ensures that all paths are files // ensures no missing inputs public CompilerIO GetCompileIO(ProjectContext project, ProjectDependenciesFacade dependencies) { var buildConfiguration = _args.ConfigValue; var buildBasePath = _args.BuildBasePathValue; var outputPath = _args.OutputValue; var isRootProject = project == _rootProject; var compilerIO = new CompilerIO(new List<string>(), new List<string>()); var calculator = project.GetOutputPaths(buildConfiguration, buildBasePath, outputPath); var binariesOutputPath = calculator.CompilationOutputPath; // input: project.json compilerIO.Inputs.Add(project.ProjectFile.ProjectFilePath); // input: lock file; find when dependencies change AddLockFile(project, compilerIO); // input: source files compilerIO.Inputs.AddRange(CompilerUtil.GetCompilationSources(project)); // todo: Factor out dependency resolution between Build and Compile. Ideally Build injects the dependencies into Compile // input: dependencies AddDependencies(dependencies, compilerIO); var allOutputPath = new List<string>(calculator.CompilationFiles.All()); if (isRootProject && project.ProjectFile.HasRuntimeOutput(buildConfiguration)) { var runtimeContext = project.CreateRuntimeContext(_args.GetRuntimes()); allOutputPath.AddRange(runtimeContext.GetOutputPaths(buildConfiguration, buildBasePath, outputPath).RuntimeFiles.All()); } // output: compiler outputs foreach (var path in allOutputPath) { compilerIO.Outputs.Add(path); } // input compilation options files AddCompilationOptions(project, buildConfiguration, compilerIO); // input / output: resources with culture AddNonCultureResources(project, calculator.IntermediateOutputDirectoryPath, compilerIO); // input / output: resources without culture AddCultureResources(project, binariesOutputPath, compilerIO); return compilerIO; }