Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -52,7 +53,7 @@ public void ItBuildsDependencyContextsFromProjectLockFiles(
ReferenceInfo.CreateDirectReferenceInfos(
referencePaths ?? new ITaskItem[] { },
referenceSatellitePaths ?? new ITaskItem[] { },
projectContextHasProjectReferences: false,
lockFileLookup: lockFileLookup,
i => true);

ProjectContext projectContext = lockFile.CreateProjectContext(
Expand All @@ -67,7 +68,7 @@ public void ItBuildsDependencyContextsFromProjectLockFiles(
resolvedNuGetFiles = Array.Empty<ResolvedFile>();
}

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)
Expand Down Expand Up @@ -264,7 +265,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();
Expand Down Expand Up @@ -325,7 +326,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
{
Expand Down
34 changes: 17 additions & 17 deletions src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,19 @@ private Dictionary<PackageIdentity, string> 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);
Expand All @@ -156,13 +155,14 @@ private void WriteDepsFile(string depsFilePath)
IEnumerable<ReferenceInfo> 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 have most project references.
// So remove any project reference contained within projectContext from directReferences to avoid duplication
IEnumerable<ReferenceInfo> directReferences =
ReferenceInfo.CreateDirectReferenceInfos(ReferencePaths,
ReferenceInfo.CreateDirectReferenceInfos(
ReferencePaths,
ReferenceSatellitePaths,
projectContextHasProjectReferences, isUserRuntimeAssembly);
lockFileLookup,
isUserRuntimeAssembly);

IEnumerable<ReferenceInfo> dependencyReferences =
ReferenceInfo.CreateDependencyReferenceInfos(ReferenceDependencyPaths, ReferenceSatellitePaths, isUserRuntimeAssembly);
Expand Down Expand Up @@ -210,7 +210,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
{
Expand Down
31 changes: 25 additions & 6 deletions src/Tasks/Microsoft.NET.Build.Tasks/ReferenceInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,36 @@ public static IEnumerable<ReferenceInfo> CreateReferenceInfos(IEnumerable<ITaskI
public static IEnumerable<ReferenceInfo> CreateDirectReferenceInfos(
IEnumerable<ITaskItem> referencePaths,
IEnumerable<ITaskItem> referenceSatellitePaths,
bool projectContextHasProjectReferences,
LockFileLookup lockFileLookup,
Func<ITaskItem, bool> isRuntimeAssembly)
{

bool filterOutProjectReferenceIfInProjectContextAlready(ITaskItem referencePath)
bool lockFileContainsProject(ITaskItem referencePath)
{
return (projectContextHasProjectReferences ? !IsProjectReference(referencePath) : true);
if (lockFileLookup == null)
{
return false;
}

if (!IsProjectReference(referencePath))
{
return false;
}

string projectName = referencePath.GetMetadata(MetadataKeys.MSBuildSourceProjectFile);
if (string.IsNullOrEmpty(projectName))
{
projectName = Path.GetFileNameWithoutExtension(referencePath.ItemSpec);
if (string.IsNullOrEmpty(projectName))
{
return true;
}
}

return lockFileLookup.GetProject(projectName) != null;
}

IEnumerable<ITaskItem> directReferencePaths = referencePaths
.Where(r => filterOutProjectReferenceIfInProjectContextAlready(r) && !IsNuGetReference(r) && isRuntimeAssembly(r));
.Where(r => !lockFileContainsProject(r) && !IsNuGetReference(r) && isRuntimeAssembly(r));

return CreateFilteredReferenceInfos(directReferencePaths, referenceSatellitePaths);
}
Expand Down Expand Up @@ -147,7 +166,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))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// 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.IO;
using System.Linq;
using System.Xml.Linq;

using FluentAssertions;
using Microsoft.Extensions.DependencyModel;
using Microsoft.NET.TestFramework;
using Microsoft.NET.TestFramework.Assertions;
using Microsoft.NET.TestFramework.Commands;
using Microsoft.NET.TestFramework.ProjectConstruction;

using Xunit;
using Xunit.Abstractions;

namespace Microsoft.NET.Build.Tests
{
public class GivenThatWeWantToBuildAnAppWithTransitiveNonSdkProjectRefs : SdkTest
{
public GivenThatWeWantToBuildAnAppWithTransitiveNonSdkProjectRefs(ITestOutputHelper log) : base(log)
{
}

[WindowsOnlyFact]
public void It_builds_the_project_successfully()
{
// NOTE the projects created by CreateTestProject:
// TestApp --depends on--> 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;

if (!string.IsNullOrEmpty(prefix))
{
XElement propertyGroup = project.Root.Element(XName.Get("PropertyGroup", ns.NamespaceName));
XElement assemblyName = propertyGroup.Element(XName.Get("AssemblyName", ns.NamespaceName));
assemblyName.RemoveAll();
assemblyName.Add("TestApp." + 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);
}
});

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ public void It_does_not_build_the_project_successfully()
buildCommand
.Execute("/p:DisableTransitiveProjectReferences=true")
.Should()
.Fail();
.Fail()
.And
.HaveStdOutContaining("CS0103");
}
}
}