Copying project dependencies when the project is a test project. Without this, dotnet test will fail to find the P2P dependencies and crash.

Refactoring the MakeCompilationOutputRunnable code so that it is easier to move it to build. Making build stop passing runtime to compile. Moving the MakeCompilationOutputRunnable from compile to build.

Making WriteDepsFile create its directory if it does not exist.
This commit is contained in:
Livar Cunha 2016-01-29 18:21:37 -08:00
parent b16ecff0e9
commit 35ef45306a
7 changed files with 283 additions and 185 deletions

View file

@ -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<string> 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;
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -9,9 +9,17 @@ namespace Microsoft.DotNet.Cli.Compiler.Common
{
public static void WriteDepsTo(this IEnumerable<LibraryExport> 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<string> GenerateLines(LibraryExport export)
{
return GenerateLines(export, export.RuntimeAssemblies, "runtime")

View file

@ -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,141 +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)
{
// REVIEW: This shouldn't be copied on compile
context
.ProjectFile
.Files
.GetContentFiles()
.StructuredCopyTo(context.ProjectDirectory, outputPath)
.RemoveAttribute(FileAttributes.ReadOnly);
var exporter = context.CreateExporter(configuration);
if (context.TargetFramework.IsDesktop())
{
// On full framework, copy all dependencies to the output path
exporter
.GetDependencies()
.SelectMany(e => e.RuntimeAssets())
.CopyTo(outputPath);
// Generate binding redirects
var outputName = context.GetOutputPathCalculator(outputPath).GetAssemblyPath(configuration);
context.GenerateBindingRedirects(exporter, outputName);
}
else
{
exporter
.GetDependencies(LibraryType.Package)
.WriteDepsTo(Path.Combine(outputPath, context.ProjectFile.Name + FileNameSuffixes.Deps));
// On core clr, only copy project references
exporter.GetDependencies(LibraryType.Project)
.SelectMany(e => e.RuntimeAssets())
.CopyTo(outputPath);
// TODO: Pick a host based on the RID
CoreHost.CopyTo(outputPath, context.ProjectFile.Name + Constants.ExeSuffix);
}
}
private static IEnumerable<string> StructuredCopyTo(this IEnumerable<string> 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<string> RemoveAttribute(this IEnumerable<string> 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)
{

View file

@ -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,13 +275,7 @@ namespace Microsoft.DotNet.Tools.Build
var args = new List<string>();
args.Add("--framework");
args.Add($"{projectDependency.Framework}");
if (!string.IsNullOrEmpty(_args.RuntimeValue))
{
args.Add("--runtime");
args.Add(_args.RuntimeValue);
}
args.Add($"{projectDependency.Framework}");
args.Add("--configuration");
args.Add(_args.ConfigValue);
@ -298,12 +294,7 @@ namespace Microsoft.DotNet.Tools.Build
// todo: add methods to CompilerCommandApp to generate the arg string?
var args = new List<string>();
args.Add("--framework");
args.Add(_rootProject.TargetFramework.ToString());
if (!string.IsNullOrEmpty(_args.RuntimeValue))
{
args.Add("--runtime");
args.Add(_args.RuntimeValue);
}
args.Add(_rootProject.TargetFramework.ToString());
args.Add("--configuration");
args.Add(_args.ConfigValue);
args.Add("--output");
@ -353,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<string>();
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<ProjectDescription> Sort(Dictionary<string, ProjectDescription> projects)

View file

@ -6,6 +6,7 @@ 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;
@ -315,31 +316,7 @@ namespace Microsoft.DotNet.Tools.Compiler
{
success &= GenerateCultureResourceAssemblies(context.ProjectFile, dependencies, outputPath);
}
if (success)
{
// TODO: Make this opt in via another mechanism
var makeRunnable = compilationOptions.EmitEntryPoint.GetValueOrDefault() ||
!string.IsNullOrEmpty(context.ProjectFile.TestRunner);
if (makeRunnable)
{
var rids = new List<string>();
if (string.IsNullOrEmpty(args.RuntimeValue))
{
rids.AddRange(PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers());
}
else
{
rids.Add(args.RuntimeValue);
}
var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, rids);
runtimeContext
.MakeCompilationOutputRunnable(outputPath, args.ConfigValue);
}
}
return PrintSummary(diagnostics, sw, success);
}

View file

@ -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<string> args, string tempPath, bool quiet)