diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenADependencyContextBuilder.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenADependencyContextBuilder.cs index 31b3bee2408c..a0b6f088b5df 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenADependencyContextBuilder.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenADependencyContextBuilder.cs @@ -40,6 +40,7 @@ public void ItBuildsDependencyContextsFromProjectLockFiles( object[] resolvedNuGetFiles) { LockFile lockFile = TestLockFiles.GetLockFile(mainProjectName); + LockFileLookup lockFileLookup = new LockFileLookup(lockFile); SingleProjectInfo mainProject = SingleProjectInfo.Create( "/usr/Path", @@ -52,8 +53,9 @@ public void ItBuildsDependencyContextsFromProjectLockFiles( ReferenceInfo.CreateDirectReferenceInfos( referencePaths ?? new ITaskItem[] { }, referenceSatellitePaths ?? new ITaskItem[] { }, - projectContextHasProjectReferences: false, - i => true); + lockFileLookup: lockFileLookup, + i => true, + true); ProjectContext projectContext = lockFile.CreateProjectContext( FrameworkConstants.CommonFrameworks.NetCoreApp10.GetShortFolderName(), @@ -67,7 +69,7 @@ public void ItBuildsDependencyContextsFromProjectLockFiles( resolvedNuGetFiles = Array.Empty(); } - DependencyContext dependencyContext = new DependencyContextBuilder(mainProject, includeRuntimeFileVersions: false, runtimeGraph: null, projectContext: projectContext) + DependencyContext dependencyContext = new DependencyContextBuilder(mainProject, includeRuntimeFileVersions: false, runtimeGraph: null, projectContext: projectContext, libraryLookup: lockFileLookup) .WithDirectReferences(directReferences) .WithCompilationOptions(compilationOptions) .WithResolvedNuGetFiles((ResolvedFile[]) resolvedNuGetFiles) @@ -264,7 +266,7 @@ private DependencyContext BuildDependencyContextWithReferenceAssemblies(bool use useCompilationOptions ? CreateCompilationOptions() : null; - DependencyContext dependencyContext = new DependencyContextBuilder(mainProject, includeRuntimeFileVersions: false, runtimeGraph: null, projectContext: projectContext) + DependencyContext dependencyContext = new DependencyContextBuilder(mainProject, includeRuntimeFileVersions: false, runtimeGraph: null, projectContext: projectContext, libraryLookup: new LockFileLookup(lockFile)) .WithReferenceAssemblies(ReferenceInfo.CreateReferenceInfos(referencePaths)) .WithCompilationOptions(compilationOptions) .Build(); @@ -325,7 +327,7 @@ public void ItCanGenerateTheRuntimeFallbackGraph() void CheckRuntimeFallbacks(string runtimeIdentifier, int fallbackCount) { projectContext.LockFileTarget.RuntimeIdentifier = runtimeIdentifier; - var dependencyContextBuilder = new DependencyContextBuilder(mainProject, includeRuntimeFileVersions: false, runtimeGraph, projectContext); + var dependencyContextBuilder = new DependencyContextBuilder(mainProject, includeRuntimeFileVersions: false, runtimeGraph, projectContext, libraryLookup: new LockFileLookup(lockFile)); var runtimeFallbacks = dependencyContextBuilder.Build().RuntimeGraph; runtimeFallbacks diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs index 5428145aa35f..bbbaeb68ba98 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs @@ -47,14 +47,12 @@ internal class DependencyContextBuilder private const string NetCorePlatformLibrary = "Microsoft.NETCore.App"; - public DependencyContextBuilder(SingleProjectInfo mainProjectInfo, bool includeRuntimeFileVersions, RuntimeGraph runtimeGraph, ProjectContext projectContext) + public DependencyContextBuilder(SingleProjectInfo mainProjectInfo, bool includeRuntimeFileVersions, RuntimeGraph runtimeGraph, ProjectContext projectContext, LockFileLookup libraryLookup) { _mainProjectInfo = mainProjectInfo; _includeRuntimeFileVersions = includeRuntimeFileVersions; _runtimeGraph = runtimeGraph; - var libraryLookup = new LockFileLookup(projectContext.LockFile); - _dependencyLibraries = projectContext.LockFileTarget.Libraries .Select(lockFileTargetLibrary => { diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs index 67369ee412eb..dc71c6e3ac7b 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs @@ -94,6 +94,8 @@ public class GenerateDepsFile : TaskWithAssemblyResolveHooks public bool IncludeRuntimeFileVersions { get; set; } + public bool IncludeProjectsNotInAssetsFile { get; set; } + [Required] public string RuntimeGraphPath { get; set; } @@ -125,20 +127,19 @@ private Dictionary GetFilteredPackages() private void WriteDepsFile(string depsFilePath) { - ProjectContext projectContext; - if (AssetsFilePath == null) - { - projectContext = null; - } - else + ProjectContext projectContext = null; + LockFileLookup lockFileLookup = null; + if (AssetsFilePath != null) { LockFile lockFile = new LockFileCache(this).GetLockFile(AssetsFilePath); projectContext = lockFile.CreateProjectContext( - TargetFramework, - RuntimeIdentifier, - PlatformLibraryName, - RuntimeFrameworks, - IsSelfContained); + TargetFramework, + RuntimeIdentifier, + PlatformLibraryName, + RuntimeFrameworks, + IsSelfContained); + + lockFileLookup = new LockFileLookup(lockFile); } CompilationOptions compilationOptions = CompilationOptionsConverter.ConvertFrom(CompilerOptions); @@ -156,13 +157,15 @@ private void WriteDepsFile(string depsFilePath) IEnumerable referenceAssemblyInfos = ReferenceInfo.CreateReferenceInfos(ReferenceAssemblies); - // If there is a generated asset file. The projectContext will have project reference. - // So remove it from directReferences to avoid duplication - var projectContextHasProjectReferences = projectContext != null; + // If there is a generated asset file, the projectContext will contain most of the project references. + // So remove any project reference contained within projectContext from directReferences to avoid duplication IEnumerable directReferences = - ReferenceInfo.CreateDirectReferenceInfos(ReferencePaths, + ReferenceInfo.CreateDirectReferenceInfos( + ReferencePaths, ReferenceSatellitePaths, - projectContextHasProjectReferences, isUserRuntimeAssembly); + lockFileLookup, + isUserRuntimeAssembly, + IncludeProjectsNotInAssetsFile); IEnumerable dependencyReferences = ReferenceInfo.CreateDependencyReferenceInfos(ReferenceDependencyPaths, ReferenceSatellitePaths, isUserRuntimeAssembly); @@ -210,7 +213,7 @@ bool ShouldIncludeRuntimeAsset(ITaskItem item) RuntimeGraph runtimeGraph = IsSelfContained ? new RuntimeGraphCache(this).GetRuntimeGraph(RuntimeGraphPath) : null; - builder = new DependencyContextBuilder(mainProject, IncludeRuntimeFileVersions, runtimeGraph, projectContext); + builder = new DependencyContextBuilder(mainProject, IncludeRuntimeFileVersions, runtimeGraph, projectContext, lockFileLookup); } else { diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ReferenceInfo.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ReferenceInfo.cs index dfe0866df067..11ad32000307 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/ReferenceInfo.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/ReferenceInfo.cs @@ -54,17 +54,49 @@ public static IEnumerable CreateReferenceInfos(IEnumerable CreateDirectReferenceInfos( IEnumerable referencePaths, IEnumerable referenceSatellitePaths, - bool projectContextHasProjectReferences, - Func isRuntimeAssembly) + LockFileLookup lockFileLookup, + Func isRuntimeAssembly, + bool includeProjectsNotInAssetsFile) { - - bool filterOutProjectReferenceIfInProjectContextAlready(ITaskItem referencePath) + bool lockFileContainsProject(ITaskItem referencePath) { - return (projectContextHasProjectReferences ? !IsProjectReference(referencePath) : true); + if (lockFileLookup == null) + { + return false; + } + + if (!IsProjectReference(referencePath)) + { + return false; + } + + if (!includeProjectsNotInAssetsFile) + { + return true; + } + + string projectName; + string projectFilePath = referencePath.GetMetadata(MetadataKeys.MSBuildSourceProjectFile); + if (!string.IsNullOrEmpty(projectFilePath)) + { + projectName = Path.GetFileNameWithoutExtension(projectFilePath); + } + else + { + // fall back to using the path to the output DLL + projectName = Path.GetFileNameWithoutExtension(referencePath.ItemSpec); + if (string.IsNullOrEmpty(projectName)) + { + // unexpected - let's assume this project was already included in the assets file. + return true; + } + } + + return lockFileLookup.GetProject(projectName) != null; } IEnumerable directReferencePaths = referencePaths - .Where(r => filterOutProjectReferenceIfInProjectContextAlready(r) && !IsNuGetReference(r) && isRuntimeAssembly(r)); + .Where(r => !lockFileContainsProject(r) && !IsNuGetReference(r) && isRuntimeAssembly(r)); return CreateFilteredReferenceInfos(directReferencePaths, referenceSatellitePaths); } @@ -147,7 +179,7 @@ private static string GetVersion(ITaskItem referencePath) if (!string.IsNullOrEmpty(fusionName)) { AssemblyName assemblyName = new AssemblyName(fusionName); - version = assemblyName.Version.ToString(); + version = assemblyName.Version?.ToString(); } if (string.IsNullOrEmpty(version)) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets index 31e6ec5f694d..000e7153f1b4 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets @@ -68,6 +68,7 @@ Copyright (c) .NET Foundation. All rights reserved. ResolvedRuntimeTargetsFiles="@(RuntimeTargetsCopyLocalItems)" TargetFramework="$(TargetFramework)" RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)" + IncludeProjectsNotInAssetsFile="$(IncludeProjectsNotInAssetsFileInDepsFile)" /> diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets index 36702c67c274..b9c15704968b 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets @@ -980,7 +980,8 @@ Copyright (c) .NET Foundation. All rights reserved. IsSelfContained="$(SelfContained)" IsSingleFile="$(_IsSingleFilePublish)" IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)" - RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)"/> + RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)" + IncludeProjectsNotInAssetsFile="$(IncludeProjectsNotInAssetsFileInDepsFile)"/> diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets index 31c53533eaf0..2c7c6c5bd71d 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets @@ -109,6 +109,11 @@ Copyright (c) .NET Foundation. All rights reserved. false + + + false + + _CheckForBuildWithNoBuild; @@ -197,7 +202,8 @@ Copyright (c) .NET Foundation. All rights reserved. ResolvedRuntimeTargetsFiles="@(RuntimeTargetsCopyLocalItems)" IsSelfContained="$(SelfContained)" IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)" - RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)"/> + RuntimeGraphPath="$(BundledRuntimeIdentifierGraphFile)" + IncludeProjectsNotInAssetsFile="$(IncludeProjectsNotInAssetsFileInDepsFile)"/> MainLibrary --depends on--> AuxLibrary (non-SDK) + // (TestApp transitively depends on AuxLibrary) + var testAsset = _testAssetsManager + .CreateTestProject(CreateTestProject()); + + VerifyAppBuilds(testAsset, string.Empty); + } + + [WindowsOnlyTheory] + [InlineData("")] + [InlineData("TestApp.")] + public void It_builds_deps_correctly_when_projects_do_not_get_restored(string prefix) + { + // NOTE the projects created by CreateTestProject: + // TestApp --depends on--> MainLibrary --depends on--> AuxLibrary + // (TestApp transitively depends on AuxLibrary) + var testAsset = _testAssetsManager + .CreateTestProject(CreateTestProject()) + .WithProjectChanges( + (projectName, project) => + { + string projectFileName = Path.GetFileNameWithoutExtension(projectName); + if (StringComparer.OrdinalIgnoreCase.Equals(projectFileName, "AuxLibrary") || + StringComparer.OrdinalIgnoreCase.Equals(projectFileName, "MainLibrary")) + { + var ns = project.Root.Name.Namespace; + + XElement propertyGroup = project.Root.Element(ns + "PropertyGroup"); + if (!string.IsNullOrEmpty(prefix)) + { + XElement assemblyName = propertyGroup.Element(ns + "AssemblyName"); + assemblyName.RemoveAll(); + assemblyName.Add(prefix + projectFileName); + } + + // indicate that project restore is not supported for these projects: + var target = new XElement(ns + "Target", + new XAttribute("Name", "_IsProjectRestoreSupported"), + new XAttribute("Returns", "@(_ValidProjectsForRestore)")); + + project.Root.Add(target); + } + else // if (StringComparer.OrdinalIgnoreCase.Equals(projectFileName, "TestApp")) + { + var ns = project.Root.Name.Namespace; + + XElement propertyGroup = project.Root.Element(ns + "PropertyGroup"); + + XElement includeProjectsNotInAssetsFileInDepsFile = new XElement(ns + "IncludeProjectsNotInAssetsFileInDepsFile"); + includeProjectsNotInAssetsFileInDepsFile.Add("true"); + propertyGroup.Add(includeProjectsNotInAssetsFileInDepsFile); + } + }); + + string outputDirectory = VerifyAppBuilds(testAsset, prefix); + + using (var depsJsonFileStream = File.OpenRead(Path.Combine(outputDirectory, "TestApp.deps.json"))) + { + var dependencyContext = new DependencyContextJsonReader().Read(depsJsonFileStream); + + var projectNames = dependencyContext.RuntimeLibraries.Select(library => library.Name).ToList(); + projectNames.Should().BeEquivalentTo(new[] { "TestApp", prefix + "AuxLibrary", prefix + "MainLibrary" }); + } + } + + private TestProject CreateTestProject() + { + string targetFrameworkVersion = "v4.8"; + + var auxLibraryProject = new TestProject("AuxLibrary") + { + IsSdkProject = false, + TargetFrameworkVersion = targetFrameworkVersion + }; + auxLibraryProject.SourceFiles["Helper.cs"] = @" + using System; + + namespace AuxLibrary + { + public static class Helper + { + public static void WriteMessage() + { + Console.WriteLine(""This string came from AuxLibrary!""); + } + } + } + "; + + var mainLibraryProject = new TestProject("MainLibrary") + { + IsSdkProject = false, + TargetFrameworkVersion = targetFrameworkVersion + }; + mainLibraryProject.ReferencedProjects.Add(auxLibraryProject); + mainLibraryProject.SourceFiles["Helper.cs"] = @" + using System; + + namespace MainLibrary + { + public static class Helper + { + public static void WriteMessage() + { + Console.WriteLine(""This string came from MainLibrary!""); + AuxLibrary.Helper.WriteMessage(); + } + } + } + "; + + var testAppProject = new TestProject("TestApp") + { + IsExe = true, + TargetFrameworks = ToolsetInfo.CurrentTargetFramework + }; + testAppProject.AdditionalProperties["ProduceReferenceAssembly"] = "false"; + testAppProject.ReferencedProjects.Add(mainLibraryProject); + testAppProject.SourceFiles["Program.cs"] = @" + using System; + + namespace TestApp + { + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine(""TestApp --depends on--> MainLibrary --depends on--> AuxLibrary""); + MainLibrary.Helper.WriteMessage(); + } + } + } + "; + + return testAppProject; + } + + private string VerifyAppBuilds(TestAsset testAsset, string prefix) + { + var buildCommand = new BuildCommand(testAsset, "TestApp"); + var outputDirectory = buildCommand.GetOutputDirectory(ToolsetInfo.CurrentTargetFramework); + + buildCommand + .Execute() + .Should() + .Pass(); + + outputDirectory.Should().OnlyHaveFiles(new[] { + "TestApp.dll", + "TestApp.pdb", + $"TestApp{EnvironmentInfo.ExecutableExtension}", + "TestApp.deps.json", + "TestApp.runtimeconfig.json", + prefix + "MainLibrary.dll", + prefix + "MainLibrary.pdb", + prefix + "AuxLibrary.dll", + prefix + "AuxLibrary.pdb", + }); + + new DotnetCommand(Log, Path.Combine(outputDirectory.FullName, "TestApp.dll")) + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("This string came from MainLibrary!") + .And + .HaveStdOutContaining("This string came from AuxLibrary!"); + + return outputDirectory.FullName; + } + } +} diff --git a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveProjectRefs.cs b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveProjectRefs.cs index c8086768b40f..479a30b06e9c 100644 --- a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveProjectRefs.cs +++ b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveProjectRefs.cs @@ -123,7 +123,9 @@ public void It_does_not_build_the_project_successfully() buildCommand .Execute("/p:DisableTransitiveProjectReferences=true") .Should() - .Fail(); + .Fail() + .And + .HaveStdOutContaining("CS0103"); } } }