diff --git a/TestAssets/CrossPublishTestProjects/StandaloneAppCrossPublish/.noautobuild b/TestAssets/CrossPublishTestProjects/StandaloneAppCrossPublish/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/CrossPublishTestProjects/StandaloneAppCrossPublish/Program.cs b/TestAssets/CrossPublishTestProjects/StandaloneAppCrossPublish/Program.cs new file mode 100644 index 000000000..529893b0e --- /dev/null +++ b/TestAssets/CrossPublishTestProjects/StandaloneAppCrossPublish/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace StandaloneApp +{ + public static class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } + } +} diff --git a/TestAssets/CrossPublishTestProjects/StandaloneAppCrossPublish/project.json b/TestAssets/CrossPublishTestProjects/StandaloneAppCrossPublish/project.json new file mode 100644 index 000000000..2a1078175 --- /dev/null +++ b/TestAssets/CrossPublishTestProjects/StandaloneAppCrossPublish/project.json @@ -0,0 +1,35 @@ +{ + "compilationOptions": { + "emitEntryPoint": true + }, + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "Microsoft.NETCore.App": "1.0.0-rc2-*" + } + } + }, + "runtimes": { + "win7-x64": {}, + "win7-x86": {}, + "osx.10.10-x64": {}, + "osx.10.11-x64": {}, + "ubuntu.14.04-x64": {}, + "centos.7-x64": {}, + "rhel.7.2-x64": {}, + "debian.8.2-x64": {} + }, + + "runtimeOptions": { + "somethingString": "anything", + "somethingBoolean": true, + "someArray": ["one", "two"], + "someObject": { + "someProperty": "someValue" + } + } +} diff --git a/TestAssets/TestProjects/KestrelSample/KestrelStandalone/project.json b/TestAssets/TestProjects/KestrelSample/KestrelStandalone/project.json index b4587d790..a74bc67c5 100644 --- a/TestAssets/TestProjects/KestrelSample/KestrelStandalone/project.json +++ b/TestAssets/TestProjects/KestrelSample/KestrelStandalone/project.json @@ -12,9 +12,9 @@ "../src/*.cs" ], "frameworks": { - "netstandardapp1.5": { + "netcoreapp1.0": { "dependencies": { - "Microsoft.NETCore.App": "1.0.0-rc2-24008" + "Microsoft.NETCore.App": "1.0.0-rc2-3002306" }, "imports": [ "dnxcore50", diff --git a/scripts/dotnet-cli-build/CompileTargets.cs b/scripts/dotnet-cli-build/CompileTargets.cs index cfbf3b1c0..17056f0f8 100644 --- a/scripts/dotnet-cli-build/CompileTargets.cs +++ b/scripts/dotnet-cli-build/CompileTargets.cs @@ -422,6 +422,9 @@ namespace Microsoft.DotNet.Cli.Build File.Copy( Path.Combine(Dirs.Corehost, HostPolicyBaseName), Path.Combine(SharedFrameworkNameAndVersionRoot, HostPolicyBaseName), true); + File.Copy( + Path.Combine(Dirs.Corehost, DotnetHostFxrBaseName), + Path.Combine(SharedFrameworkNameAndVersionRoot, DotnetHostFxrBaseName), true); if (File.Exists(Path.Combine(SharedFrameworkNameAndVersionRoot, "mscorlib.ni.dll"))) { diff --git a/scripts/dotnet-cli-build/TestTargets.cs b/scripts/dotnet-cli-build/TestTargets.cs index 3c860b7be..d19ef1ea1 100644 --- a/scripts/dotnet-cli-build/TestTargets.cs +++ b/scripts/dotnet-cli-build/TestTargets.cs @@ -58,7 +58,7 @@ namespace Microsoft.DotNet.Cli.Build [Target(nameof(RestoreTestAssetPackages), nameof(BuildTestAssetPackages))] public static BuildTargetResult SetupTestPackages(BuildTargetContext c) => c.Success(); - [Target(nameof(RestoreTestAssetProjects), nameof(RestoreDesktopTestAssetProjects), nameof(BuildTestAssetProjects))] + [Target(nameof(RestoreTestAssetProjects), nameof(RestoreDesktopTestAssetProjects), nameof(RestoreCrossPublishTestAssetProjects), nameof(BuildTestAssetProjects))] public static BuildTargetResult SetupTestProjects(BuildTargetContext c) => c.Success(); [Target] @@ -137,6 +137,18 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } + + [Target] + public static BuildTargetResult RestoreCrossPublishTestAssetProjects(BuildTargetContext c) + { + var dotnet = DotNetCli.Stage2; + + dotnet.Restore("--verbosity", "verbose") + .WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "CrossPublishTestProjects")) + .Execute().EnsureSuccessful(); + + return c.Success(); + } [Target(nameof(CleanTestPackages), nameof(CleanProductPackages))] public static BuildTargetResult BuildTestAssetPackages(BuildTargetContext c) diff --git a/src/Microsoft.DotNet.Cli.Utils/Constants.cs b/src/Microsoft.DotNet.Cli.Utils/Constants.cs index 13a349b13..0550983ba 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Constants.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Constants.cs @@ -34,6 +34,7 @@ namespace Microsoft.DotNet.Cli.Utils public static readonly string ResponseFileSuffix = ".rsp"; + public static readonly string PublishedHostExecutableName = "dotnet"; public static readonly string HostExecutableName = "corehost" + ExeSuffix; public static readonly string[] HostBinaryNames = new string[] { HostExecutableName, diff --git a/src/Microsoft.DotNet.Compiler.Common/LibraryExporterExtensions.cs b/src/Microsoft.DotNet.Compiler.Common/LibraryExporterExtensions.cs index c6c340b1f..52322101e 100644 --- a/src/Microsoft.DotNet.Compiler.Common/LibraryExporterExtensions.cs +++ b/src/Microsoft.DotNet.Compiler.Common/LibraryExporterExtensions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System; using System.Linq; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Compilation; diff --git a/src/dotnet/commands/dotnet-publish/PublishCommand.cs b/src/dotnet/commands/dotnet-publish/PublishCommand.cs index 5c42f70e5..b72df6df9 100644 --- a/src/dotnet/commands/dotnet-publish/PublishCommand.cs +++ b/src/dotnet/commands/dotnet-publish/PublishCommand.cs @@ -139,11 +139,10 @@ namespace Microsoft.DotNet.Tools.Publish var exports = exporter.GetAllExports(); foreach (var export in exports.Where(e => !collectExclusionList.Contains(e.Library.Identity.Name))) { - Reporter.Verbose.WriteLine($"Publishing {export.Library.Identity.ToString().Green().Bold()} ..."); + Reporter.Verbose.WriteLine($"publish: Publishing {export.Library.Identity.ToString().Green().Bold()} ..."); PublishAssetGroups(export.RuntimeAssemblyGroups, outputPath, nativeSubdirectories: false, includeRuntimeGroups: isPortable); PublishAssetGroups(export.NativeLibraryGroups, outputPath, nativeSubdirectories, includeRuntimeGroups: isPortable); - export.RuntimeAssets.StructuredCopyTo(outputPath, outputPaths.IntermediateOutputDirectoryPath); } if (options.PreserveCompilationContext.GetValueOrDefault()) @@ -153,19 +152,9 @@ namespace Microsoft.DotNet.Tools.Publish PublishRefs(export, outputPath, !collectExclusionList.Contains(export.Library.Identity.Name)); } } - - 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.DepsJson, - buildOutputPaths.RuntimeFiles.RuntimeConfigJson - }, - outputPath); - } + + var buildOutputPaths = context.GetOutputPaths(configuration, buildBasePath, null); + PublishBuildOutputFiles(buildOutputPaths, context, outputPath, Configuration); var contentFiles = new ContentFiles(context); contentFiles.StructuredCopyTo(outputPath); @@ -173,17 +162,72 @@ namespace Microsoft.DotNet.Tools.Publish // Publish a host if this is an application if (options.EmitEntryPoint.GetValueOrDefault() && !string.IsNullOrEmpty(context.RuntimeIdentifier)) { - Reporter.Verbose.WriteLine($"Copying native host to output to create fully standalone output."); - PublishHost(context, outputPath, options); + Reporter.Verbose.WriteLine($"publish: Renaming native host in output to create fully standalone output."); + RenamePublishedHost(context, outputPath, options); } RunScripts(context, ScriptNames.PostPublish, contextVariables); - Reporter.Output.WriteLine($"Published to {outputPath}".Green().Bold()); + Reporter.Output.WriteLine($"publish: Published to {outputPath}".Green().Bold()); return true; } + private void PublishBuildOutputFiles(OutputPaths buildOutputPaths, ProjectContext context, string outputPath, string configuration) + { + List filesToPublish = new List(); + + string[] buildOutputFiles = null; + + if (context.ProjectFile.HasRuntimeOutput(configuration)) + { + Reporter.Verbose.WriteLine($"publish: using runtime build output files"); + + buildOutputFiles = new string[] + { + buildOutputPaths.RuntimeFiles.DepsJson, + buildOutputPaths.RuntimeFiles.RuntimeConfigJson, + buildOutputPaths.RuntimeFiles.Config, + buildOutputPaths.RuntimeFiles.Assembly, + buildOutputPaths.RuntimeFiles.PdbPath, + Path.ChangeExtension(buildOutputPaths.RuntimeFiles.Assembly, "xml") + }; + + filesToPublish.AddRange(buildOutputPaths.RuntimeFiles.Resources()); + } + else + { + Reporter.Verbose.WriteLine($"publish: using compilation build output files"); + + buildOutputFiles = new string[] + { + buildOutputPaths.CompilationFiles.Assembly, + buildOutputPaths.CompilationFiles.PdbPath, + Path.ChangeExtension(buildOutputPaths.CompilationFiles.Assembly, "xml") + }; + + filesToPublish.AddRange(buildOutputPaths.CompilationFiles.Resources()); + } + + foreach (var buildOutputFile in buildOutputFiles) + { + if (File.Exists(buildOutputFile)) + { + filesToPublish.Add(buildOutputFile); + } + else + { + Reporter.Verbose.WriteLine($"publish: build output file not found {buildOutputFile} "); + } + } + + Reporter.Verbose.WriteLine($"publish: Copying build output files:\n {string.Join("\n", filesToPublish)}"); + + PublishFiles( + filesToPublish, + outputPath); + } + private bool InvokeBuildOnProject(ProjectContext context, string buildBasePath, string configuration) { var args = new List() @@ -272,33 +316,60 @@ namespace Microsoft.DotNet.Tools.Publish } } - private static int PublishHost(ProjectContext context, string outputPath, CommonCompilerOptions compilationOptions) + private static int RenamePublishedHost(ProjectContext context, string outputPath, CommonCompilerOptions compilationOptions) { if (context.TargetFramework.IsDesktop()) { return 0; } - foreach (var binaryName in Constants.HostBinaryNames) + var publishedHostFile = ResolvePublishedHostFile(outputPath); + if (publishedHostFile == null) { - var hostBinaryPath = Path.Combine(AppContext.BaseDirectory, binaryName); - if (!File.Exists(hostBinaryPath)) - { - Reporter.Error.WriteLine($"Cannot find {binaryName} in the dotnet directory.".Red()); - return 1; - } + Reporter.Output.WriteLine($"publish: warning: host executable not available in dependencies, using host for current platform"); + // TODO should this be an error? - var outputBinaryName = binaryName.Equals(Constants.HostExecutableName) - ? compilationOptions.OutputName + Constants.ExeSuffix - : binaryName; - var outputBinaryPath = Path.Combine(outputPath, outputBinaryName); + CoreHost.CopyTo(outputPath, compilationOptions.OutputName + Constants.ExeSuffix); + return 0; + } - File.Copy(hostBinaryPath, outputBinaryPath, overwrite: true); + var publishedHostExtension = Path.GetExtension(publishedHostFile); + var renamedHostName = compilationOptions.OutputName + publishedHostExtension; + var renamedHostFile = Path.Combine(outputPath, renamedHostName); + + try + { + Reporter.Verbose.WriteLine($"publish: renaming published host {publishedHostFile} to {renamedHostFile}"); + File.Copy(publishedHostFile, renamedHostFile, true); + File.Delete(publishedHostFile); + } + catch (Exception e) + { + Reporter.Error.WriteLine($"publish: Failed to rename {publishedHostFile} to {renamedHostFile}: {e.Message}"); + return 1; } return 0; } + private static string ResolvePublishedHostFile(string outputPath) + { + var tryExtensions = new string[] { "", ".exe" }; + + foreach (var extension in tryExtensions) + { + var hostFile = Path.Combine(outputPath, Constants.PublishedHostExecutableName + extension); + if (File.Exists(hostFile)) + { + Reporter.Verbose.WriteLine($"resolved published host: {hostFile}"); + return hostFile; + } + } + + Reporter.Verbose.WriteLine($"failed to resolve published host in: {outputPath}"); + return null; + } + private static void PublishFiles(IEnumerable files, string outputPath) { foreach (var file in files) @@ -326,6 +397,7 @@ namespace Microsoft.DotNet.Tools.Publish Directory.CreateDirectory(destinationDirectory); } + Reporter.Verbose.WriteLine($"Publishing file {Path.GetFileName(file.RelativePath)} to {destinationDirectory}"); File.Copy(file.ResolvedPath, Path.Combine(destinationDirectory, file.FileName), overwrite: true); } } diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs index 0c556d181..fee3a0417 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs @@ -218,6 +218,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests projectToolsCommandResolver.GenerateDepsJsonFile(lockFile, depsJsonFile); File.ReadAllText(depsJsonFile).Should().Be("temp"); + File.Delete(depsJsonFile); } private ProjectToolsCommandResolver SetupProjectToolsCommandResolver( diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs index a72b7b18f..740fb7cf3 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs @@ -55,13 +55,20 @@ namespace Microsoft.DotNet.Tools.Test.Utilities { if (s_testsAssetsMgr == null) { - string assetsRoot = Path.Combine(RepoRoot, "TestAssets", "TestProjects"); - s_testsAssetsMgr = new TestAssetsManager(assetsRoot); + s_testsAssetsMgr = GetTestGroupTestAssetsManager("TestProjects"); } return s_testsAssetsMgr; } } + + protected static TestAssetsManager GetTestGroupTestAssetsManager(string testGroup) + { + string assetsRoot = Path.Combine(RepoRoot, "TestAssets", testGroup); + var testAssetsMgr = new TestAssetsManager(assetsRoot); + + return testAssetsMgr; + } protected TestBase() { diff --git a/test/dotnet-publish.Tests/PublishTests.cs b/test/dotnet-publish.Tests/PublishTests.cs index 99a282b7f..43c4ad9ba 100644 --- a/test/dotnet-publish.Tests/PublishTests.cs +++ b/test/dotnet-publish.Tests/PublishTests.cs @@ -92,6 +92,46 @@ namespace Microsoft.DotNet.Tools.Publish.Tests publishCommand.Execute().Should().Fail(); } + [Theory] + [InlineData("centos.7-x64", "", new string[] { "libhostfxr.so", "libcoreclr.so", "libhostpolicy.so" })] + [InlineData("rhel.7.2-x64", "", new string[] { "libhostfxr.so", "libcoreclr.so", "libhostpolicy.so" })] + [InlineData("ubuntu.14.04-x64", "", new string[] { "libhostfxr.so", "libcoreclr.so", "libhostpolicy.so" })] + [InlineData("win7-x64", ".exe", new string[] { "hostfxr.dll", "coreclr.dll", "hostpolicy.dll" })] + [InlineData("osx.10.11-x64", "", new string[] { "libhostfxr.dylib", "libcoreclr.dylib", "libhostpolicy.dylib" })] + public void CrossPublishingSucceedsAndHasExpectedArtifacts(string rid, string hostExtension, string[] expectedArtifacts) + { + var testNugetCache = "packages_cross_publish_test"; + TestInstance instance = GetTestGroupTestAssetsManager("CrossPublishTestProjects") + .CreateTestInstance("StandaloneAppCrossPublish"); + + var testProject = Path.Combine(instance.TestRoot, "project.json"); + + var restoreCommand = new RestoreCommand(); + + restoreCommand.WorkingDirectory = Path.GetDirectoryName(testProject); + restoreCommand.Environment["NUGET_PACKAGES"] = testNugetCache; + restoreCommand.Execute().Should().Pass(); + + var buildCommand = new BuildCommand(testProject, runtime: rid); + + buildCommand.WorkingDirectory = Path.GetDirectoryName(testProject); + buildCommand.Environment["NUGET_PACKAGES"] = testNugetCache; + buildCommand.Execute().Should().Pass(); + + var publishCommand = new PublishCommand(testProject, runtime: rid, noBuild: true); + publishCommand.Environment["NUGET_PACKAGES"] = testNugetCache; + publishCommand.WorkingDirectory = Path.GetDirectoryName(testProject); + publishCommand.Execute().Should().Pass(); + + var publishedDir = publishCommand.GetOutputDirectory(); + publishedDir.Should().HaveFile("StandaloneAppCrossPublish"+ hostExtension); + + foreach (var artifact in expectedArtifacts) + { + publishedDir.Should().HaveFile(artifact); + } + } + [Fact] public void LibraryPublishTest() {