Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise Transitive Project References to MSBuild elements #478

Merged
merged 11 commits into from
Dec 21, 2016

Conversation

natidea
Copy link
Contributor

@natidea natidea commented Dec 12, 2016

Raise closure of Transitive Project References to msbuild as ProjectReference elements. This closure is generated by NuGet in the assets file.

Fixes #200
See dotnet/project-system#199 (comment) for details on P2P plan.

<Target Name="IncludeTransitiveProjectReferences"
Condition="'$(ResolvePackageDependenciesForBuild)' == 'true' and '$(EnableResolvePackageDependencies)' == 'true' and Exists('$(ProjectAssetsFile)')"
DependsOnTargets="_ComputeTransitiveProjectReferences"
BeforeTargets="AssignProjectConfiguration">
Copy link
Contributor Author

@natidea natidea Dec 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BeforeTargets="AssignProjectConfiguration" ensures ProjectReference elements are created before they are processed into ResolvedProjectReferences. The alternative is to add this to the list ResolvePackageDependenciesForBuildDependsOn, but that would result in IncludeTransitiveProjectReferences being called too late.

Choosing this approach means we have to copy the condition from ResolvePackageDependenciesForBuild to ensure we are not calling RunResolvePackageDependencies at inappropriate times. (E.g. Without this condition, the sdk build fails on building xUnit.net.csproj

Another alternative is to bypass ResolveProjectReferences and generate the _ResolvedProjectReferencePaths element directly, as is done in Microsoft.NuGet.targets. This currently does not work because of a bug calculating resolved paths (#479).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add IncludeTransitiveProjectReferences to ResolvePackageDependenciesForBuildDependsOn, and then put BeforeTargets="AssignProjectConfiguration" on the ResolvePackageDependenciesForBuild target? It seems like that would prevent us from having to repeat the same condition twice.

In any case, I would like to see comments in the targets file itself explaining how the ordering works. IIUC, the key is that the AssignProjectConfiguration is the entry point that reads ProjectReference items, so this target needs to run before that, but only if there is an assets file.

Separately, what is the difference between the ResolvePackageDependenciesForBuild and EnableResolvePackageDependencies properties? They seem to only be used together.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears the two flag properties are redundant, and may have been added separately. I've removed EnableResolvePackageDependencies and also simplified the ordering as you suggested to avoid repeating the condition.

@natidea
Copy link
Contributor Author

natidea commented Dec 14, 2016

/cc @dotnet/project-system
@emgarten for NuGet fyi.

DependsOnTargets="RunResolvePackageDependencies"
Returns="_TransitiveProjectReferences">
<ItemGroup>
<ProjectDefs Include="@(PackageDefinitions->WithMetadataValue('Type', 'project'))" />
Copy link
Contributor

@nguerrera nguerrera Dec 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's be consistent on not abbreviating things. We have PackageDefinitions and ProjectDefs on the same line here. It should be ProjectDefinitions.

Item names are also supposed to be singular, but that's one Microsoft has gotten wrong in many places so I feel less strongly about enforcing it. #Resolved

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>42.43.44.45-alpha</Version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason these projects need to have a version? If not, let's remove them from the project file.

}

[Fact]
public void It_builds_the_project_successfully_twice()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as my comment on #489: Is there a reason we need to test building twice for this scenario?

.HaveStdOutContaining("This string came from AuxLibrary!");

var appInfo = FileVersionInfo.GetVersionInfo(Path.Combine(outputDirectory.FullName, "TestApp.dll"));
appInfo.CompanyName.Should().Be("Test Authors");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My feeling is that we don't need to be testing the assembly versions and attributes here. Tests should generally be specific about what they are testing as opposed to testing a bunch of behaviors that don't seem to be related in a given scenario.

<Target Name="IncludeTransitiveProjectReferences"
Condition="'$(ResolvePackageDependenciesForBuild)' == 'true' and '$(EnableResolvePackageDependencies)' == 'true' and Exists('$(ProjectAssetsFile)')"
DependsOnTargets="_ComputeTransitiveProjectReferences"
BeforeTargets="AssignProjectConfiguration">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add IncludeTransitiveProjectReferences to ResolvePackageDependenciesForBuildDependsOn, and then put BeforeTargets="AssignProjectConfiguration" on the ResolvePackageDependenciesForBuild target? It seems like that would prevent us from having to repeat the same condition twice.

In any case, I would like to see comments in the targets file itself explaining how the ordering works. IIUC, the key is that the AssignProjectConfiguration is the entry point that reads ProjectReference items, so this target needs to run before that, but only if there is an assets file.

Separately, what is the difference between the ResolvePackageDependenciesForBuild and EnableResolvePackageDependencies properties? They seem to only be used together.

BeforeTargets="AssignProjectConfiguration">
<ItemGroup>
<!-- Add the references we computed -->
<ProjectReference Include="@(_TransitiveProjectReferences)" />
Copy link
Member

@dsplaisted dsplaisted Dec 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for there to be a transitive reference from the assets file to a project that is also directly referenced? It would be good to have a test case to cover this, where B references C, and A references both B and C. #Resolved

…encies and remove redundant condition EnableResolvePackageDependencies
Use package dependencies (under "target") instead of package definitions (under "library") to compute transitive project references. Package dependencies could contain different information for different TFMs. This also simplifies determining which project references are transitive since we can do that all using information in the package dependency tree.
@natidea
Copy link
Contributor Author

natidea commented Dec 21, 2016

After chatting with @emgarten , I've made some changes. Previously we were computing transitive project references from ProjectDefinitions which comes from the asset file's "libraries" section. This is a deduped list of packages/projects. This is not correct because project transitivity could be conditional on the TFM. In this case, the project reference may be handled transitively in one TFM, but not the other.

Commit f3b057b changes to using PackageDependencies so computing transitive project references matches the information for the active target. This includes excluding projects marked with a placeholder compile time assembly _._

Additionally I added logic to exclude directly referenced projects to address the concern @dsplaisted raised. It turns out these duplicated projects were deduped by ResolveAssemblyReference because their output assemblies were identical, so the compiler only gets one copy. However the Build Hierarchy printed at the end of a diagnostic build contains more entries without this fix, so perhaps more redundant NoOp builds were happening.

I've tested this using the following project layout:

    A -> B -> C -> D
              C -> E
    A -> D

When building A:

  • C and E are treated as transitive references
  • B and D are direct references and hence are already included; they are not appended by the new target

I simulated making E a non-transitive reference in C, by modifying the assets file to insert a placeholder in E. With this change only C is treated as a transitive reference and E is not sent to the compiler. This compiles fine, but I get an error in the GenerateBuildDependencyFile target about missing project information. We will need to fix DependencyContextBuilder to handle this case. I'll file a bug on this.

@natidea
Copy link
Contributor Author

natidea commented Dec 21, 2016

/cc @srivatsn for Ask Mode approval

@natidea
Copy link
Contributor Author

natidea commented Dec 21, 2016

Merging so we can get some insertions going. Additional feedback still welcome.

@ripenko
Copy link

ripenko commented Mar 2, 2017

When this fix will be available in VS 2017?

@dsplaisted
Copy link
Member

It should already be in the latest RC of VS 2017.

@KshitizGIT
Copy link

Any way I can opt out of this?

Lets say, I have the following project layout.

A -> B -> C.

I don't want classes inside assembly C to be directly accessible inside assembly A.

@srivatsn
Copy link
Contributor

srivatsn commented Jun 23, 2017

Adding PrivateAssets=All to the ProjectReference stops the flow of the transitive ref. So, in this case. B.csproj would have this:

  <ProjectReference Include="C.csproj" PrivateAssets="All" />

@nguerrera
Copy link
Contributor

@srivatsn, you mean ProjectReference, right?

@srivatsn
Copy link
Contributor

Ah yes typo - fixed it the original comment.

@KshitizGIT
Copy link

Thanks a lot.. Worked like a charm 👍

mmitche pushed a commit to mmitche/sdk that referenced this pull request Jun 5, 2020
kzu added a commit to devlooped/nugetizer that referenced this pull request Aug 8, 2024
Turns out that the .NET SDK lifts transitive project references as direct references (without any additional metadata), and this causes the second-level dependency from being built unexpectedly (see dotnet/sdk#478 and dotnet/project-system#199).

Since we don't want to disrupt the IDE (BuildingInsideVisualStudio) and we only want to fix this for the very specific case of running from the CLI `dotnet pack --no-build`, we make the fix very constrained for that scenario. We check for `NoBuild` but ALSO for `_IsPacking`, which is passed by the `dotnet pack` command.

This ensures minimal impact in all other scenarios, since we're essentially turning off a built-in behavior in the SDK that has explicit side-effects (by design and desirable) and we should preserve.

#Fixes 501
kzu added a commit to devlooped/nugetizer that referenced this pull request Aug 8, 2024
Turns out that the .NET SDK lifts transitive project references as direct references (without any additional metadata), and this causes the second-level dependency from being built unexpectedly (see dotnet/sdk#478 and dotnet/project-system#199).

Since we don't want to disrupt the IDE (BuildingInsideVisualStudio) and we only want to fix this for the very specific case of running from the CLI `dotnet pack --no-build`, we make the fix very constrained for that scenario. We check for `NoBuild` but ALSO for `_IsPacking`, which is passed by the `dotnet pack` command.

This ensures minimal impact in all other scenarios, since we're essentially turning off a built-in behavior in the SDK that has explicit side-effects (by design and desirable) and we should preserve.

#Fixes 501
kzu added a commit to devlooped/nugetizer that referenced this pull request Aug 8, 2024
Turns out that the .NET SDK lifts transitive project references as direct references (without any additional metadata), and this causes the second-level dependency from being built unexpectedly (see dotnet/sdk#478 and dotnet/project-system#199).

Since we don't want to disrupt the IDE (BuildingInsideVisualStudio) and we only want to fix this for the very specific case of running from the CLI `dotnet pack --no-build`, we make the fix very constrained for that scenario. We check for `NoBuild` but ALSO for `_IsPacking`, which is passed by the `dotnet pack` command.

This ensures minimal impact in all other scenarios, since we're essentially turning off a built-in behavior in the SDK that has explicit side-effects (by design and desirable) and we should preserve.

#Fixes 501
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants