diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryAsset.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryAsset.cs index 69c203e84..d7740a94d 100644 --- a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryAsset.cs +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryAsset.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Internal; namespace Microsoft.DotNet.ProjectModel.Compilation { @@ -19,5 +20,27 @@ namespace Microsoft.DotNet.ProjectModel.Compilation RelativePath = relativePath; ResolvedPath = resolvedPath; } + + public bool Equals(LibraryAsset other) + { + return string.Equals(Name, other.Name) + && string.Equals(RelativePath, other.RelativePath) + && string.Equals(ResolvedPath, other.ResolvedPath); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is LibraryAsset && Equals((LibraryAsset) obj); + } + + public override int GetHashCode() + { + var combiner = HashCodeCombiner.Start(); + combiner.Add(Name); + combiner.Add(RelativePath); + combiner.Add(ResolvedPath); + return combiner.CombinedHash; + } } } diff --git a/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs b/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs index ea754d876..5eee6fa5a 100644 --- a/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs +++ b/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using Microsoft.Extensions.PlatformAbstractions; namespace Microsoft.Extensions.DependencyModel { @@ -12,6 +13,10 @@ namespace Microsoft.Extensions.DependencyModel { private static Lazy _entryAssembly = new Lazy(GetEntryAssembly); + private static string _nugetPackages = Environment.GetEnvironmentVariable("NUGET_PACKAGES") ?? GetDefaultPackageDirectory(); + + private static string _packageCache = Environment.GetEnvironmentVariable("DOTNET_PACKAGES_CACHE"); + public CompilationLibrary(string libraryType, string packageName, string version, string hash, string[] assemblies, Dependency[] dependencies, bool serviceable) : base(libraryType, packageName, version, hash, dependencies, serviceable) { @@ -24,24 +29,126 @@ namespace Microsoft.Extensions.DependencyModel { var entryAssembly = _entryAssembly.Value; var entryAssemblyName = entryAssembly.GetName().Name; - var basePath = GetRefsLocation(); - foreach (var assembly in Assemblies) + string basePath; + string fullName; + + var appBase = Path.GetDirectoryName(entryAssembly.Location); + var refsDir = Path.Combine(appBase, "refs"); + var hasRefs = Directory.Exists(refsDir); + var isProject = string.Equals(LibraryType, "project", StringComparison.OrdinalIgnoreCase); + + if (hasRefs || isProject) { - if (Path.GetFileNameWithoutExtension(assembly) == entryAssemblyName) + foreach (var assembly in Assemblies) { - yield return entryAssembly.Location; - continue; + var assemblyFile = Path.GetFileName(assembly); + if (hasRefs && TryResolveAssemblyFile(refsDir, assemblyFile, out fullName)) + { + yield return fullName; + } + else if (TryResolveAssemblyFile(appBase, assemblyFile, out fullName)) + { + yield return fullName; + } + else + { + var errorMessage = $"Can not find assembly file {assemblyFile} at '{appBase}'"; + if (hasRefs) + { + errorMessage += $", '{refsDir}'"; + } + throw new InvalidOperationException(errorMessage); + } + } + yield break; + } + else if (TryResolvePackagePath(out basePath)) + { + foreach (var assembly in Assemblies) + { + if (!TryResolveAssemblyFile(basePath, assembly, out fullName)) + { + throw new InvalidOperationException($"Can not find assembly file at '{fullName}'"); + } + yield return fullName; + } + yield break; + } + throw new InvalidOperationException($"Can not find compilation library location for package '{PackageName}'"); + } + + private bool TryResolveAssemblyFile(string basePath, string assemblyPath, out string fullName) + { + fullName = Path.Combine(basePath, assemblyPath); + if (File.Exists(fullName)) + { + return true; + } + return false; + } + + private bool TryResolvePackagePath(out string packagePath) + { + packagePath = null; + + if (!string.IsNullOrEmpty(_packageCache)) + { + var hashSplitterPos = Hash.IndexOf('-'); + if (hashSplitterPos <= 0 || hashSplitterPos == Hash.Length - 1) + { + throw new InvalidOperationException($"Invalid hash entry '{Hash}' for package '{PackageName}'"); } - var fullName = Path.Combine(basePath, Path.GetFileName(assembly)); - if (!File.Exists(fullName)) + var hashAlgorithm = Hash.Substring(0, hashSplitterPos); + + var cacheHashPath = Path.Combine(_packageCache, $"{PackageName}.{Version}.nupkg.{hashAlgorithm}"); + + if (File.Exists(cacheHashPath) && + File.ReadAllText(cacheHashPath) == Hash.Substring(hashSplitterPos + 1)) { - throw new InvalidOperationException($"Can not resolve assembly {assembly} location"); + if (TryResolvePackagePath(_nugetPackages, out packagePath)) + { + return true; + } } - yield return fullName; } + if (!string.IsNullOrEmpty(_nugetPackages) && + TryResolvePackagePath(_nugetPackages, out packagePath)) + { + return true; + } + return false; } + + private bool TryResolvePackagePath(string basePath, out string packagePath) + { + packagePath = Path.Combine(basePath, PackageName, Version); + if (Directory.Exists(packagePath)) + { + return true; + } + return false; + } + + private static string GetDefaultPackageDirectory() + { + string basePath; + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) + { + basePath = Environment.GetEnvironmentVariable("USERPROFILE"); + } + else + { + basePath = Environment.GetEnvironmentVariable("HOME"); + } + if (string.IsNullOrEmpty(basePath)) + { + return null; + } + return Path.Combine(basePath, ".nuget", "packages"); + } + private static Assembly GetEntryAssembly() { var entryAssembly = (Assembly)typeof(Assembly).GetTypeInfo().GetDeclaredMethod("GetEntryAssembly").Invoke(null, null); @@ -51,10 +158,5 @@ namespace Microsoft.Extensions.DependencyModel } return entryAssembly; } - - private static string GetRefsLocation() - { - return Path.Combine(Path.GetDirectoryName(_entryAssembly.Value.Location), "refs"); - } } } \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/project.json b/src/Microsoft.Extensions.DependencyModel/project.json index 622f7ef3b..8e5cca5d4 100644 --- a/src/Microsoft.Extensions.DependencyModel/project.json +++ b/src/Microsoft.Extensions.DependencyModel/project.json @@ -10,7 +10,8 @@ "keyFile": "../../tools/Key.snk" }, "dependencies": { - "Newtonsoft.Json": "7.0.1" + "Newtonsoft.Json": "7.0.1", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16530" }, "frameworks": { "net451": { }, diff --git a/src/dotnet-compile/Program.cs b/src/dotnet-compile/Program.cs index 7c5ea08e2..cdbd9ec57 100644 --- a/src/dotnet-compile/Program.cs +++ b/src/dotnet-compile/Program.cs @@ -249,18 +249,6 @@ namespace Microsoft.DotNet.Tools.Compiler } compilerArgs.Add($"--resource:\"{depsJsonFile},{context.ProjectFile.Name}.deps.json\""); - - var refsFolder = Path.Combine(outputPath, "refs"); - if (Directory.Exists(refsFolder)) - { - Directory.Delete(refsFolder, true); - } - - Directory.CreateDirectory(refsFolder); - foreach (var reference in references) - { - File.Copy(reference, Path.Combine(refsFolder, Path.GetFileName(reference))); - } } if (!AddNonCultureResources(context.ProjectFile, compilerArgs, intermediateOutputPath)) diff --git a/src/dotnet-publish/PublishCommand.cs b/src/dotnet-publish/PublishCommand.cs index 50a6280ec..e2d2f0d67 100644 --- a/src/dotnet-publish/PublishCommand.cs +++ b/src/dotnet-publish/PublishCommand.cs @@ -133,6 +133,11 @@ namespace Microsoft.DotNet.Tools.Publish PublishFiles(export.RuntimeAssemblies, outputPath, nativeSubdirectories: false); PublishFiles(export.NativeLibraries, outputPath, nativeSubdirectories); PublishFiles(export.RuntimeAssets, outputPath); + + if (options.PreserveCompilationContext.GetValueOrDefault()) + { + PublishRefs(export, outputPath); + } } CopyContents(context, outputPath); @@ -151,6 +156,26 @@ namespace Microsoft.DotNet.Tools.Publish return true; } + private static void PublishRefs(LibraryExport export, string outputPath) + { + var refsPath = Path.Combine(outputPath, "refs"); + if (!Directory.Exists(refsPath)) + { + Directory.CreateDirectory(refsPath); + } + + // Do not copy compilation assembly if it's in runtime assemblies + var runtimeAssemblies = new HashSet(export.RuntimeAssemblies); + foreach (var compilationAssembly in export.CompilationAssemblies) + { + if (!runtimeAssemblies.Contains(compilationAssembly)) + { + var destFileName = Path.Combine(refsPath, Path.GetFileName(compilationAssembly.ResolvedPath)); + File.Copy(compilationAssembly.ResolvedPath, destFileName, overwrite: true); + } + } + } + private static int PublishHost(ProjectContext context, string outputPath) { if (context.TargetFramework.IsDesktop()) diff --git a/test/TestProjects/TestAppCompilationContext/NuGet.Config b/test/TestProjects/TestAppCompilationContext/NuGet.Config new file mode 100644 index 000000000..d35fb92ab --- /dev/null +++ b/test/TestProjects/TestAppCompilationContext/NuGet.Config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/test/TestProjects/TestAppCompilationContext/Program.cs b/test/TestProjects/TestAppCompilationContext/Program.cs new file mode 100644 index 000000000..ac3163a58 --- /dev/null +++ b/test/TestProjects/TestAppCompilationContext/Program.cs @@ -0,0 +1,17 @@ +// 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.Diagnostics; + +namespace TestApp +{ + public class Program + { + public static int Main(string[] args) + { + Console.WriteLine(TestLibrary.Helper.GetMessage()); + return 100; + } + } +} diff --git a/test/TestProjects/TestAppCompilationContext/project.json b/test/TestProjects/TestAppCompilationContext/project.json new file mode 100644 index 000000000..71beee222 --- /dev/null +++ b/test/TestProjects/TestAppCompilationContext/project.json @@ -0,0 +1,23 @@ +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + + "dependencies": { + "TestLibrary": "1.0.0-*", + + "NETStandard.Library": "1.0.0-rc2-23704", + "Microsoft.NETCore.Platforms": "1.0.1-rc2-23704" + }, + + "frameworks": { + "dnxcore50": { } + }, + + "scripts": { + "prepublish" : ["echo prepublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:Framework%? ?%publish:Runtime%?"], + "postpublish" : ["echo postpublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:Framework%? ?%publish:Runtime%?"] + } +} diff --git a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs b/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs index a883591aa..2d7444248 100644 --- a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs +++ b/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs @@ -169,6 +169,37 @@ namespace Microsoft.DotNet.Tools.Publish.Tests publishCommand.GetOutputDirectory().Should().HaveFile("Newtonsoft.Json.dll"); } + [Fact] + public void RefsPublishTest() + { + // create unique directories in the 'temp' folder + var root = Temp.CreateDirectory(); + root.CopyFile(Path.Combine(_testProjectsRoot, "global.json")); + var testAppDir = root.CreateDirectory("TestAppCompilationContext"); + var testLibDir = root.CreateDirectory("TestLibrary"); + + // copy projects to the temp dir + CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestAppCompilationContext"), testAppDir); + CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestLibrary"), testLibDir); + + RunRestore(testAppDir.Path); + RunRestore(testLibDir.Path); + + var testProject = GetProjectPath(testAppDir); + var publishCommand = new PublishCommand(testProject); + publishCommand.Execute().Should().Pass(); + + publishCommand.GetOutputDirectory().Should().HaveFile("TestAppCompilationContext.dll"); + publishCommand.GetOutputDirectory().Should().HaveFile("TestLibrary.dll"); + + var refsDirectory = new DirectoryInfo(Path.Combine(publishCommand.GetOutputDirectory().FullName, "refs")); + // Should have compilation time assemblies + refsDirectory.Should().HaveFile("System.IO.dll"); + // Libraries in which lib==ref should be deduped + refsDirectory.Should().NotHaveFile("TestLibrary.dll"); + } + + [Fact] public void CompilationFailedTest() {