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

Transitive Dependencies are referenced #6614

Closed
GKBelmonte opened this issue Feb 27, 2018 · 15 comments
Closed

Transitive Dependencies are referenced #6614

GKBelmonte opened this issue Feb 27, 2018 · 15 comments
Labels
Resolution:Question This issues appears to be a question, not a product defect Style:PackageReference
Milestone

Comments

@GKBelmonte
Copy link

GKBelmonte commented Feb 27, 2018

NuGet product used:

NuGet version: 4.5.1
VS version: 2015 update 3

Transitive dependencies are added as direct references to the consuming project.

There's no way of distinguishing private vs. public dependencies that I could find anywhere.
A public dependency is a dependency that the dependent will also need to reference (current default behaviour)
Described in this and this.
(private vs public)

Adding them directly to your package and using <reference> on only your lib (which is obviously a bad idea) still doesn't work, because MSBuild only copies dlls that are references.

Which is this issue, also a problem, since if the dlls are not copied you have to either copy them yourself through some MSBuild hack, a post-build hack, or as the first recommended work-around removing <references> hence defeating the purpose of using it in the first place.

@GKBelmonte
Copy link
Author

bump?

@nkolev92
Copy link
Member

nkolev92 commented Mar 2, 2018

This is a known design flaw of packages.config, and unfortunately there's not much you can do.

For that reason, we've spent the last few years developing PackageReference

Consider trying that out and give us any feedback.
More docs here

@nkolev92 nkolev92 added the Resolution:Question This issues appears to be a question, not a product defect label Mar 2, 2018
@nkolev92 nkolev92 closed this as completed Mar 2, 2018
@nkolev92 nkolev92 added this to the 4.7 milestone Mar 2, 2018
@GKBelmonte
Copy link
Author

It appears to me that this is package authoring problem, not a restore problem. The package author should be able to declare what dependencies are private or not (since he knows) and the package consumer able to override these.
I understand PackageReference can be used to pretend all dependencies are private, but that depends on the package itself and how it was authored not how it is consumed.

@nkolev92
Copy link
Member

nkolev92 commented Mar 2, 2018

I don't think I follow here.

What's the difference between a public and a private dependency?

NuGet doesn't really have that concept once the nupkg is built.
Once the nupkg is built, only the concept of dependencies is relevant.

@GKBelmonte
Copy link
Author

Hi,
I added reference to an explanation in my original post (private vs public).

In a nutshell, public dependencies are exposed through public API, private dependencies are not.

e.g.:

If I use a library for utilities, say Newtonsoft.Json, to read/write Json, that's a private dependency, a module depending on mine, does not need to reference Newtonsoft.Json.

In fact, unless he himself uses it, he doesn't even need to know I use it.

On the other hand, if for whatever reason I return a JObject (or Newtonsoft object) or take it as a parameter, the module depending on mine needs to reference Newtonsoft.Json in order to use me, or when tries to add a call to that code, it will be a compile-time error, since he can't pass me a JObject or receive one if he doesn't reference the transitive dependence. That's a public dependency.

I suspect that is also why originally "Copy Local" msbuild reference tag was called <Private> in vs2015 and earlier.

As a Package author, I know (or should know) which of my dependencies are public (exposed to my clients) and which are private (only used internally), and should be able to say that in my nuspec.

Public dependencies should be referenced, by the consumer of my package and Private should not.

As it stands now, I can have a UtilityPackage that needs Newtonsoft, and people adding me as a NuGet package could call Newtonsoft arbitrarily even though they themselves have no dependency on them, which could break things if let's say tomorrow I decide to remove Newtonsoft dependency from my package.

If, for whatever reason, the consumer wants to override the default package dependency, by all means.

If you want a sample project demonstrating the difference, I can cook one up too!

Thanks

@nkolev92
Copy link
Member

nkolev92 commented Mar 2, 2018

NuGet does not have a concept of public/private dependencies.

When you have a project, you can decide whether the dependencies of your project are build time or runtime by playing with the metadata of the package declaration.

developmentDependency=true in packages.config
PrivateAssets=All, ExcludeAssets=runtime in Package Reference.

If you're using a library, there's no way to prevent the consumer from being able to code against it. That's just an extra API, same as yours. The access level is defined by the author the dll you're using.

The example you're articulating is a user-error.
You need to know your dependencies. And the fix is simple.

@GKBelmonte
Copy link
Author

Hey,

Thanks for getting back to me.

I understand that if my project A requires a package B, I can say whether my package B is a build-time or run-time dependency, so that if its build-time, it's excluded from the output.

The problem is that the concept of public v. private is missing, so if I have a reference on a package A that depends (at runtime) on package B, not only do I get package B (as expected) but I also have a reference to package B, not as expected.

Since I have a reference to Package B, it pollutes my access, and worse I can mistakenly call package B code directly.

Here's a concrete example of the problem:

I have a project that consumes as a NuGet package NuGet.Build.Tasks

static void Main(string[] args)
{
    Console.WriteLine(Newtonsoft.Json.ConstructorHandling.Default);
}

This project compiles and runs fine.
Despite that nothing about NuGet.Build.Tasks exposes Newtonsoft.
(It is in fact exposed via NuGet.ProjectModel in DependencyGraphSpec though, so it's a public dependency transitively)

That's my point, if DependencyGraphSpec didn't expose Newtonsoft in its public interface (hence making it a public dependency, not a private one), and so people consuming NuGet.ProjectModel wouldn't need to reference Newtonsoft, despite depending on it at runtime.

If, let's say tomorrow you switch to SimpleJson, it wouldn't be seamless, as there could be compile-time errors if the clients use JObject.

If I author a package that depends on Newtonsoft but never expose Newtonsoft in my API, I can be free to mark it as a Private Dependency and people consuming my package dont need to have a reference to my package.

@nkolev92
Copy link
Member

nkolev92 commented Mar 3, 2018

I get your example.
As far as I'm aware, it's just not possible to do in .NET.

It's definitely not something that NuGet standalone can provide.

@GKBelmonte
Copy link
Author

Hello,

If you are in the old project format, .NET Framework (old .csproj) and earlier, you have to add the reference to a transitive public reference manually.

This means the default (and only) behaviour for .NET framework is to treat all dependencies as private (no transitive dependency).
If this happens for NuGet, it's because the dependencies are treated as public by default and NuGet manually adds a transitive reference, which wouldn't be done for a normal project reference.

e.g.:

 var dep = new DirectDependency.DirectClass();
 var transPub = new TransitivePublicDependency.TransitivePublicClass();
 //The type or namespace name 'TransitivePublicDependency' could not be found (are you missing a using directive or an assembly reference?)

On the other hand,
In the new project Vs2017 with .NET Starndard, transitive dependencies are treated as public by default
you can do this.

e.g.:

(Consumer.csproj)
  <ItemGroup>
    <ProjectReference Include="..\DirectDependency\DirectDependency.Standard.csproj" />
  </ItemGroup>

(DirectDependency.csproj)
  <ItemGroup>
    <ProjectReference Include="..\TransitivePrivateDependency\TransitivePrivateDependency.csproj" PrivateAssets="All" />
    <ProjectReference Include="..\TransitivePublicDependency\TransitivePublicDependency.csproj" />
  </ItemGroup>

What I think would be the solution is something like:

    <dependencies>
      <dependency id="MyPrivateDependency" version="1.0" PrivateAssets="All" />
      <dependency id="MyPublicDependency" version="1.0" PrivateAssets="None" />
    </dependencies>

So, in a nutshell, it is possible to do in .NET.

Right now, PrivateAssets is always none on NuGet dependencies, so that could be the default.

It's possible I'm misunderstading something, but I think this is essentially either a feature request, or a bug, since the default MsBuild behaviour for .NET framework is the opposite of NuGet. (bug?)
And what can be done with just projects in .NET Standard, cannot be done with nuspec. (feature request?)

@nkolev92
Copy link
Member

nkolev92 commented Mar 5, 2018

PrivateAssets is used for dependency flow.

If you have a package that's marked with private assets it merely prevents it from flowing to parent project or getting packed.

For example, I'm developing

CoolPackage

and the dependencies for the project are:

<PackageReference Include="A" Version="1.0.0">
<PackageReference Include="B" Version="1.0.0" PrivateAssets="all">

then when you pack that package, in the dependencies section you will only see package A. as per #5455.

So for NuGet, when it gets the package CoolPackage, it only knows about packageA as a dependency, has no clue about package B.
Hence why I said, NuGet does not have a private/public dependency concept.

I'm not sure I understand what you're suggesting with the packages.config and old csproj scenario.
Take the NuGet.Commands package for example.
It has many dependencies.
A 2 level deep transitive dependency is newtonsoft.json.
If I install nuget.commands to my project (nuget would install the full closure of the package), I can still code against Newtonsoft.json.

@GKBelmonte
Copy link
Author

Hello,
I both understand how PrivateAssets work (that's the point of my example!) and that NuGet doesn't support it for it's dependencies.

My point is that it should, since currently this breaks encapsulation.

A package author should be able to declare a dependency as private or not (just like we can declare a PackageReference or a ProjectReference we can say that they're private).

My request is simple: Add an option at the .nuspec level (package authoring) to say that a dependence should not be transitively referenced.

As far as I'm aware, it's just not possible to do in .NET.

My example simply shows that it is possible with .NET, hence

So, in a nutshell, it is possible to do in .NET.

I showed this is doable in both the old and the new project format.

Thanks

@jainaashish
Copy link
Contributor

Although PrivateAssets isn't supported on dependencies in nuspec file but nuspec file has include and exclude flags which works similar to PrivateAssets and even their values are set as per different flags defined on package reference in project file.

like in your case:

<ItemGroup>
    <PackageReference Include="MyPrivateDependency" PrivateAssets="All" />
    <PackageReference Include="MyPublicDependency" />
  </ItemGroup>

This will be converted to below code in nuspec file:

<dependency id="MyPrivateDependency" version="1.0" exclude="All" />
<dependency id="MyPublicDependency" version="1.0" />

So here exclude=all on MyPrivateDependency means, nothing will be flown from this to destination project, which is what you want isn't it?

@GKBelmonte
Copy link
Author

Hello,

Sorry for the delay, I was under the weather.

I thought exclude='all' was the default value, but I went ahead and tried it anyway.
I found out that exclude="All" doesn't work because the private dependency dll is not copied to the output directory, but exclude="compile" does.

e.g.: Consumer -> DirectDependency -> MyPrivateDepenency
MyPrivateDependency.dll is copied but not referenced if I use exclude='compile'

This is what the .nuspec looks like:

<dependencies>
      <dependency id="TransitivePrivateDependency" version="1.0" exclude="compile" />
      <dependency id="TransitivePublicDependency" version="1.0" />
</dependencies>

Tested it in the new and old .csproj format successfully!

This is what project.lock.json results in:

"DirectDependency/1.0.1": {
        "type": "package",
        "dependencies": {
          "TransitivePrivateDependency": "1.0.1",
          "TransitivePublicDependency": "1.0.1"
        },
        "compile": {
          "lib/net45/DirectDependency.vs2012.dll": {}
        },
        "runtime": {
          "lib/net45/DirectDependency.vs2012.dll": {}
        }
      },
      "TransitivePrivateDependency/1.0.1": {
        "type": "package",
        "compile": {
          "lib/net45/_._": {}
        },
        "runtime": {
          "lib/net45/TransitivePrivateDependency.vs2012.dll": {}
        }
      },
      "TransitivePublicDependency/1.0.1": {
        "type": "package",
        "compile": {
          "lib/net45/TransitivePublicDependency.vs2012.dll": {}
        },
        "runtime": {
          "lib/net45/TransitivePublicDependency.vs2012.dll": {}
        }
      }

Also, FYI I tested and discovered that PrivateAssets doesn't work for some reason for .NET standard. The private dependency does get copied to the output directory, but at runtime it fails to find it., giving a FileNotFound with the dll info.

I guess the Doc could use some updating?

Thank you for your time, I guess I'll go poke MicroSoft about this other problem next

Gab

@jainaashish
Copy link
Contributor

Also, FYI I tested and discovered that PrivateAssets doesn't work for some reason for .NET standard. The private dependency does get copied to the output directory, but at runtime it fails to find it., giving a FileNotFound with the dll info.

Can you provide more details here or a sample repro case to depict what you're really seeing?

And I agree doc should be updated to reflect the meaning of all these flags. I'll take a pass at it and update it accordingly.

@GKBelmonte
Copy link
Author

Sorry, I wasn't super clear.
PrivateAssets for ProjectReference did not work, the runtime-dependency (non-transitive reference) gets copied (as expected), but at runtime it isn't found.
This is dotnet's problem, not your fault. I'd give you more info, but my VS is broken since I updated it in the weekend.
(I updated it in fact to check if it was just a bug that was fixed).

Not NuGet end, I think the behaviour using exlude="build" is exactly what I expected, since it is a runtime-only dependency (so no need to reference).

Thanks,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Resolution:Question This issues appears to be a question, not a product defect Style:PackageReference
Projects
None yet
Development

No branches or pull requests

3 participants