diff --git a/TestAssets/DesktopTestProjects/DesktopAppWithNativeDep/Program.cs b/TestAssets/DesktopTestProjects/DesktopAppWithNativeDep/Program.cs new file mode 100644 index 000000000..8ef303f2d --- /dev/null +++ b/TestAssets/DesktopTestProjects/DesktopAppWithNativeDep/Program.cs @@ -0,0 +1,11 @@ +using System; + +namespace DesktopAppWithNativeDep +{ + public static class Program + { + public static void Main(string[] args) + { + } + } +} diff --git a/TestAssets/DesktopTestProjects/DesktopAppWithNativeDep/project.json b/TestAssets/DesktopTestProjects/DesktopAppWithNativeDep/project.json new file mode 100644 index 000000000..8503296a6 --- /dev/null +++ b/TestAssets/DesktopTestProjects/DesktopAppWithNativeDep/project.json @@ -0,0 +1,13 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "PackageWithFakeNativeDep": "1.0.0-*", + "Microsoft.NETCore.Platforms": "1.0.0-rc2-24008" + }, + "compilationOptions": { + "emitEntryPoint": true + }, + "frameworks": { + "net451": { } + } +} diff --git a/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktop/.noautobuild b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktop/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktop/project.json b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktop/project.json new file mode 100644 index 000000000..e3cc576a0 --- /dev/null +++ b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktop/project.json @@ -0,0 +1,18 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-20113", + "Microsoft.AspNetCore.Hosting": "1.0.0-rc2-20113", + "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-20254", + "Microsoft.NETCore.Platforms": "1.0.0-rc2-24008" + }, + "compilationOptions": { + "emitEntryPoint": true + }, + "compile": [ + "../src/*.cs" + ], + "frameworks": { + "net451": { } + } +} diff --git a/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopForce32/.noautobuild b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopForce32/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopForce32/project.json b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopForce32/project.json new file mode 100644 index 000000000..266055480 --- /dev/null +++ b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopForce32/project.json @@ -0,0 +1,19 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-20113", + "Microsoft.AspNetCore.Hosting": "1.0.0-rc2-20113", + "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-20254", + "Microsoft.NETCore.Platforms": "1.0.0-rc2-24008" + }, + "compilationOptions": { + "platform": "anycpu32bitpreferred", + "emitEntryPoint": true + }, + "compile": [ + "../src/*.cs" + ], + "frameworks": { + "net451": { } + } +} diff --git a/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopWithRuntimes/.noautobuild b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopWithRuntimes/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopWithRuntimes/project.json b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopWithRuntimes/project.json new file mode 100644 index 000000000..8ccc5081d --- /dev/null +++ b/TestAssets/DesktopTestProjects/DesktopKestrelSample/KestrelDesktopWithRuntimes/project.json @@ -0,0 +1,23 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-20113", + "Microsoft.AspNetCore.Hosting": "1.0.0-rc2-20113", + "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-20254", + "Microsoft.NETCore.Platforms": "1.0.0-rc2-24008" + }, + "compilationOptions": { + "emitEntryPoint": true + }, + "compile": [ + "../src/*.cs" + ], + "frameworks": { + "net451": { } + }, + "runtimes": { + "win7-x86": {}, + "win7-x64": {}, + "osx.10.11-x64": {} + } +} diff --git a/TestAssets/DesktopTestProjects/DesktopKestrelSample/src/Startup.cs b/TestAssets/DesktopTestProjects/DesktopKestrelSample/src/Startup.cs new file mode 100644 index 000000000..4c6b0bead --- /dev/null +++ b/TestAssets/DesktopTestProjects/DesktopKestrelSample/src/Startup.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel; +using Microsoft.AspNetCore.Server.Kestrel.Filter; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.PlatformAbstractions; + +namespace SampleApp +{ + public class Startup + { + private static string Args { get; set; } + private static CancellationTokenSource ServerCancellationTokenSource { get; set; } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IApplicationEnvironment env) + { + var ksi = app.ServerFeatures.Get(); + ksi.NoDelay = true; + + loggerFactory.AddConsole(LogLevel.Error); + + app.UseKestrelConnectionLogging(); + + app.Run(async context => + { + Console.WriteLine("{0} {1}{2}{3}", + context.Request.Method, + context.Request.PathBase, + context.Request.Path, + context.Request.QueryString); + Console.WriteLine($"Method: {context.Request.Method}"); + Console.WriteLine($"PathBase: {context.Request.PathBase}"); + Console.WriteLine($"Path: {context.Request.Path}"); + Console.WriteLine($"QueryString: {context.Request.QueryString}"); + + var connectionFeature = context.Connection; + Console.WriteLine($"Peer: {connectionFeature.RemoteIpAddress?.ToString()} {connectionFeature.RemotePort}"); + Console.WriteLine($"Sock: {connectionFeature.LocalIpAddress?.ToString()} {connectionFeature.LocalPort}"); + + var content = $"Hello world!{Environment.NewLine}Received '{Args}' from command line."; + context.Response.ContentLength = content.Length; + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync(content); + }); + } + + public static int Main(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("KestrelHelloWorld "); + return 1; + } + + var url = new Uri(args[0]); + Args = string.Join(" ", args); + + var host = new WebHostBuilder() + .UseServer("Microsoft.AspNetCore.Server.Kestrel") + .UseUrls(url.ToString()) + .UseStartup() + .Build(); + + ServerCancellationTokenSource = new CancellationTokenSource(); + + // shutdown server after 20s. + var shutdownTask = Task.Run(async () => + { + await Task.Delay(20000); + ServerCancellationTokenSource.Cancel(); + }); + + host.Run(ServerCancellationTokenSource.Token); + shutdownTask.Wait(); + + return 0; + } + } +} diff --git a/TestAssets/TestPackages/PackageWithFakeNativeDep/Lib.cs b/TestAssets/TestPackages/PackageWithFakeNativeDep/Lib.cs new file mode 100644 index 000000000..c0f79e226 --- /dev/null +++ b/TestAssets/TestPackages/PackageWithFakeNativeDep/Lib.cs @@ -0,0 +1,10 @@ +namespace PackageWithFakeNativeDep +{ + public static class Lib + { + public static string GetMessage() + { + return "Hello, World"; + } + } +} diff --git a/TestAssets/TestPackages/PackageWithFakeNativeDep/project.json b/TestAssets/TestPackages/PackageWithFakeNativeDep/project.json new file mode 100644 index 000000000..a6e1e5d21 --- /dev/null +++ b/TestAssets/TestPackages/PackageWithFakeNativeDep/project.json @@ -0,0 +1,9 @@ +{ + "version": "1.0.0", + "packInclude": { + "runtimes/": "runtimes/" + }, + "frameworks": { + "net45": {} + } +} diff --git a/TestAssets/TestPackages/PackageWithFakeNativeDep/runtimes/win7-x64/native/the-win-x64-version.txt b/TestAssets/TestPackages/PackageWithFakeNativeDep/runtimes/win7-x64/native/the-win-x64-version.txt new file mode 100644 index 000000000..ee3c10a16 --- /dev/null +++ b/TestAssets/TestPackages/PackageWithFakeNativeDep/runtimes/win7-x64/native/the-win-x64-version.txt @@ -0,0 +1 @@ +This is just a marker to ensure things are deployed right diff --git a/TestAssets/TestPackages/PackageWithFakeNativeDep/runtimes/win7-x86/native/the-win-x86-version.txt b/TestAssets/TestPackages/PackageWithFakeNativeDep/runtimes/win7-x86/native/the-win-x86-version.txt new file mode 100644 index 000000000..ee3c10a16 --- /dev/null +++ b/TestAssets/TestPackages/PackageWithFakeNativeDep/runtimes/win7-x86/native/the-win-x86-version.txt @@ -0,0 +1 @@ +This is just a marker to ensure things are deployed right diff --git a/TestAssets/TestPackages/dotnet-dependency-tool-invoker/dotnet-dependency-tool-invoker.xproj b/TestAssets/TestPackages/dotnet-dependency-tool-invoker/dotnet-dependency-tool-invoker.xproj index 7811e7531..f115e2bc5 100644 --- a/TestAssets/TestPackages/dotnet-dependency-tool-invoker/dotnet-dependency-tool-invoker.xproj +++ b/TestAssets/TestPackages/dotnet-dependency-tool-invoker/dotnet-dependency-tool-invoker.xproj @@ -9,9 +9,8 @@ c26a48bb-193f-450c-ab09-4d3324c78188 dotnet-dependency-tool-invoker ..\..\..\artifacts\obj\$(MSBuildProjectName) - ..\..\..\artifacts\bin\$(MSBuildProjectName)\ + ..\..\..\artifacts\ - 2.0 diff --git a/TestAssets/TestProjects/TestAppWithArgs/TestAppWithArgs.xproj b/TestAssets/TestProjects/TestAppWithArgs/TestAppWithArgs.xproj index fbd214cbd..9a39a775b 100644 --- a/TestAssets/TestProjects/TestAppWithArgs/TestAppWithArgs.xproj +++ b/TestAssets/TestProjects/TestAppWithArgs/TestAppWithArgs.xproj @@ -9,9 +9,8 @@ da8e0e9e-a6d6-4583-864c-8f40465e3a48 TestAppWithArgs ..\..\..\artifacts\obj\$(MSBuildProjectName) - ..\..\..\artifacts\bin\$(MSBuildProjectName)\ + ..\..\..\artifacts\ - 2.0 diff --git a/TestAssets/TestProjects/TestAppWithContents/TestAppWithContents.xproj b/TestAssets/TestProjects/TestAppWithContents/TestAppWithContents.xproj index 00103e020..fa06ff0b3 100644 --- a/TestAssets/TestProjects/TestAppWithContents/TestAppWithContents.xproj +++ b/TestAssets/TestProjects/TestAppWithContents/TestAppWithContents.xproj @@ -9,9 +9,8 @@ 0138cb8f-4aa9-4029-a21e-c07c30f425ba TestAppWithContents ..\..\..\artifacts\obj\$(MSBuildProjectName) - ..\..\..\artifacts\bin\$(MSBuildProjectName)\ + ..\..\..\artifacts\ - 2.0 diff --git a/scripts/dotnet-cli-build/TestPackageProjects.cs b/scripts/dotnet-cli-build/TestPackageProjects.cs index aa8656f55..5a486100f 100644 --- a/scripts/dotnet-cli-build/TestPackageProjects.cs +++ b/scripts/dotnet-cli-build/TestPackageProjects.cs @@ -25,6 +25,15 @@ namespace Microsoft.DotNet.Cli.Build public static readonly dynamic[] Projects = new[] { + new + { + Name = "PackageWithFakeNativeDep", + IsTool = false, + Path = "TestAssets/TestPackages/PackageWithFakeNativeDep", + IsApplicable = new Func(() => true), + VersionSuffix = s_testPackageBuildVersionSuffix, + Clean = true + }, new { Name = "dotnet-dependency-context-test", diff --git a/scripts/dotnet-cli-build/TestTargets.cs b/scripts/dotnet-cli-build/TestTargets.cs index d19ef1ea1..ba6de920a 100644 --- a/scripts/dotnet-cli-build/TestTargets.cs +++ b/scripts/dotnet-cli-build/TestTargets.cs @@ -200,7 +200,6 @@ namespace Microsoft.DotNet.Cli.Build .Execute(); } - var projectJson = Path.Combine(fullPath, "project.json"); var dotnetPackArgs = new List { projectJson, @@ -245,7 +244,6 @@ namespace Microsoft.DotNet.Cli.Build Rmdir(Path.Combine(Dirs.NuGetPackages, ".tools", packageProject.Name)); } } - return c.Success(); } diff --git a/src/Microsoft.DotNet.Compiler.Common/Executable.cs b/src/Microsoft.DotNet.Compiler.Common/Executable.cs index 5582e7d57..ac4862b1b 100644 --- a/src/Microsoft.DotNet.Compiler.Common/Executable.cs +++ b/src/Microsoft.DotNet.Compiler.Common/Executable.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.DependencyModel; using NuGet.Frameworks; using Newtonsoft.Json.Linq; using Newtonsoft.Json; +using System.Reflection.PortableExecutable; namespace Microsoft.Dotnet.Cli.Compiler.Common { @@ -147,16 +148,10 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common private void WriteFramework(JObject runtimeOptions, LibraryExporter exporter) { - var redistPackage = _context.RootProject.Dependencies - .Where(r => r.Type.Equals(LibraryDependencyType.Platform)) - .ToList(); - if (redistPackage.Count > 0) + var redistPackage = _context.PlatformLibrary; + if (redistPackage != null) { - if (redistPackage.Count > 1) - { - throw new InvalidOperationException("Multiple packages with type: \"platform\" were specified!"); - } - var packageName = redistPackage.Single().Name; + var packageName = redistPackage.Identity.Name; var redistExport = exporter.GetAllExports() .FirstOrDefault(e => e.Library.Identity.Name.Equals(packageName)); diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs index 8fc68ba34..f9e25ce98 100644 --- a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs @@ -20,6 +20,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation { private readonly string _configuration; private readonly string _runtime; + private readonly string[] _runtimeFallbacks; private readonly ProjectDescription _rootProject; private readonly string _buildBasePath; private readonly string _solutionRootPath; @@ -28,6 +29,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation LibraryManager manager, string configuration, string runtime, + string[] runtimeFallbacks, string buildBasePath, string solutionRootPath) { @@ -39,6 +41,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation LibraryManager = manager; _configuration = configuration; _runtime = runtime; + _runtimeFallbacks = runtimeFallbacks; _buildBasePath = buildBasePath; _solutionRootPath = solutionRootPath; _rootProject = rootProject; @@ -96,7 +99,6 @@ namespace Microsoft.DotNet.ProjectModel.Compilation var analyzerReferences = new List(); var libraryExport = GetExport(library); - // We need to filter out source references from non-root libraries, // so we rebuild the library export foreach (var reference in libraryExport.CompilationAssemblies) @@ -114,12 +116,25 @@ namespace Microsoft.DotNet.ProjectModel.Compilation analyzerReferences.AddRange(libraryExport.AnalyzerReferences); } - yield return LibraryExportBuilder.Create(library) + var builder = LibraryExportBuilder.Create(library); + if (_runtime != null && _runtimeFallbacks != null) + { + // For portable apps that are built with runtime trimming we replace RuntimeAssemblyGroups and NativeLibraryGroups + // with single default group that contains asset specific to runtime we are trimming for + // based on runtime fallback list + builder.WithRuntimeAssemblyGroups(TrimAssetGroups(libraryExport.RuntimeAssemblyGroups, _runtimeFallbacks)); + builder.WithNativeLibraryGroups(TrimAssetGroups(libraryExport.NativeLibraryGroups, _runtimeFallbacks)); + } + else + { + builder.WithRuntimeAssemblyGroups(libraryExport.RuntimeAssemblyGroups); + builder.WithNativeLibraryGroups(libraryExport.NativeLibraryGroups); + } + + yield return builder .WithCompilationAssemblies(compilationAssemblies) .WithSourceReferences(sourceReferences) - .WithRuntimeAssemblyGroups(libraryExport.RuntimeAssemblyGroups) .WithRuntimeAssets(libraryExport.RuntimeAssets) - .WithNativeLibraryGroups(libraryExport.NativeLibraryGroups) .WithEmbedddedResources(libraryExport.EmbeddedResources) .WithAnalyzerReference(analyzerReferences) .WithResourceAssemblies(libraryExport.ResourceAssemblies) @@ -127,6 +142,27 @@ namespace Microsoft.DotNet.ProjectModel.Compilation } } + private IEnumerable TrimAssetGroups(IEnumerable runtimeAssemblyGroups, + string[] runtimeFallbacks) + { + LibraryAssetGroup runtimeAssets; + foreach (var rid in runtimeFallbacks) + { + runtimeAssets = runtimeAssemblyGroups.GetRuntimeGroup(rid); + if (runtimeAssets != null) + { + yield return new LibraryAssetGroup(runtimeAssets.Assets); + yield break; + } + } + + runtimeAssets = runtimeAssemblyGroups.GetDefaultGroup(); + if (runtimeAssets != null) + { + yield return runtimeAssets; + } + } + /// /// Create a LibraryExport from LibraryDescription. /// diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs b/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs index 4ac31b2ec..0f07a2421 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs @@ -14,12 +14,18 @@ namespace Microsoft.DotNet.ProjectModel { public class ProjectContext { + private string[] _runtimeFallbacks; + public GlobalSettings GlobalSettings { get; } public ProjectDescription RootProject { get; } public NuGetFramework TargetFramework { get; } + public LibraryDescription PlatformLibrary { get; } + + public bool IsPortable { get; } + public string RuntimeIdentifier { get; } public Project ProjectFile => RootProject?.Project; @@ -37,7 +43,9 @@ namespace Microsoft.DotNet.ProjectModel internal ProjectContext( GlobalSettings globalSettings, ProjectDescription rootProject, + LibraryDescription platformLibrary, NuGetFramework targetFramework, + bool isPortable, string runtimeIdentifier, string packagesDirectory, LibraryManager libraryManager, @@ -45,16 +53,29 @@ namespace Microsoft.DotNet.ProjectModel { GlobalSettings = globalSettings; RootProject = rootProject; + PlatformLibrary = platformLibrary; TargetFramework = targetFramework; RuntimeIdentifier = runtimeIdentifier; PackagesDirectory = packagesDirectory; LibraryManager = libraryManager; LockFile = lockfile; + IsPortable = isPortable; } public LibraryExporter CreateExporter(string configuration, string buildBasePath = null) { - return new LibraryExporter(RootProject, LibraryManager, configuration, RuntimeIdentifier, buildBasePath, RootDirectory); + if (IsPortable && RuntimeIdentifier != null && _runtimeFallbacks == null) + { + var graph = RuntimeGraphCollector.Collect(LibraryManager.GetLibraries()); + _runtimeFallbacks = graph.ExpandRuntime(RuntimeIdentifier).ToArray(); + } + return new LibraryExporter(RootProject, + LibraryManager, + configuration, + RuntimeIdentifier, + _runtimeFallbacks, + buildBasePath, + RootDirectory); } /// @@ -146,15 +167,12 @@ namespace Microsoft.DotNet.ProjectModel return this; } - var standalone = !RootProject.Dependencies - .Any(d => d.Type.Equals(LibraryDependencyType.Platform)); - var context = CreateBuilder(ProjectFile.ProjectFilePath, TargetFramework) - .WithRuntimeIdentifiers(standalone ? runtimeIdentifiers : Enumerable.Empty()) + .WithRuntimeIdentifiers(runtimeIdentifiers) .WithLockFile(LockFile) .Build(); - if (standalone && context.RuntimeIdentifier == null) + if (!context.IsPortable && context.RuntimeIdentifier == null) { // We are standalone, but don't support this runtime var rids = string.Join(", ", runtimeIdentifiers); diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs b/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs index 2399d1d92..562a7d9ea 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs @@ -9,6 +9,7 @@ using System.Text; using Microsoft.DotNet.ProjectModel.Graph; using Microsoft.DotNet.ProjectModel.Resolution; using Microsoft.Extensions.Internal; +using Microsoft.Extensions.PlatformAbstractions; using NuGet.Frameworks; namespace Microsoft.DotNet.ProjectModel @@ -32,7 +33,7 @@ namespace Microsoft.DotNet.ProjectModel private string PackagesDirectory { get; set; } private string ReferenceAssembliesPath { get; set; } - + private bool IsDesignTime { get; set; } private Func ProjectResolver { get; set; } @@ -118,7 +119,7 @@ namespace Microsoft.DotNet.ProjectModel Settings = settings; return this; } - + public ProjectContextBuilder AsDesignTime() { IsDesignTime = true; @@ -197,18 +198,53 @@ namespace Microsoft.DotNet.ProjectModel libraries.Add(new LibraryKey(mainProject.Identity.Name), mainProject); } + LibraryRange? platformDependency = null; + if (mainProject != null) + { + platformDependency = mainProject.Dependencies + .Where(d => d.Type.Equals(LibraryDependencyType.Platform)) + .Cast() + .FirstOrDefault(); + } + bool isPortable = platformDependency != null || TargetFramework.IsDesktop(); + LockFileTarget target = null; + LibraryDescription platformLibrary = null; + if (lockFileLookup != null) { - target = SelectTarget(LockFile); + target = SelectTarget(LockFile, isPortable); if (target != null) { var nugetPackageResolver = new PackageDependencyProvider(PackagesDirectory, frameworkReferenceResolver); var msbuildProjectResolver = new MSBuildDependencyProvider(Project, ProjectResolver); ScanLibraries(target, lockFileLookup, libraries, msbuildProjectResolver, nugetPackageResolver, projectResolver); + + if (platformDependency != null) + { + platformLibrary = libraries[new LibraryKey(platformDependency.Value.Name)]; + } } } + string runtime; + if (TargetFramework.IsDesktop()) + { + var legacyRuntime = PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier(); + if (RuntimeIdentifiers.Contains(legacyRuntime)) + { + runtime = legacyRuntime; + } + else + { + runtime = RuntimeIdentifiers.FirstOrDefault(); + } + } + else + { + runtime = target?.RuntimeIdentifier; + } + var referenceAssemblyDependencyResolver = new ReferenceAssemblyDependencyResolver(frameworkReferenceResolver); bool requiresFrameworkAssemblies; @@ -272,8 +308,10 @@ namespace Microsoft.DotNet.ProjectModel return new ProjectContext( GlobalSettings, mainProject, + platformLibrary, TargetFramework, - target?.RuntimeIdentifier, + isPortable, + runtime, PackagesDirectory, libraryManager, LockFile); @@ -454,15 +492,18 @@ namespace Microsoft.DotNet.ProjectModel } } - private LockFileTarget SelectTarget(LockFile lockFile) + private LockFileTarget SelectTarget(LockFile lockFile, bool isPortable) { - foreach (var runtimeIdentifier in RuntimeIdentifiers) + if (!isPortable) { - foreach (var scanTarget in lockFile.Targets) + foreach (var runtimeIdentifier in RuntimeIdentifiers) { - if (Equals(scanTarget.TargetFramework, TargetFramework) && string.Equals(scanTarget.RuntimeIdentifier, runtimeIdentifier, StringComparison.Ordinal)) + foreach (var scanTarget in lockFile.Targets) { - return scanTarget; + if (Equals(scanTarget.TargetFramework, TargetFramework) && string.Equals(scanTarget.RuntimeIdentifier, runtimeIdentifier, StringComparison.Ordinal)) + { + return scanTarget; + } } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/RuntimeEnvironmentRidExtensions.cs b/src/Microsoft.DotNet.ProjectModel/RuntimeEnvironmentRidExtensions.cs similarity index 100% rename from src/Microsoft.DotNet.Cli.Utils/RuntimeEnvironmentRidExtensions.cs rename to src/Microsoft.DotNet.ProjectModel/RuntimeEnvironmentRidExtensions.cs diff --git a/src/Microsoft.DotNet.ProjectModel/RuntimeGraphCollector.cs b/src/Microsoft.DotNet.ProjectModel/RuntimeGraphCollector.cs new file mode 100644 index 000000000..c2625c982 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/RuntimeGraphCollector.cs @@ -0,0 +1,34 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.DotNet.ProjectModel.Graph; +using NuGet.RuntimeModel; + +namespace Microsoft.DotNet.ProjectModel +{ + class RuntimeGraphCollector + { + private const string RuntimeJsonFileName = "runtime.json"; + + public static RuntimeGraph Collect(IEnumerable libraries) + { + var graph = RuntimeGraph.Empty; + foreach (var library in libraries) + { + if (library.Identity.Type == LibraryType.Package) + { + var runtimeJson = ((PackageDescription)library).PackageLibrary.Files.FirstOrDefault(f => f == RuntimeJsonFileName); + if (runtimeJson != null) + { + var runtimeJsonFullName = Path.Combine(library.Path, runtimeJson); + graph = RuntimeGraph.Merge(graph, JsonRuntimeFormat.ReadRuntimeGraph(runtimeJsonFullName)); + } + } + } + return graph; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/project.json b/src/Microsoft.DotNet.ProjectModel/project.json index 24e563c6b..321004869 100644 --- a/src/Microsoft.DotNet.ProjectModel/project.json +++ b/src/Microsoft.DotNet.ProjectModel/project.json @@ -17,6 +17,7 @@ }, "Newtonsoft.Json": "7.0.1", "NuGet.Packaging": "3.5.0-beta-1130", + "NuGet.RuntimeModel": "3.5.0-beta-1130", "System.Reflection.Metadata": "1.3.0-rc2-24008" }, "frameworks": { diff --git a/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestInstance.cs b/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestInstance.cs index f6c66d7a6..8912c13ee 100644 --- a/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestInstance.cs +++ b/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestInstance.cs @@ -44,9 +44,9 @@ namespace Microsoft.DotNet.TestFramework .Where(dir => { dir = dir.ToLower(); - return !dir.EndsWith($"{Path.DirectorySeparatorChar}bin") + return !dir.EndsWith($"{Path.DirectorySeparatorChar}bin") && !dir.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") - && !dir.EndsWith($"{Path.DirectorySeparatorChar}obj") + && !dir.EndsWith($"{Path.DirectorySeparatorChar}obj") && !dir.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"); }); diff --git a/src/dotnet/commands/dotnet-build/CompileContext.cs b/src/dotnet/commands/dotnet-build/CompileContext.cs index d231dd33e..2a9ff80bc 100644 --- a/src/dotnet/commands/dotnet-build/CompileContext.cs +++ b/src/dotnet/commands/dotnet-build/CompileContext.cs @@ -108,10 +108,10 @@ namespace Microsoft.DotNet.Tools.Build { if (CLIChangedSinceLastCompilation(project)) { - Reporter.Output.WriteLine($"Project {project.GetDisplayName()} will be compiled because the CLI changed"); + Reporter.Output.WriteLine($"Project {project.GetDisplayName()} will be compiled because the version or bitness of the CLI changed since the last build"); return true; } - + var compilerIO = GetCompileIO(project, dependencies); // rebuild if empty inputs / outputs @@ -189,7 +189,7 @@ namespace Microsoft.DotNet.Tools.Build return true; } - + private bool CLIChangedSinceLastCompilation(ProjectContext project) { var currentVersionFile = DotnetFiles.VersionFile; @@ -207,7 +207,9 @@ namespace Microsoft.DotNet.Tools.Build return false; } - var versionsAreEqual = string.Equals(File.ReadAllText(currentVersionFile), File.ReadAllText(versionFileFromLastCompile), StringComparison.OrdinalIgnoreCase); + var currentContent = ComputeCurrentVersionFileData(); + + var versionsAreEqual = string.Equals(currentContent, File.ReadAllText(versionFileFromLastCompile), StringComparison.OrdinalIgnoreCase); return !versionsAreEqual; } @@ -216,7 +218,7 @@ namespace Microsoft.DotNet.Tools.Build { if (File.Exists(DotnetFiles.VersionFile)) { - var projectVersionFile = project.GetSDKVersionFile(_args.ConfigValue, _args.BuildBasePathValue,_args.OutputValue); + var projectVersionFile = project.GetSDKVersionFile(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue); var parentDirectory = Path.GetDirectoryName(projectVersionFile); if (!Directory.Exists(parentDirectory)) @@ -224,7 +226,9 @@ namespace Microsoft.DotNet.Tools.Build Directory.CreateDirectory(parentDirectory); } - File.Copy(DotnetFiles.VersionFile, projectVersionFile, true); + string content = ComputeCurrentVersionFileData(); + + File.WriteAllText(projectVersionFile, content); } else { @@ -232,6 +236,14 @@ namespace Microsoft.DotNet.Tools.Build } } + private static string ComputeCurrentVersionFileData() + { + var content = File.ReadAllText(DotnetFiles.VersionFile); + content += Environment.NewLine; + content += PlatformServices.Default.Runtime.GetRuntimeIdentifier(); + return content; + } + private void PrintSummary(bool success) { // todo: Ideally it's the builder's responsibility for adding the time elapsed. That way we avoid cross cutting display concerns between compile and build for printing time elapsed diff --git a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs index 3177d61df..a21b1be51 100644 --- a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs +++ b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs @@ -5,11 +5,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Compilation; using Microsoft.Extensions.DependencyModel; +using NuGet.Frameworks; namespace Microsoft.DotNet.Tools.Compiler { @@ -77,6 +79,15 @@ namespace Microsoft.DotNet.Tools.Compiler }; var compilationOptions = context.ResolveCompilationOptions(args.ConfigValue); + + // Set default platform if it isn't already set and we're on desktop + if(compilationOptions.EmitEntryPoint == true && string.IsNullOrEmpty(compilationOptions.Platform) && context.TargetFramework.IsDesktop()) + { + // See https://github.com/dotnet/cli/issues/2428 for more details. + compilationOptions.Platform = RuntimeInformation.ProcessArchitecture == Architecture.X64 ? + "x64" : "anycpu32bitpreferred"; + } + var languageId = CompilerUtil.ResolveLanguageId(context); var references = new List(); diff --git a/src/dotnet/commands/dotnet-publish/PublishCommand.cs b/src/dotnet/commands/dotnet-publish/PublishCommand.cs index 81b3a32f6..90ae4e8fb 100644 --- a/src/dotnet/commands/dotnet-publish/PublishCommand.cs +++ b/src/dotnet/commands/dotnet-publish/PublishCommand.cs @@ -128,21 +128,19 @@ namespace Microsoft.DotNet.Tools.Publish // Use a library exporter to collect publish assets var exporter = context.CreateExporter(configuration, buildBasePath); - var isPortable = string.IsNullOrEmpty(context.RuntimeIdentifier); - // Collect all exports and organize them var packageExports = exporter.GetAllExports() .Where(e => e.Library.Identity.Type.Equals(LibraryType.Package)) .ToDictionary(e => e.Library.Identity.Name); - var collectExclusionList = isPortable ? GetExclusionList(context, packageExports) : new HashSet(); + var collectExclusionList = context.IsPortable ? GetExclusionList(context, packageExports) : new HashSet(); var exports = exporter.GetAllExports(); foreach (var export in exports.Where(e => !collectExclusionList.Contains(e.Library.Identity.Name))) { 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); + PublishAssetGroups(export.RuntimeAssemblyGroups, outputPath, nativeSubdirectories: false, includeRuntimeGroups: context.IsPortable); + PublishAssetGroups(export.NativeLibraryGroups, outputPath, nativeSubdirectories, includeRuntimeGroups: context.IsPortable); } if (options.PreserveCompilationContext.GetValueOrDefault()) @@ -152,7 +150,7 @@ namespace Microsoft.DotNet.Tools.Publish PublishRefs(export, outputPath, !collectExclusionList.Contains(export.Library.Identity.Name)); } } - + var buildOutputPaths = context.GetOutputPaths(configuration, buildBasePath, null); PublishBuildOutputFiles(buildOutputPaths, context, outputPath, Configuration); diff --git a/test/Installer/Microsoft.DotNet.Cli.Msi.Tests/Microsoft.DotNet.Cli.Msi.Tests.xproj b/test/Installer/Microsoft.DotNet.Cli.Msi.Tests/Microsoft.DotNet.Cli.Msi.Tests.xproj index 64ee27ba4..00b0d9710 100644 --- a/test/Installer/Microsoft.DotNet.Cli.Msi.Tests/Microsoft.DotNet.Cli.Msi.Tests.xproj +++ b/test/Installer/Microsoft.DotNet.Cli.Msi.Tests/Microsoft.DotNet.Cli.Msi.Tests.xproj @@ -9,7 +9,7 @@ 0B31C336-149D-471A-B7B1-27B0F1E80F83 Microsoft.DotNet.Cli.Msi.Tests ..\..\..\artifacts\obj\$(MSBuildProjectName) - ..\..\..\artifacts\bin\$(MSBuildProjectName)\ + ..\..\..\artifacts\ 2.0 diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs b/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs index 2c98adae6..0a555e7b0 100644 --- a/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs +++ b/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs @@ -328,7 +328,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests } var libraryManager = new LibraryManager(new[] { description }, new DiagnosticMessage[] { }, ""); - var allExports = new LibraryExporter(rootProjectDescription, libraryManager, "config", "runtime", "basepath", "solutionroot").GetAllExports(); + var allExports = new LibraryExporter(rootProjectDescription, libraryManager, "config", "runtime", null, "basepath", "solutionroot").GetAllExports(); var export = allExports.Single(); return export; } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs index e04e88f7d..4a0952940 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.PlatformAbstractions; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace Microsoft.DotNet.Tools.Test.Utilities { @@ -42,6 +43,12 @@ namespace Microsoft.DotNet.Tools.Test.Utilities _buidBasePathDirectory = buildBasePath; } + public override Task ExecuteAsync(string args = "") + { + args = $"publish {BuildArgs()} {args}"; + return base.ExecuteAsync(args); + } + public override CommandResult Execute(string args = "") { args = $"publish {BuildArgs()} {args}"; diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/WindowsOnlyFactAttribute.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/WindowsOnlyFactAttribute.cs index fbc766729..07341b2a7 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/WindowsOnlyFactAttribute.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/WindowsOnlyFactAttribute.cs @@ -16,4 +16,15 @@ namespace Microsoft.DotNet.Tools.Test.Utilities } } } + + public class WindowsOnlyTheoryAttribute : TheoryAttribute + { + public WindowsOnlyTheoryAttribute() + { + if (PlatformServices.Default.Runtime.OperatingSystemPlatform != Platform.Windows) + { + this.Skip = "This test requires windows to run"; + } + } + } } \ No newline at end of file diff --git a/test/dotnet-publish.Tests/PublishDesktopTests.cs b/test/dotnet-publish.Tests/PublishDesktopTests.cs new file mode 100644 index 000000000..752bc27dc --- /dev/null +++ b/test/dotnet-publish.Tests/PublishDesktopTests.cs @@ -0,0 +1,129 @@ +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.TestFramework; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using Xunit; + +namespace Microsoft.DotNet.Tools.Publish.Tests +{ + public class PublishDesktopTests : TestBase + { + [WindowsOnlyTheory] + [InlineData(null, null)] + [InlineData("win7-x64", "the-win-x64-version.txt")] + [InlineData("win7-x86", "the-win-x86-version.txt")] + public async Task DesktopApp_WithDependencyOnNativePackage_ProducesExpectedOutput(string runtime, string expectedOutputName) + { + if(string.IsNullOrEmpty(expectedOutputName)) + { + expectedOutputName = $"the-win-{RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant()}-version.txt"; + } + + var testInstance = TestAssetsManager.CreateTestInstance(Path.Combine("..", "DesktopTestProjects", "DesktopAppWithNativeDep")) + .WithLockFiles(); + + var publishCommand = new PublishCommand(testInstance.TestRoot, runtime: runtime); + var result = await publishCommand.ExecuteAsync(); + + result.Should().Pass(); + + // Test the output + var outputDir = publishCommand.GetOutputDirectory(portable: false); + outputDir.Should().HaveFile(expectedOutputName); + outputDir.Should().HaveFile(publishCommand.GetOutputExecutable()); + } + + [WindowsOnlyTheory] + [InlineData("KestrelDesktopWithRuntimes", "http://localhost:20201", null, "libuv.dll", false)] + [InlineData("KestrelDesktopWithRuntimes", "http://localhost:20202", "win7-x64", "libuv.dll", false)] + [InlineData("KestrelDesktopWithRuntimes", "http://localhost:20203", "win7-x86", "libuv.dll", false)] + [InlineData("KestrelDesktopForce32", "http://localhost:20204", "win7-x86", "libuv.dll", true)] + [InlineData("KestrelDesktop", "http://localhost:20205", null, "libuv.dll", false)] + [InlineData("KestrelDesktop", "http://localhost:20206", "win7-x64", "libuv.dll", false)] + [InlineData("KestrelDesktop", "http://localhost:20207", "win7-x86", "libuv.dll", false)] + public async Task DesktopApp_WithKestrel_WorksWhenPublished(string project, string url, string runtime, string libuvName, bool forceRunnable) + { + var runnable = forceRunnable || string.IsNullOrEmpty(runtime) || (PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier().Contains(runtime)); + + var testInstance = GetTestInstance() + .WithLockFiles(); + + var publishCommand = new PublishCommand(Path.Combine(testInstance.TestRoot, project), runtime: runtime); + var result = await publishCommand.ExecuteAsync(); + + result.Should().Pass(); + + // Test the output + var outputDir = publishCommand.GetOutputDirectory(portable: false); + outputDir.Should().HaveFile(libuvName); + outputDir.Should().HaveFile(publishCommand.GetOutputExecutable()); + + Task exec = null; + if (runnable) + { + var outputExePath = Path.Combine(outputDir.FullName, publishCommand.GetOutputExecutable()); + + var command = new TestCommand(outputExePath); + try + { + exec = command.ExecuteAsync(url); + NetworkHelper.IsServerUp(url).Should().BeTrue($"Unable to connect to kestrel server - {project} @ {url}"); + NetworkHelper.TestGetRequest(url, url); + } + finally + { + command.KillTree(); + } + if (exec != null) + { + await exec; + } + } + } + + [WindowsOnlyTheory] + [InlineData("KestrelDesktop", "http://localhost:20301")] + [InlineData("KestrelDesktopWithRuntimes", "http://localhost:20302")] + public async Task DesktopApp_WithKestrel_WorksWhenRun(string project, string url) + { + // Disabled due to https://github.com/dotnet/cli/issues/2428 + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + return; + } + + var testInstance = GetTestInstance() + .WithLockFiles() + .WithBuildArtifacts(); + + Task exec = null; + var command = new RunCommand(Path.Combine(testInstance.TestRoot, project)); + try + { + exec = command.ExecuteAsync(url); + NetworkHelper.IsServerUp(url).Should().BeTrue($"Unable to connect to kestrel server - {project} @ {url}"); + NetworkHelper.TestGetRequest(url, url); + } + finally + { + command.KillTree(); + } + if (exec != null) + { + await exec; + } + } + + private static TestInstance GetTestInstance([CallerMemberName] string callingMethod = "") + { + return TestAssetsManager.CreateTestInstance(Path.Combine("..", "DesktopTestProjects", "DesktopKestrelSample"), callingMethod); + } + } +} diff --git a/test/dotnet-publish.Tests/project.json b/test/dotnet-publish.Tests/project.json index 6a50d1868..87a6459ec 100644 --- a/test/dotnet-publish.Tests/project.json +++ b/test/dotnet-publish.Tests/project.json @@ -10,21 +10,19 @@ "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, - "Microsoft.DotNet.Cli.Utils": { - "target": "project" - }, "xunit": "2.1.0", "xunit.netcore.extensions": "1.0.0-prerelease-00206", - "dotnet-test-xunit": "1.0.0-dev-140469-38" + "dotnet-test-xunit": "1.0.0-dev-140469-38", + "System.Runtime.InteropServices.RuntimeInformation": "4.0.0-rc2-24008" }, "frameworks": { - "netcoreapp1.0": { - "imports": [ - "netstandardapp1.5", - "dnxcore50", - "portable-net45+win8" - ] - } + "netcoreapp1.0": { + "imports": [ + "netstandardapp1.5", + "dnxcore50", + "portable-net45+win8" + ] + } }, "testRunner": "xunit" }