add support for portable application layout

This commit is contained in:
Andrew Stanton-Nurse 2016-03-09 11:36:16 -08:00
parent cef64895c8
commit d08e83d5db
38 changed files with 371 additions and 192 deletions

View file

@ -1,3 +0,0 @@
public static class Thingy
{
}

View file

@ -1,3 +0,0 @@
public static class Thingy
{
}

View file

@ -0,0 +1,12 @@
using System;
namespace PortableApp
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}

View file

@ -1,14 +1,17 @@
{
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
},
"frameworks": {
"netstandardapp1.5": {
"netstandard1.5": {
"imports": [
"dnxcore50",
"portable-net45+win8"
],
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23901"
"Microsoft.NETCore.App": "1.0.0-rc2-23911"
}
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace PortableAppWithNative
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}

View file

@ -0,0 +1,14 @@
{
"compilationOptions": {
"emitEntryPoint": true
},
"frameworks": {
"netstandard1.5": {
"imports": [ "dnxcore50", "portable-net45+win8" ],
"dependencies": {
"Microsoft.NETCore.App": "1.0.0-rc2-23911",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*"
}
}
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace StandaloneApp
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}

View file

@ -1,5 +1,7 @@
{
"dependencies": { },
"compilationOptions": {
"emitEntryPoint": true
},
"frameworks": {
"netstandardapp1.5": {
"imports": [
@ -7,7 +9,7 @@
"portable-net45+win8"
],
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23901"
"Microsoft.NETCore.App": "1.0.0-rc2-23911"
}
}
},

View file

@ -0,0 +1,2 @@
# What is this?
This is a test wrapped project where we've checked in the binaries. To protect it from the build scripts cleaning the `bin` folder, we've renamed that folder to `bin.keep`. Please don't rename it!

View file

@ -3,8 +3,8 @@
"netstandardapp1.5": {
"imports": "dnxcore50",
"bin": {
"assembly": "bin\\{configuration}\\dnxcore50\\TestLibrary.dll",
"pdb": "bin\\{configuration}\\dnxcore50\\TestLibrary.pdb"
"assembly": "bin.keep\\{configuration}\\dnxcore50\\TestLibrary.dll",
"pdb": "bin.keep\\{configuration}\\dnxcore50\\TestLibrary.pdb"
}
}
}

View file

@ -44,7 +44,7 @@ namespace Microsoft.DotNet.Cli.Build
[Target(nameof(SetupTestPackages), nameof(SetupTestProjects))]
public static BuildTargetResult SetupTests(BuildTargetContext c) => c.Success();
[Target(nameof(RestoreTestAssetPackages), nameof(BuildTestAssetPackages))]
public static BuildTargetResult SetupTestPackages(BuildTargetContext c) => c.Success();
@ -64,7 +64,7 @@ namespace Microsoft.DotNet.Cli.Build
return c.Success();
}
[Target]
public static BuildTargetResult RestoreTestAssetProjects(BuildTargetContext c)
{
@ -74,16 +74,16 @@ namespace Microsoft.DotNet.Cli.Build
CleanNuGetTempCache();
var dotnet = DotNetCli.Stage2;
dotnet.Restore("--fallbacksource", Dirs.TestPackages)
.WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestProjects"))
.Execute().EnsureSuccessful();
// The 'ProjectModelServer' directory contains intentionally-unresolved dependencies, so don't check for success. Also, suppress the output
dotnet.Restore()
.WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "ProjectModelServer", "DthTestProjects"))
.Execute();
dotnet.Restore()
.WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "ProjectModelServer", "DthUpdateSearchPathSample"))
.Execute();
@ -94,6 +94,8 @@ namespace Microsoft.DotNet.Cli.Build
[Target(nameof(CleanTestPackages))]
public static BuildTargetResult BuildTestAssetPackages(BuildTargetContext c)
{
CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestPackages"));
var dotnet = DotNetCli.Stage2;
Rmdir(Dirs.TestPackages);
@ -108,21 +110,23 @@ namespace Microsoft.DotNet.Cli.Build
.Execute()
.EnsureSuccessful();
}
return c.Success();
}
[Target]
public static BuildTargetResult CleanTestPackages(BuildTargetContext c)
{
Rmdir(Path.Combine(Dirs.NuGetPackages, "dotnet-hello"));
return c.Success();
}
[Target]
public static BuildTargetResult BuildTestAssetProjects(BuildTargetContext c)
{
CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestProjects"));
var dotnet = DotNetCli.Stage2;
var nobuildFileName = ".noautobuild";
string testProjectsRoot = Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestProjects");
@ -137,7 +141,7 @@ namespace Microsoft.DotNet.Cli.Build
.Execute()
.EnsureSuccessful();
}
return c.Success();
}
@ -246,7 +250,7 @@ namespace Microsoft.DotNet.Cli.Build
{
return new Dictionary<string, string>();
}
c.Verbose("Start Collecting Visual Studio Environment Variables");
var vsvarsPath = Path.GetFullPath(Path.Combine(Environment.GetEnvironmentVariable("VS140COMNTOOLS"), "..", "..", "VC"));
@ -273,14 +277,14 @@ set");
File.Delete(temp);
}
}
result.EnsureSuccessful();
var vars = new Dictionary<string, string>();
foreach (var line in result.StdOut.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
{
var splat = line.Split(new[] { '=' }, 2);
if (splat.Length == 2)
{
c.Verbose($"Adding variable '{line}'");
@ -291,9 +295,8 @@ set");
c.Info($"Skipping VS Env Variable. Unknown format: '{line}'");
}
}
c.Verbose("Finish Collecting Visual Studio Environment Variables");
return vars;
}
}

View file

@ -25,12 +25,12 @@ namespace Microsoft.DotNet.Cli.Utils
{
if (environment == null)
{
throw new ArgumentNullException("environment");
throw new ArgumentNullException(nameof(environment));
}
if (packagedCommandSpecFactory == null)
{
throw new ArgumentNullException("packagedCommandSpecFactory");
throw new ArgumentNullException(nameof(packagedCommandSpecFactory));
}
_environment = environment;

View file

@ -49,6 +49,9 @@ namespace Microsoft.DotNet.Cli.Utils
var outputBinaryPath = Path.Combine(destinationPath, outputBinaryName);
var hostBinaryPath = Path.Combine(HostDir, binaryName);
File.Copy(hostBinaryPath, outputBinaryPath, overwrite: true);
// Update the last write time so this file can be treated as an output of a build
File.SetLastWriteTimeUtc(outputBinaryPath, DateTime.UtcNow);
}
}
}

View file

@ -14,11 +14,16 @@ using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.Extensions.DependencyModel;
using NuGet.Frameworks;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
namespace Microsoft.Dotnet.Cli.Compiler.Common
{
public class Executable
{
// GROOOOOSS
private static readonly string RedistPackageName = "Microsoft.NETCore.App";
private readonly ProjectContext _context;
private readonly LibraryExporter _exporter;
@ -71,7 +76,8 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common
{
WriteDepsFileAndCopyProjectDependencies(_exporter);
if (!string.IsNullOrEmpty(_context.RuntimeIdentifier))
var emitEntryPoint = _context.ProjectFile.GetCompilerOptions(_context.TargetFramework, _configuration).EmitEntryPoint ?? false;
if (emitEntryPoint && !string.IsNullOrEmpty(_context.RuntimeIdentifier))
{
// TODO: Pick a host based on the RID
CoreHost.CopyTo(_runtimeOutputPath, _context.ProjectFile.Name + Constants.ExeSuffix);
@ -106,6 +112,7 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common
private void WriteDepsFileAndCopyProjectDependencies(LibraryExporter exporter)
{
WriteDeps(exporter);
WriteRuntimeConfig(exporter);
var projectExports = exporter.GetDependencies(LibraryType.Project);
CopyAssemblies(projectExports);
@ -115,6 +122,37 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common
CopyAssets(packageExports);
}
private void WriteRuntimeConfig(LibraryExporter exporter)
{
if (!_context.TargetFramework.IsDesktop())
{
// TODO: Suppress this file if there's nothing to write? RuntimeOutputFiles would have to be updated
// in order to prevent breaking incremental compilation...
var json = new JObject();
var runtimeOptions = new JObject();
json.Add("runtimeOptions", runtimeOptions);
var redistExport = exporter
.GetAllExports()
.FirstOrDefault(l => l.Library.Identity.Name.Equals(RedistPackageName, StringComparison.OrdinalIgnoreCase));
if (redistExport != null)
{
var framework = new JObject(
new JProperty("name", redistExport.Library.Identity.Name),
new JProperty("version", redistExport.Library.Identity.Version.ToNormalizedString()));
runtimeOptions.Add("framework", framework);
}
var runtimeConfigJsonFile = Path.Combine(_runtimeOutputPath, _context.ProjectFile.Name + FileNameSuffixes.RuntimeConfigJson);
using (var writer = new JsonTextWriter(new StreamWriter(File.Create(runtimeConfigJsonFile))))
{
writer.Formatting = Formatting.Indented;
json.WriteTo(writer);
}
}
}
public void WriteDeps(LibraryExporter exporter)
{
var path = Path.Combine(_runtimeOutputPath, _context.ProjectFile.Name + FileNameSuffixes.Deps);
@ -128,7 +166,7 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common
var exports = exporter.GetAllExports().ToArray();
var dependencyContext = new DependencyContextBuilder().Build(
compilerOptions: includeCompile? compilerOptions: null,
compilerOptions: includeCompile ? compilerOptions : null,
compilationExports: includeCompile ? exports : null,
runtimeExports: exports,
portable: string.IsNullOrEmpty(_context.RuntimeIdentifier),
@ -141,10 +179,8 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common
{
writer.Write(dependencyContext, fileStream);
}
}
public void GenerateBindingRedirects(LibraryExporter exporter)
{
var outputName = _outputPaths.RuntimeFiles.Assembly;

View file

@ -15,6 +15,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation
public string Name { get; }
public string RelativePath { get; }
public string ResolvedPath { get; }
public string FileName => Path.GetFileName(RelativePath);
public Action<Stream, Stream> Transform { get; set; }
public LibraryAsset(string name, string relativePath, string resolvedPath, Action<Stream, Stream> transform = null)

View file

@ -7,6 +7,7 @@ namespace Microsoft.DotNet.ProjectModel
{
public const string Deps = ".deps";
public const string DepsJson = ".deps.json";
public const string RuntimeConfigJson = ".runtimeconfig.json";
public static PlatformFileNameSuffixes CurrentPlatform
{

View file

@ -70,11 +70,7 @@ namespace Microsoft.DotNet.ProjectModel
var compilationFiles = new CompilationOutputFiles(compilationOutputPath, project, configuration, framework);
RuntimeOutputFiles runtimeFiles = null;
if (runtimeOutputPath != null)
{
runtimeFiles = new RuntimeOutputFiles(runtimeOutputPath, project, configuration, framework);
}
RuntimeOutputFiles runtimeFiles = new RuntimeOutputFiles(runtimeOutputPath, project, configuration, framework, runtimeIdentifier);
return new OutputPaths(intermediateOutputPath, compilationOutputPath, runtimeOutputPath, compilationFiles, runtimeFiles);
}
}

View file

@ -140,6 +140,12 @@ namespace Microsoft.DotNet.ProjectModel
public ProjectContext CreateRuntimeContext(IEnumerable<string> runtimeIdentifiers)
{
// Temporary until we have removed RID inference from NuGet
if(TargetFramework.IsCompileOnly)
{
return this;
}
// Check if there are any runtime targets (i.e. are we portable)
var standalone = LockFile.Targets
.Where(t => t.TargetFramework.Equals(TargetFramework))

View file

@ -9,11 +9,15 @@ namespace Microsoft.DotNet.ProjectModel
{
public class RuntimeOutputFiles : CompilationOutputFiles
{
private readonly string _runtimeIdentifier;
public RuntimeOutputFiles(string basePath,
Project project,
string configuration,
NuGetFramework framework) : base(basePath, project, configuration, framework)
NuGetFramework framework,
string runtimeIdentifier) : base(basePath, project, configuration, framework)
{
_runtimeIdentifier = runtimeIdentifier;
}
public string Executable
@ -39,6 +43,7 @@ namespace Microsoft.DotNet.ProjectModel
return Path.ChangeExtension(Assembly, FileNameSuffixes.Deps);
}
}
public string DepsJson
{
get
@ -47,6 +52,14 @@ namespace Microsoft.DotNet.ProjectModel
}
}
public string RuntimeConfigJson
{
get
{
return Path.ChangeExtension(Assembly, FileNameSuffixes.RuntimeConfigJson);
}
}
public string Config
{
get { return Assembly + ".config"; }
@ -59,20 +72,28 @@ namespace Microsoft.DotNet.ProjectModel
yield return file;
}
if (Project.HasRuntimeOutput(Config))
{
if (!Framework.IsDesktop())
{
yield return Deps;
yield return DepsJson;
yield return RuntimeConfigJson;
}
// If the project actually has an entry point AND we're doing a standalone build
var hasEntryPoint = Project.GetCompilerOptions(targetFramework: null, configurationName: Configuration).EmitEntryPoint ?? false;
if (hasEntryPoint && !string.IsNullOrEmpty(_runtimeIdentifier))
{
// Yield the executable
yield return Executable;
}
}
if (File.Exists(Config))
{
yield return Config;
}
if (File.Exists(Deps))
{
yield return Deps;
}
if (File.Exists(DepsJson))
{
yield return DepsJson;
}
}
}
}
}

View file

@ -92,7 +92,7 @@ namespace Microsoft.DotNet.TestFramework
{
throw new Exception($"Cannot find '{testProjectName}' at '{AssetsRoot}'");
}
string testDestination = Path.Combine(AppContext.BaseDirectory, callingMethod + identifier, testProjectName);
var testInstance = new TestInstance(testProjectDir, testDestination);
return testInstance;

View file

@ -33,7 +33,7 @@ namespace Microsoft.DotNet.Tools.Build
{
_rootProject = rootProject;
// Cleaner to clone the args and mutate the clone than have separate CompileContext fields for mutated args
// Cleaner to clone the args and mutate the clone than have separate CompileContext fields for mutated args
// and then reasoning which ones to get from args and which ones from fields.
_args = (BuilderCommandApp)args.ShallowCopy();
@ -420,12 +420,6 @@ namespace Microsoft.DotNet.Tools.Build
private void MakeRunnable()
{
var runtimeContext = _rootProject.CreateRuntimeContext(_args.GetRuntimes());
if(_args.PortableMode)
{
// HACK: Force the use of the portable target
runtimeContext = _rootProject;
}
var outputPaths = runtimeContext.GetOutputPaths(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue);
var libraryExporter = runtimeContext.CreateExporter(_args.ConfigValue, _args.BuildBasePathValue);
@ -436,6 +430,7 @@ namespace Microsoft.DotNet.Tools.Build
CopyCompilationOutput(outputPaths);
}
var options = runtimeContext.ProjectFile.GetCompilerOptions(runtimeContext.TargetFramework, _args.ConfigValue);
var executable = new Executable(runtimeContext, outputPaths, libraryExporter, _args.ConfigValue);
executable.MakeCompilationOutputRunnable();
@ -446,24 +441,22 @@ namespace Microsoft.DotNet.Tools.Build
// time. See: https://github.com/dotnet/cli/issues/1374
private static void PatchMscorlibNextToCoreClr(ProjectContext context, string config)
{
foreach (var exp in context.CreateExporter(config).GetAllExports())
{
foreach (var exp in context.CreateExporter(config).GetAllExports())
var coreclrLib = exp.NativeLibraries.FirstOrDefault(nLib =>
string.Equals(Constants.LibCoreClrFileName, nLib.Name));
if (string.IsNullOrEmpty(coreclrLib.ResolvedPath))
{
var coreclrLib = exp.NativeLibraries.FirstOrDefault(nLib =>
string.Equals(Constants.LibCoreClrFileName, nLib.Name));
if (string.IsNullOrEmpty(coreclrLib.ResolvedPath))
{
continue;
}
var coreclrDir = Path.GetDirectoryName(coreclrLib.ResolvedPath);
if (File.Exists(Path.Combine(coreclrDir, "mscorlib.dll")) ||
File.Exists(Path.Combine(coreclrDir, "mscorlib.ni.dll")))
{
continue;
}
var mscorlibFile = exp.RuntimeAssemblies.FirstOrDefault(r => r.Name.Equals("mscorlib") || r.Name.Equals("mscorlib.ni")).ResolvedPath;
File.Copy(mscorlibFile, Path.Combine(coreclrDir, Path.GetFileName(mscorlibFile)), overwrite: true);
continue;
}
var coreclrDir = Path.GetDirectoryName(coreclrLib.ResolvedPath);
if (File.Exists(Path.Combine(coreclrDir, "mscorlib.dll")) ||
File.Exists(Path.Combine(coreclrDir, "mscorlib.ni.dll")))
{
continue;
}
var mscorlibFile = exp.RuntimeAssemblies.FirstOrDefault(r => r.Name.Equals("mscorlib") || r.Name.Equals("mscorlib.ni")).ResolvedPath;
File.Copy(mscorlibFile, Path.Combine(coreclrDir, Path.GetFileName(mscorlibFile)), overwrite: true);
}
}
@ -533,12 +526,16 @@ namespace Microsoft.DotNet.Tools.Build
// input: dependencies
AddDependencies(dependencies, compilerIO);
var allOutputPath = new List<string>(calculator.CompilationFiles.All());
var allOutputPath = new HashSet<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());
foreach (var path in runtimeContext.GetOutputPaths(buildConfiguration, buildBasePath, outputPath).RuntimeFiles.All())
{
allOutputPath.Add(path);
}
}
// output: compiler outputs
foreach (var path in allOutputPath)
{

View file

@ -29,7 +29,6 @@ namespace Microsoft.DotNet.Tools.Compiler
private CommandOption _runtimeOption;
private CommandOption _versionSuffixOption;
private CommandOption _configurationOption;
private CommandOption _portableOption;
private CommandArgument _projectArgument;
private CommandOption _nativeOption;
private CommandOption _archOption;
@ -55,7 +54,6 @@ namespace Microsoft.DotNet.Tools.Compiler
public bool IsCppModeValue { get; set; }
public string AppDepSdkPathValue { get; set; }
public string CppCompilerFlagsValue { get; set; }
public bool PortableMode { get; set; }
// workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params
private readonly Dictionary<string, CommandOption> baseClassOptions;
@ -86,9 +84,6 @@ namespace Microsoft.DotNet.Tools.Compiler
_versionSuffixOption = _app.Option("--version-suffix <VERSION_SUFFIX>", "Defines what `*` should be replaced with in version field in project.json", CommandOptionType.SingleValue);
_projectArgument = _app.Argument("<PROJECT>", "The project to compile, defaults to the current directory. Can be a path to a project.json or a project directory");
// HACK: Allow us to treat a project as though it was portable by ignoring the runtime-specific targets. This is temporary until RID inference is removed from NuGet
_portableOption = _app.Option("--portable", "TEMPORARY: Enforces portable build/publish mode", CommandOptionType.NoValue);
// Native Args
_nativeOption = _app.Option("-n|--native", "Compiles source to native machine code.", CommandOptionType.NoValue);
_archOption = _app.Option("-a|--arch <ARCH>", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue);
@ -122,7 +117,6 @@ namespace Microsoft.DotNet.Tools.Compiler
ConfigValue = _configurationOption.Value() ?? Constants.DefaultConfiguration;
RuntimeValue = _runtimeOption.Value();
VersionSuffixValue = _versionSuffixOption.Value();
PortableMode = _portableOption.HasValue();
IsNativeValue = _nativeOption.HasValue();
ArchValue = _archOption.Value();

View file

@ -114,7 +114,7 @@ namespace Microsoft.DotNet.Tools.Compiler
var dependencyContext = new DependencyContextBuilder().Build(compilationOptions,
allExports,
allExports,
args.PortableMode,
true, // For now, just assume portable mode in the legacy deps file (this is going away soon anyway)
context.TargetFramework,
context.RuntimeIdentifier ?? string.Empty);

View file

@ -29,7 +29,7 @@ namespace Microsoft.DotNet.Tools.Publish
public string Runtime { get; set; }
public bool NativeSubdirectories { get; set; }
public NuGetFramework NugetFramework { get; set; }
public IEnumerable<ProjectContext> ProjectContexts { get; set; }
public IList<ProjectContext> ProjectContexts { get; set; }
public string VersionSuffix { get; set; }
public int NumberOfProjects { get; private set; }
public int NumberOfPublishedProjects { get; private set; }
@ -47,10 +47,10 @@ namespace Microsoft.DotNet.Tools.Publish
}
}
ProjectContexts = SelectContexts(ProjectPath, NugetFramework, Runtime);
ProjectContexts = SelectContexts(ProjectPath, NugetFramework, Runtime).ToList();
if (!ProjectContexts.Any())
{
string errMsg = $"'{ProjectPath}' cannot be published for '{Framework ?? "<no framework provided>"}' '{Runtime ?? "<no runtime provided>"}'";
string errMsg = $"'{ProjectPath}' cannot be published for '{Framework ?? "<no framework provided>"}' '{Runtime ?? "<no runtime provided>"}'";
Reporter.Output.WriteLine(errMsg.Red());
return false;
}
@ -84,7 +84,12 @@ namespace Microsoft.DotNet.Tools.Publish
/// <returns>Return 0 if successful else return non-zero</returns>
private bool PublishProjectContext(ProjectContext context, string buildBasePath, string outputPath, string configuration, bool nativeSubdirectories)
{
Reporter.Output.WriteLine($"Publishing {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}/{context.RuntimeIdentifier.Yellow()}");
var target = context.TargetFramework.DotNetFrameworkName;
if (!string.IsNullOrEmpty(context.RuntimeIdentifier))
{
target = $"{target}/{context.RuntimeIdentifier}";
}
Reporter.Output.WriteLine($"Publishing {context.RootProject.Identity.Name.Yellow()} for {target.Yellow()}");
var options = context.ProjectFile.GetCompilerOptions(context.TargetFramework, configuration);
var outputPaths = context.GetOutputPaths(configuration, buildBasePath, outputPath);
@ -115,13 +120,17 @@ namespace Microsoft.DotNet.Tools.Publish
var args = new List<string>() {
"--framework",
$"{context.TargetFramework.DotNetFrameworkName}",
"--runtime",
context.RuntimeIdentifier,
"--configuration",
configuration,
context.ProjectFile.ProjectDirectory
};
if (!string.IsNullOrEmpty(context.RuntimeIdentifier))
{
args.Insert(0, context.RuntimeIdentifier);
args.Insert(0, "--runtime");
}
if (!string.IsNullOrEmpty(VersionSuffix))
{
args.Add("--version-suffix");
@ -147,23 +156,43 @@ namespace Microsoft.DotNet.Tools.Publish
{
Reporter.Verbose.WriteLine($"Publishing {export.Library.Identity.ToString().Green().Bold()} ...");
PublishFiles(export.RuntimeAssemblies, outputPath, nativeSubdirectories: false);
PublishFiles(export.NativeLibraries, outputPath, nativeSubdirectories);
PublishFiles(export.RuntimeAssemblies, outputPath, nativeSubdirectories: false, preserveRelativePath: false);
PublishFiles(export.NativeLibraries, outputPath, nativeSubdirectories, preserveRelativePath: false);
export.RuntimeAssets.StructuredCopyTo(outputPath, outputPaths.IntermediateOutputDirectoryPath);
if (string.IsNullOrEmpty(context.RuntimeIdentifier))
{
var assets = export.RuntimeTargets.SelectMany(t => Enumerable.Concat(t.NativeLibraries, t.RuntimeAssemblies));
PublishFiles(assets, outputPath, nativeSubdirectories: false, preserveRelativePath: true);
}
if (options.PreserveCompilationContext.GetValueOrDefault())
{
PublishRefs(export, outputPath);
}
}
if (context.ProjectFile.HasRuntimeOutput(configuration) && !context.TargetFramework.IsDesktop())
{
// Get the output paths used by the call to `dotnet build` above (since we didn't pass `--output`, they will be different from
// our current output paths)
var buildOutputPaths = context.GetOutputPaths(configuration, buildBasePath);
PublishFiles(
new[] {
buildOutputPaths.RuntimeFiles.Deps,
buildOutputPaths.RuntimeFiles.DepsJson,
buildOutputPaths.RuntimeFiles.RuntimeConfigJson
},
outputPath);
}
var contentFiles = new ContentFiles(context);
contentFiles.StructuredCopyTo(outputPath);
// Publish a host if this is an application
if (options.EmitEntryPoint.GetValueOrDefault())
if (options.EmitEntryPoint.GetValueOrDefault() && !string.IsNullOrEmpty(context.RuntimeIdentifier))
{
Reporter.Verbose.WriteLine($"Making {context.ProjectFile.Name.Cyan()} runnable ...");
Reporter.Verbose.WriteLine($"Copying native host to output to create fully standalone output.");
PublishHost(context, outputPath);
}
@ -228,18 +257,23 @@ namespace Microsoft.DotNet.Tools.Publish
}
}
private static void PublishFiles(IEnumerable<LibraryAsset> files, string outputPath, bool nativeSubdirectories)
private static void PublishFiles(IEnumerable<LibraryAsset> files, string outputPath, bool nativeSubdirectories, bool preserveRelativePath)
{
foreach (var file in files)
{
var destinationDirectory = DetermineFileDestinationDirectory(file, outputPath, nativeSubdirectories);
if (preserveRelativePath)
{
destinationDirectory = Path.Combine(destinationDirectory, Path.GetDirectoryName(file.RelativePath));
}
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
File.Copy(file.ResolvedPath, Path.Combine(destinationDirectory, Path.GetFileName(file.ResolvedPath)), overwrite: true);
File.Copy(file.ResolvedPath, Path.Combine(destinationDirectory, file.FileName), overwrite: true);
}
}
@ -272,29 +306,46 @@ namespace Microsoft.DotNet.Tools.Publish
return candidate;
}
private static IEnumerable<ProjectContext> SelectContexts(string projectPath, NuGetFramework framework, string runtime)
private IEnumerable<ProjectContext> SelectContexts(string projectPath, NuGetFramework framework, string runtime)
{
var allContexts = ProjectContext.CreateContextForEachTarget(projectPath);
var allContexts = ProjectContext.CreateContextForEachTarget(projectPath).ToList();
var frameworks = framework == null ?
allContexts.Select(c => c.TargetFramework).Distinct().ToArray() :
new[] { framework };
if (string.IsNullOrEmpty(runtime))
{
// Nothing was specified, so figure out what the candidate runtime identifiers are and try each of them
// Temporary until #619 is resolved
foreach (var candidate in PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers())
{
var contexts = GetMatchingProjectContexts(allContexts, framework, candidate);
if (contexts.Any())
{
return contexts;
}
}
return Enumerable.Empty<ProjectContext>();
// For each framework, find the best matching RID item
var candidates = PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers();
return frameworks.Select(f => FindBestTarget(f, allContexts, candidates));
}
else
{
return GetMatchingProjectContexts(allContexts, framework, runtime);
return frameworks.SelectMany(f => allContexts.Where(c =>
Equals(c.TargetFramework, f) &&
string.Equals(c.RuntimeIdentifier, runtime, StringComparison.Ordinal)));
}
}
private ProjectContext FindBestTarget(NuGetFramework f, List<ProjectContext> allContexts, IEnumerable<string> candidates)
{
foreach (var candidate in candidates)
{
var target = allContexts.FirstOrDefault(c =>
Equals(c.TargetFramework, f) &&
string.Equals(c.RuntimeIdentifier, candidate, StringComparison.Ordinal));
if (target != null)
{
return target;
}
}
// No RID-specific target found, use the RID-less target and publish portable
return allContexts.FirstOrDefault(c =>
Equals(c.TargetFramework, f) &&
string.IsNullOrEmpty(c.RuntimeIdentifier));
}
/// <summary>
/// Return the matching framework/runtime ProjectContext.
/// If 'framework' or 'runtimeIdentifier' is null or empty then it matches with any.

View file

@ -47,7 +47,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
}
public AndConstraint<DirectoryInfoAssertions> HaveFiles(IEnumerable<string> expectedFiles)
{
{
foreach (var expectedFile in expectedFiles)
{
HaveFile(expectedFile);
@ -61,7 +61,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
var dir = _dirInfo.EnumerateDirectories(expectedDir, SearchOption.TopDirectoryOnly).SingleOrDefault();
Execute.Assertion.ForCondition(dir != null)
.FailWith("Expected directory {0} cannot be found inside directory {1}.", expectedDir, _dirInfo.FullName);
return new AndConstraint<DirectoryInfoAssertions>(new DirectoryInfoAssertions(dir));
}
}

View file

@ -29,7 +29,6 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
private readonly bool _noIncremental;
private readonly bool _noDependencies;
private readonly string _runtime;
private readonly bool _forcePortable;
private string OutputOption
{
@ -41,16 +40,6 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
}
}
private string ForcePortableOption
{
get
{
return _forcePortable ?
"--portable" :
string.Empty;
}
}
private string BuildBasePathOption
{
get
@ -228,9 +217,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
string cppCompilerFlags="",
bool buildProfile=true,
bool noIncremental=false,
bool noDependencies=false,
bool forcePortable=false
)
bool noDependencies=false)
: base("dotnet")
{
_projectPath = projectPath;
@ -253,7 +240,6 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
_buildProfile = buildProfile;
_noIncremental = noIncremental;
_noDependencies = noDependencies;
_forcePortable = forcePortable;
}
public override CommandResult Execute(string args = "")
@ -277,7 +263,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
private string BuildArgs()
{
return $"{BuildProfile} {ForcePortableOption} {NoDependencies} {NoIncremental} \"{_projectPath}\" {OutputOption} {BuildBasePathOption} {ConfigurationOption} {FrameworkOption} {RuntimeOption} {VersionSuffixOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}";
return $"{BuildProfile} {NoDependencies} {NoIncremental} \"{_projectPath}\" {OutputOption} {BuildBasePathOption} {ConfigurationOption} {FrameworkOption} {RuntimeOption} {VersionSuffixOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}";
}
}
}

View file

@ -1,16 +1,12 @@
// 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 Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.Extensions.PlatformAbstractions;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Test.Utilities;
using Microsoft.Extensions.PlatformAbstractions;
namespace Microsoft.DotNet.Tools.Test.Utilities
{
@ -18,14 +14,14 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
{
private const string PublishSubfolderName = "publish";
private Project _project;
private string _path;
private string _framework;
private string _runtime;
private string _config;
private string _output;
private readonly Project _project;
private readonly string _path;
private readonly string _framework;
private readonly string _runtime;
private readonly string _config;
private readonly string _output;
public PublishCommand(string projectPath, string framework="", string runtime="", string output="", string config="")
public PublishCommand(string projectPath, string framework = "", string runtime = "", string output = "", string config = "", bool forcePortable = false)
: base("dotnet")
{
_path = projectPath;
@ -36,7 +32,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
_config = config;
}
public override CommandResult Execute(string args="")
public override CommandResult Execute(string args = "")
{
args = $"publish {BuildArgs()} {args}";
return base.Execute(args);
@ -48,34 +44,33 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
return base.ExecuteWithCapturedOutput(args);
}
public string ProjectName
{
get
{
return _project.Name;
}
}
public string ProjectName => _project.Name;
private string BuildRelativeOutputPath()
private string BuildRelativeOutputPath(bool portable)
{
// lets try to build an approximate output path
string config = string.IsNullOrEmpty(_config) ? "Debug" : _config;
string framework = string.IsNullOrEmpty(_framework) ?
_project.GetTargetFrameworks().First().FrameworkName.GetShortFolderName() : _framework;
string runtime = string.IsNullOrEmpty(_runtime) ? PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier() : _runtime;
string output = Path.Combine(config, framework, runtime, PublishSubfolderName);
return output;
if (!portable)
{
var runtime = string.IsNullOrEmpty(_runtime) ? PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier() : _runtime;
return Path.Combine(config, framework, runtime, PublishSubfolderName);
}
else
{
return Path.Combine(config, framework, PublishSubfolderName);
}
}
public DirectoryInfo GetOutputDirectory()
public DirectoryInfo GetOutputDirectory(bool portable = false)
{
if (!string.IsNullOrEmpty(_output))
{
return new DirectoryInfo(_output);
}
string output = Path.Combine(_project.ProjectDirectory, "bin", BuildRelativeOutputPath());
string output = Path.Combine(_project.ProjectDirectory, "bin", BuildRelativeOutputPath(portable));
return new DirectoryInfo(output);
}
@ -88,27 +83,12 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
private string BuildArgs()
{
return $"{_path} {GetFrameworkOption()} {GetRuntimeOption()} {GetOutputOption()} {GetConfigOption()}";
return $"{_path} {FrameworkOption} {RuntimeOption} {OutputOption} {ConfigOption}";
}
private string GetFrameworkOption()
{
return string.IsNullOrEmpty(_framework) ? "" : $"-f {_framework}";
}
private string GetRuntimeOption()
{
return string.IsNullOrEmpty(_runtime) ? "" : $"-r {_runtime}";
}
private string GetOutputOption()
{
return string.IsNullOrEmpty(_output) ? "" : $"-o \"{_output}\"";
}
private string GetConfigOption()
{
return string.IsNullOrEmpty(_config) ? "" : $"-c {_output}";
}
private string FrameworkOption => string.IsNullOrEmpty(_framework) ? "" : $"-f {_framework}";
private string RuntimeOption => string.IsNullOrEmpty(_runtime) ? "" : $"-r {_runtime}";
private string OutputOption => string.IsNullOrEmpty(_output) ? "" : $"-o \"{_output}\"";
private string ConfigOption => string.IsNullOrEmpty(_config) ? "" : $"-c {_output}";
}
}

View file

@ -9,11 +9,11 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
[Fact]
public void ErrorOccursWhenBuildingPortableProjectToSpecificOutputPathWithoutSpecifyingFramework()
{
var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject")
var testInstance = TestAssetsManager.CreateTestInstance("PortableTests")
.WithLockFiles();
var result = new BuildCommand(
projectPath: testInstance.TestRoot,
projectPath: Path.Combine(testInstance.TestRoot, "PortableApp"),
output: Path.Combine(testInstance.TestRoot, "out"))
.ExecuteWithCapturedOutput();
@ -24,11 +24,11 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
[Fact]
public void ErrorOccursWhenBuildingPortableProjectAndSpecifyingFrameworkThatProjectDoesNotSupport()
{
var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject")
var testInstance = TestAssetsManager.CreateTestInstance("PortableTests")
.WithLockFiles();
var result = new BuildCommand(
projectPath: testInstance.TestRoot,
projectPath: Path.Combine(testInstance.TestRoot, "PortableApp"),
output: Path.Combine(testInstance.TestRoot, "out"),
framework: "sl40")
.ExecuteWithCapturedOutput();
@ -40,11 +40,11 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
[Fact]
public void ErrorOccursWhenBuildingStandaloneProjectToSpecificOutputPathWithoutSpecifyingFramework()
{
var testInstance = TestAssetsManager.CreateTestInstance("BuildTestStandaloneProject")
var testInstance = TestAssetsManager.CreateTestInstance("PortableTests")
.WithLockFiles();
var result = new BuildCommand(
projectPath: testInstance.TestRoot,
projectPath: Path.Combine(testInstance.TestRoot, "StandaloneApp"),
output: Path.Combine(testInstance.TestRoot, "out"))
.ExecuteWithCapturedOutput();

View file

@ -39,7 +39,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
private readonly string[] _libCompileFiles =
{
"TestLibrary" + FileNameSuffixes.DotNet.DynamicLib,
"TestLibrary" + FileNameSuffixes.DotNet.ProgramDatabase,
"TestLibrary" + FileNameSuffixes.DotNet.ProgramDatabase
};
private void GetProjectInfo(string testRoot)
@ -114,7 +114,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
fileVersion.Should().NotBeNull();
fileVersion.Should().BeEquivalentTo("1.0.0.345");
}
[Fact]
public void SettingVersionSuffixFlag_ShouldStampAssemblyInfoInOutputAssembly()
{

View file

@ -9,28 +9,27 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
[Fact]
public void BuildingAPortableProjectProducesDepsFile()
{
var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject")
var testInstance = TestAssetsManager.CreateTestInstance("PortableTests")
.WithLockFiles();
var result = new BuildCommand(
projectPath: testInstance.TestRoot,
forcePortable: true)
projectPath: Path.Combine(testInstance.TestRoot, "PortableApp"))
.ExecuteWithCapturedOutput();
result.Should().Pass();
var outputBase = new DirectoryInfo(Path.Combine(testInstance.TestRoot, "bin", "Debug"));
var outputBase = new DirectoryInfo(Path.Combine(testInstance.TestRoot, "PortableApp", "bin", "Debug"));
var netstandardappOutput = outputBase.Sub("netstandardapp1.5");
var netstandardappOutput = outputBase.Sub("netstandard1.5");
netstandardappOutput.Should()
.Exist().And
.HaveFiles(new[]
{
"BuildTestPortableProject.deps",
"BuildTestPortableProject.deps.json",
"BuildTestPortableProject.dll",
"BuildTestPortableProject.pdb"
"PortableApp.deps",
"PortableApp.deps.json",
"PortableApp.dll",
"PortableApp.pdb"
});
}
}

View file

@ -0,0 +1,54 @@
using Microsoft.DotNet.Tools.Test.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using Xunit;
namespace Microsoft.DotNet.Tools.Publish.Tests
{
public class PublishPortableTests : TestBase
{
private static readonly IEnumerable<Tuple<string, string>> ExpectedRuntimeOutputs = new[] {
Tuple.Create("debian-x64", "libuv.so"),
Tuple.Create("rhel-x64", "libuv.so"),
Tuple.Create("osx", "libuv.dylib"),
Tuple.Create("win7-arm", "libuv.dll"),
Tuple.Create("win7-x86", "libuv.dll"),
Tuple.Create("win7-x64", "libuv.dll")
};
[Fact]
public void PortableAppWithRuntimeTargetsIsPublishedCorrectly()
{
var testInstance = TestAssetsManager.CreateTestInstance("PortableTests")
.WithLockFiles();
var publishCommand = new PublishCommand(Path.Combine(testInstance.TestRoot, "PortableAppWithNative"));
var publishResult = publishCommand.Execute();
publishResult.Should().Pass();
var publishDir = publishCommand.GetOutputDirectory(portable: true);
publishDir.Should().HaveFiles(new[]
{
"PortableAppWithNative.dll",
"PortableAppWithNative.deps",
"PortableAppWithNative.deps.json"
});
var runtimesOutput = publishDir.Sub("runtimes");
runtimesOutput.Should().Exist();
foreach (var output in ExpectedRuntimeOutputs)
{
var ridDir = runtimesOutput.Sub(output.Item1);
ridDir.Should().Exist();
var nativeDir = ridDir.Sub("native");
nativeDir.Should().Exist();
nativeDir.Should().HaveFile(output.Item2);
}
}
}
}