diff --git a/scripts/test/package-command-test.ps1 b/scripts/test/package-command-test.ps1 index 914c02b0a..295e9e88f 100644 --- a/scripts/test/package-command-test.ps1 +++ b/scripts/test/package-command-test.ps1 @@ -43,7 +43,7 @@ popd dir "$RepoRoot\test\PackagedCommands\Consumers" | where {$_.PsIsContainer} | where {$_.Name.Contains("Direct")} | foreach { pushd "$RepoRoot\test\PackagedCommands\Consumers\$_" - dotnet compile + dotnet build popd } diff --git a/scripts/test/package-command-test.sh b/scripts/test/package-command-test.sh index 75fab53ab..3f32f0edf 100755 --- a/scripts/test/package-command-test.sh +++ b/scripts/test/package-command-test.sh @@ -43,7 +43,7 @@ popd for test in $(ls -l "$REPOROOT/test/PackagedCommands/Consumers" | grep ^d | awk '{print $9}' | grep "Direct") do pushd "$REPOROOT/test/PackagedCommands/Consumers/$test" - dotnet compile + dotnet build popd done diff --git a/src/Microsoft.DotNet.Compiler.Common/ContentFiles.cs b/src/Microsoft.DotNet.Compiler.Common/ContentFiles.cs new file mode 100644 index 000000000..afba12c84 --- /dev/null +++ b/src/Microsoft.DotNet.Compiler.Common/ContentFiles.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Common; + +namespace Microsoft.Dotnet.Cli.Compiler.Common +{ + public class ContentFiles + { + private readonly ProjectContext _context; + + public ContentFiles(ProjectContext context) + { + _context = context; + } + + public void StructuredCopyTo(string targetDirectory) + { + var sourceFiles = _context + .ProjectFile + .Files + .GetContentFiles(); + + var sourceDirectory = _context.ProjectDirectory; + + if (sourceFiles == null) + { + throw new ArgumentNullException(nameof(sourceFiles)); + } + + sourceDirectory = EnsureTrailingSlash(sourceDirectory); + targetDirectory = EnsureTrailingSlash(targetDirectory); + + var pathMap = sourceFiles + .ToDictionary(s => s, + s => Path.Combine(targetDirectory, + PathUtility.GetRelativePath(sourceDirectory, s))); + + foreach (var targetDir in pathMap.Values + .Select(Path.GetDirectoryName) + .Distinct() + .Where(t => !Directory.Exists(t))) + { + Directory.CreateDirectory(targetDir); + } + + foreach (var sourceFilePath in pathMap.Keys) + { + File.Copy( + sourceFilePath, + pathMap[sourceFilePath], + overwrite: true); + } + + RemoveAttributeFromFiles(pathMap.Values, FileAttributes.ReadOnly); + } + + private static void RemoveAttributeFromFiles(IEnumerable files, FileAttributes attribute) + { + foreach (var file in files) + { + var fileAttributes = File.GetAttributes(file); + if ((fileAttributes & attribute) == attribute) + { + File.SetAttributes(file, fileAttributes & ~attribute); + } + } + } + + private static string EnsureTrailingSlash(string path) + { + return EnsureTrailingCharacter(path, Path.DirectorySeparatorChar); + } + + private static string EnsureTrailingCharacter(string path, char trailingCharacter) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + // if the path is empty, we want to return the original string instead of a single trailing character. + if (path.Length == 0 || path[path.Length - 1] == trailingCharacter) + { + return path; + } + + return path + trailingCharacter; + } + } +} diff --git a/src/Microsoft.DotNet.Compiler.Common/Executable.cs b/src/Microsoft.DotNet.Compiler.Common/Executable.cs new file mode 100644 index 000000000..f296e9ffe --- /dev/null +++ b/src/Microsoft.DotNet.Compiler.Common/Executable.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.DotNet.Cli.Compiler.Common; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Compilation; +using Microsoft.DotNet.ProjectModel.Graph; +using NuGet.Frameworks; + +namespace Microsoft.Dotnet.Cli.Compiler.Common +{ + public class Executable + { + private readonly ProjectContext _context; + + private readonly OutputPathCalculator _calculator; + + public Executable(ProjectContext context, OutputPathCalculator calculator) + { + _context = context; + + _calculator = calculator; + } + + public void MakeCompilationOutputRunnable(string configuration) + { + var outputPath = _calculator.GetOutputDirectoryPath(configuration); + + CopyContentFiles(outputPath); + + ExportRuntimeAssets(outputPath, configuration); + } + + private void ExportRuntimeAssets(string outputPath, string configuration) + { + var exporter = _context.CreateExporter(configuration); + + if (_context.TargetFramework.IsDesktop()) + { + MakeCompilationOutputRunnableForFullFramework(outputPath, configuration, exporter); + } + else + { + MakeCompilationOutputRunnableForCoreCLR(outputPath, exporter); + } + } + + private void MakeCompilationOutputRunnableForFullFramework( + string outputPath, + string configuration, + LibraryExporter exporter) + { + CopyAllDependencies(outputPath, exporter); + + GenerateBindingRedirects(exporter, configuration); + } + + private void MakeCompilationOutputRunnableForCoreCLR(string outputPath, LibraryExporter exporter) + { + WriteDepsFileAndCopyProjectDependencies(exporter, _context.ProjectFile.Name, outputPath); + + // TODO: Pick a host based on the RID + CoreHost.CopyTo(outputPath, _context.ProjectFile.Name + Constants.ExeSuffix); + } + + private void CopyContentFiles(string outputPath) + { + var contentFiles = new ContentFiles(_context); + contentFiles.StructuredCopyTo(outputPath); + } + + private static void CopyAllDependencies(string outputPath, LibraryExporter exporter) + { + exporter + .GetDependencies() + .SelectMany(e => e.RuntimeAssets()) + .CopyTo(outputPath); + } + + private static void WriteDepsFileAndCopyProjectDependencies( + LibraryExporter exporter, + string projectFileName, + string outputPath) + { + exporter + .GetDependencies(LibraryType.Package) + .WriteDepsTo(Path.Combine(outputPath, projectFileName + FileNameSuffixes.Deps)); + + exporter + .GetDependencies(LibraryType.Project) + .SelectMany(e => e.RuntimeAssets()) + .CopyTo(outputPath); + } + + public void GenerateBindingRedirects(LibraryExporter exporter, string configuration) + { + var outputName = _calculator.GetAssemblyPath(configuration); + + var existingConfig = new DirectoryInfo(_context.ProjectDirectory) + .EnumerateFiles() + .FirstOrDefault(f => f.Name.Equals("app.config", StringComparison.OrdinalIgnoreCase)); + + XDocument baseAppConfig = null; + + if (existingConfig != null) + { + using (var fileStream = File.OpenRead(existingConfig.FullName)) + { + baseAppConfig = XDocument.Load(fileStream); + } + } + + var appConfig = exporter.GetAllExports().GenerateBindingRedirects(baseAppConfig); + + if (appConfig == null) { return; } + + var path = outputName + ".config"; + using (var stream = File.Create(path)) + { + appConfig.Save(stream); + } + } + } +} diff --git a/src/Microsoft.DotNet.Compiler.Common/LibraryExporterExtensions.cs b/src/Microsoft.DotNet.Compiler.Common/LibraryExporterExtensions.cs index c5291bb75..9fd81a3ef 100644 --- a/src/Microsoft.DotNet.Compiler.Common/LibraryExporterExtensions.cs +++ b/src/Microsoft.DotNet.Compiler.Common/LibraryExporterExtensions.cs @@ -9,9 +9,17 @@ namespace Microsoft.DotNet.Cli.Compiler.Common { public static void WriteDepsTo(this IEnumerable exports, string path) { + CreateDirectoryIfNotExists(path); + File.WriteAllLines(path, exports.SelectMany(GenerateLines)); } - + + private static void CreateDirectoryIfNotExists(string path) + { + var depsFile = new FileInfo(path); + depsFile.Directory.Create(); + } + private static IEnumerable GenerateLines(LibraryExport export) { return GenerateLines(export, export.RuntimeAssemblies, "runtime") diff --git a/src/Microsoft.DotNet.Compiler.Common/ProjectContextExtensions.cs b/src/Microsoft.DotNet.Compiler.Common/ProjectContextExtensions.cs index e28496247..0129fcada 100644 --- a/src/Microsoft.DotNet.Compiler.Common/ProjectContextExtensions.cs +++ b/src/Microsoft.DotNet.Compiler.Common/ProjectContextExtensions.cs @@ -2,16 +2,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Xml.Linq; -using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectModel; -using Microsoft.DotNet.ProjectModel.Compilation; -using Microsoft.DotNet.ProjectModel.Graph; -using Microsoft.DotNet.Tools.Common; using NuGet.Frameworks; namespace Microsoft.DotNet.Cli.Compiler.Common @@ -21,133 +14,7 @@ namespace Microsoft.DotNet.Cli.Compiler.Common public static string ProjectName(this ProjectContext context) => context.RootProject.Identity.Name; public static string GetDisplayName(this ProjectContext context) => $"{context.RootProject.Identity.Name} ({context.TargetFramework})"; - public static void MakeCompilationOutputRunnable(this ProjectContext context, string outputPath, string configuration) - { - context - .ProjectFile - .Files - .GetContentFiles() - .StructuredCopyTo(context.ProjectDirectory, outputPath) - .RemoveAttribute(FileAttributes.ReadOnly); - - var exporter = context.CreateExporter(configuration); - - if (context.TargetFramework.IsDesktop()) - { - exporter - .GetDependencies() - .SelectMany(e => e.RuntimeAssets()) - .CopyTo(outputPath); - } - else - { - exporter - .GetDependencies(LibraryType.Package) - .WriteDepsTo(Path.Combine(outputPath, context.ProjectFile.Name + FileNameSuffixes.Deps)); - - exporter.GetDependencies(LibraryType.Project) - .SelectMany(e => e.RuntimeAssets()) - .CopyTo(outputPath); - - CoreHost.CopyTo(outputPath, context.ProjectFile.Name + Constants.ExeSuffix); - } - } - - private static IEnumerable StructuredCopyTo(this IEnumerable sourceFiles, string sourceDirectory, string targetDirectory) - { - if (sourceFiles == null) - { - throw new ArgumentNullException(nameof(sourceFiles)); - } - - sourceDirectory = EnsureTrailingSlash(sourceDirectory); - targetDirectory = EnsureTrailingSlash(targetDirectory); - - var pathMap = sourceFiles - .ToDictionary(s => s, - s => Path.Combine(targetDirectory, - PathUtility.GetRelativePath(sourceDirectory, s))); - - foreach (var targetDir in pathMap.Values - .Select(Path.GetDirectoryName) - .Distinct() - .Where(t => !Directory.Exists(t))) - { - Directory.CreateDirectory(targetDir); - } - - foreach (var sourceFilePath in pathMap.Keys) - { - File.Copy( - sourceFilePath, - pathMap[sourceFilePath], - overwrite: true); - } - - return pathMap.Values; - } - - private static string EnsureTrailingSlash(string path) - { - return EnsureTrailingCharacter(path, Path.DirectorySeparatorChar); - } - - private static string EnsureTrailingCharacter(string path, char trailingCharacter) - { - if (path == null) - { - throw new ArgumentNullException(nameof(path)); - } - - // if the path is empty, we want to return the original string instead of a single trailing character. - if (path.Length == 0 || path[path.Length - 1] == trailingCharacter) - { - return path; - } - - return path + trailingCharacter; - } - - private static IEnumerable RemoveAttribute(this IEnumerable files, FileAttributes attribute) - { - foreach (var file in files) - { - var fileAttributes = File.GetAttributes(file); - if ((fileAttributes & attribute) == attribute) - { - File.SetAttributes(file, fileAttributes & ~attribute); - } - } - - return files; - } - - public static void GenerateBindingRedirects(this ProjectContext context, LibraryExporter exporter, string outputName) - { - var existingConfig = new DirectoryInfo(context.ProjectDirectory) - .EnumerateFiles() - .FirstOrDefault(f => f.Name.Equals("app.config", StringComparison.OrdinalIgnoreCase)); - - XDocument baseAppConfig = null; - - if (existingConfig != null) - { - using (var fileStream = File.OpenRead(existingConfig.FullName)) - { - baseAppConfig = XDocument.Load(fileStream); - } - } - - var appConfig = exporter.GetAllExports().GenerateBindingRedirects(baseAppConfig); - - if (appConfig == null) { return; } - - var path = outputName + ".config"; - using (var stream = File.Create(path)) - { - appConfig.Save(stream); - } - } + public static bool IsTestProject(this ProjectContext context) => !string.IsNullOrEmpty(context.ProjectFile.TestRunner); public static CommonCompilerOptions GetLanguageSpecificCompilerOptions(this ProjectContext context, NuGetFramework framework, string configurationName) { diff --git a/src/dotnet-build/CompileContext.cs b/src/dotnet-build/CompileContext.cs index 519a0d012..a70c92aac 100644 --- a/src/dotnet-build/CompileContext.cs +++ b/src/dotnet-build/CompileContext.cs @@ -5,11 +5,13 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Dotnet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Utilities; using Microsoft.DotNet.Tools.Compiler; +using Microsoft.Extensions.PlatformAbstractions; namespace Microsoft.DotNet.Tools.Build { @@ -273,16 +275,12 @@ namespace Microsoft.DotNet.Tools.Build var args = new List(); args.Add("--framework"); - args.Add($"{projectDependency.Framework}"); + args.Add($"{projectDependency.Framework}"); + args.Add("--configuration"); args.Add(_args.ConfigValue); args.Add(projectDependency.Project.ProjectDirectory); - - if (_args.NoHostValue) - { - args.Add("--no-host"); - } - + var compileResult = Command.Create("dotnet-compile", args) .ForwardStdOut() .ForwardStdErr() @@ -296,7 +294,7 @@ namespace Microsoft.DotNet.Tools.Build // todo: add methods to CompilerCommandApp to generate the arg string? var args = new List(); args.Add("--framework"); - args.Add(_rootProject.TargetFramework.ToString()); + args.Add(_rootProject.TargetFramework.ToString()); args.Add("--configuration"); args.Add(_args.ConfigValue); args.Add("--output"); @@ -304,11 +302,6 @@ namespace Microsoft.DotNet.Tools.Build args.Add("--temp-output"); args.Add(_args.IntermediateValue); - if (_args.NoHostValue) - { - args.Add("--no-host"); - } - //native args if (_args.IsNativeValue) { @@ -351,7 +344,41 @@ namespace Microsoft.DotNet.Tools.Build .ForwardStdErr() .Execute(); - return compileResult.ExitCode == 0; + var succeeded = compileResult.ExitCode == 0; + + if (succeeded) + { + MakeRunnableIfNecessary(); + } + + return succeeded; + } + + private void MakeRunnableIfNecessary() + { + var compilationOptions = CompilerUtil.ResolveCompilationOptions(_rootProject, _args.ConfigValue); + + // TODO: Make this opt in via another mechanism + var makeRunnable = compilationOptions.EmitEntryPoint.GetValueOrDefault() || + _rootProject.IsTestProject(); + + if (makeRunnable) + { + var outputPathCalculator = _rootProject.GetOutputPathCalculator(_args.OutputValue); + var rids = new List(); + if (string.IsNullOrEmpty(_args.RuntimeValue)) + { + rids.AddRange(PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers()); + } + else + { + rids.Add(_args.RuntimeValue); + } + + var runtimeContext = ProjectContext.Create(_rootProject.ProjectDirectory, _rootProject.TargetFramework, rids); + var executable = new Executable(runtimeContext, outputPathCalculator); + executable.MakeCompilationOutputRunnable(_args.ConfigValue); + } } private static ISet Sort(Dictionary projects) diff --git a/src/dotnet-compile/CompilerCommandApp.cs b/src/dotnet-compile/CompilerCommandApp.cs index 5fde9c822..a181a8bce 100644 --- a/src/dotnet-compile/CompilerCommandApp.cs +++ b/src/dotnet-compile/CompilerCommandApp.cs @@ -23,8 +23,8 @@ namespace Microsoft.DotNet.Tools.Compiler private CommandOption _outputOption; private CommandOption _intermediateOutputOption; private CommandOption _frameworkOption; + private CommandOption _runtimeOption; private CommandOption _configurationOption; - private CommandOption _noHostOption; private CommandArgument _projectArgument; private CommandOption _nativeOption; private CommandOption _archOption; @@ -39,8 +39,8 @@ namespace Microsoft.DotNet.Tools.Compiler public string ProjectPathValue { get; set; } public string OutputValue { get; set; } public string IntermediateValue { get; set; } + public string RuntimeValue{ get; set; } public string ConfigValue { get; set; } - public bool NoHostValue { get; set; } public bool IsNativeValue { get; set; } public string ArchValue { get; set; } public string IlcArgsValue { get; set; } @@ -75,7 +75,7 @@ namespace Microsoft.DotNet.Tools.Compiler _intermediateOutputOption = _app.Option("-t|--temp-output ", "Directory in which to place temporary outputs", CommandOptionType.SingleValue); _frameworkOption = _app.Option("-f|--framework ", "Compile a specific framework", CommandOptionType.MultipleValue); _configurationOption = _app.Option("-c|--configuration ", "Configuration under which to build", CommandOptionType.SingleValue); - _noHostOption = _app.Option("--no-host", "Set this to skip publishing a runtime host when building for CoreCLR", CommandOptionType.NoValue); + _runtimeOption = _app.Option("-r|--runtime ", "Target runtime to publish for", CommandOptionType.SingleValue); _projectArgument = _app.Argument("", "The project to compile, defaults to the current directory. Can be a path to a project.json or a project directory"); // Native Args @@ -103,7 +103,7 @@ namespace Microsoft.DotNet.Tools.Compiler OutputValue = _outputOption.Value(); IntermediateValue = _intermediateOutputOption.Value(); ConfigValue = _configurationOption.Value() ?? Constants.DefaultConfiguration; - NoHostValue = _noHostOption.HasValue(); + RuntimeValue = _runtimeOption.Value(); IsNativeValue = _nativeOption.HasValue(); ArchValue = _archOption.Value(); diff --git a/src/dotnet-compile/Program.cs b/src/dotnet-compile/Program.cs index cdbd9ec57..71aba04ca 100644 --- a/src/dotnet-compile/Program.cs +++ b/src/dotnet-compile/Program.cs @@ -6,13 +6,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using Microsoft.Dotnet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Compilation; -using Microsoft.DotNet.ProjectModel.Graph; using Microsoft.DotNet.ProjectModel.Utilities; -using NuGet.Frameworks; using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.PlatformAbstractions; @@ -317,38 +316,7 @@ namespace Microsoft.DotNet.Tools.Compiler { success &= GenerateCultureResourceAssemblies(context.ProjectFile, dependencies, outputPath); } - - bool generateBindingRedirects = false; - if (success && !args.NoHostValue && compilationOptions.EmitEntryPoint.GetValueOrDefault()) - { - generateBindingRedirects = true; - var rids = PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers(); - var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, rids); - runtimeContext - .MakeCompilationOutputRunnable(outputPath, args.ConfigValue); - } - else if (!string.IsNullOrEmpty(context.ProjectFile.TestRunner)) - { - generateBindingRedirects = true; - var projectContext = - ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, - new[] { PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier() }); - - // Don't generate a deps file if we're on desktop - if (!context.TargetFramework.IsDesktop()) - { - projectContext - .CreateExporter(args.ConfigValue) - .GetDependencies(LibraryType.Package) - .WriteDepsTo(Path.Combine(outputPath, projectContext.ProjectFile.Name + FileNameSuffixes.Deps)); - } - } - - if (generateBindingRedirects && context.TargetFramework.IsDesktop()) - { - context.GenerateBindingRedirects(exporter, outputName); - } - + return PrintSummary(diagnostics, sw, success); } diff --git a/src/dotnet-publish/Program.cs b/src/dotnet-publish/Program.cs index 219371cce..72aabd21a 100644 --- a/src/dotnet-publish/Program.cs +++ b/src/dotnet-publish/Program.cs @@ -3,10 +3,8 @@ using Microsoft.Dnx.Runtime.Common.CommandLine; using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.ProjectModel; using System; using System.IO; -using Microsoft.Extensions.PlatformAbstractions; namespace Microsoft.DotNet.Tools.Publish { diff --git a/src/dotnet-publish/PublishCommand.cs b/src/dotnet-publish/PublishCommand.cs index e2d2f0d67..562f4c11e 100644 --- a/src/dotnet-publish/PublishCommand.cs +++ b/src/dotnet-publish/PublishCommand.cs @@ -109,10 +109,11 @@ namespace Microsoft.DotNet.Tools.Publish new string[] { "--framework", $"{context.TargetFramework.DotNetFrameworkName}", + "--runtime", + context.RuntimeIdentifier, "--configuration", - $"{configuration}", - "--no-host", - $"{context.ProjectFile.ProjectDirectory}" + configuration, + context.ProjectFile.ProjectDirectory }) .ForwardStdErr() .ForwardStdOut() diff --git a/src/dotnet-restore/Program.cs b/src/dotnet-restore/Program.cs index 5406b6d7e..fac03bdfd 100644 --- a/src/dotnet-restore/Program.cs +++ b/src/dotnet-restore/Program.cs @@ -9,6 +9,7 @@ using System.Text; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.Dnx.Runtime.Common.CommandLine; +using Microsoft.Dotnet.Cli.Compiler.Common; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Graph; using NuGet.Frameworks; @@ -169,12 +170,15 @@ namespace Microsoft.DotNet.Tools.Restore toolDescription.Path, Path.GetDirectoryName(toolDescription.Target.RuntimeAssemblies.First().Path), toolDescription.Identity.Name + FileNameSuffixes.Deps); - - context.MakeCompilationOutputRunnable(context.ProjectDirectory, Constants.DefaultConfiguration); + + var calculator = context.GetOutputPathCalculator(context.ProjectDirectory); + var executable = new Executable(context, calculator); + + executable.MakeCompilationOutputRunnable(Constants.DefaultConfiguration); if (File.Exists(depsPath)) File.Delete(depsPath); - File.Move(Path.Combine(context.ProjectDirectory, "bin" + FileNameSuffixes.Deps), depsPath); + File.Move(Path.Combine(calculator.GetOutputDirectoryPath(Constants.DefaultConfiguration), "bin" + FileNameSuffixes.Deps), depsPath); } private static void RestoreToolToPath(LibraryRange tooldep, IEnumerable args, string tempPath, bool quiet)