NuGet / Home

Repo for NuGet Client issues
Other
1.49k stars 249 forks source link

Provide an option to hide content files from project tree in new-style csproj #4856

Open davidmatson opened 7 years ago

davidmatson commented 7 years ago

I'm currently using VS 2015 with project.json with a number of sources-style NuGet packages (packages that only have contentFiles with c# code as *.cs files).

There are quite a few files that come in this way, so having them not show up in the project root is key. When I try migrating to the new-style csproj, that's what happens, so I'm currently blocked (stuck back in project.json-style NuGet).

Two possible solutions here (I'm sure there are others as well):

  1. Change back the default for contentFiles to be hidden from the csproj like they were with project.json.
  2. Provide a per-csproj setting to turn off showing referenced packages' contentFiles in the project tree.

If there's a workaround I could use at the moment (perhaps getting the files to appear somewhere nested under a project folder rather than showing up directly in the project root), that would be great to know as well. (Hopefully long-term there is a way to control where these files appear, unless the default changes back. Project root-only doesn't seem sufficient for a large number of content files.)

davidmatson commented 7 years ago

See also #4166 which may be related.

davidmatson commented 7 years ago

I think there's a slightly broader question here to consider as well - if contentFiles do appear, how should they show up in the project tree? One possible option (my preference) would be to have them appear underneath the Dependencies -> NuGet node in the project (show the .cs files directly underneath the name of the package that includes them).

There may be other/better options as well. Having the ability to show these files is nice for setting breakpoints, but IMO we should have something better than just dumping everything in the project root.

davidmatson commented 7 years ago

I'm currently using the following workaround (thanks to @emgarten for the idea):

  <ItemGroup>
    <Compile Update="@(Compile)">
      <Visible Condition="'%(NuGetItemType)' == 'Compile'">false</Visible>
    </Compile>
  </ItemGroup>
davidmatson commented 7 years ago

Here's a fairly simple option that might improve things: The props file currently does something like this:

    <Compile Include="$(NuGetPackageRoot){package ID}\{package version}\contentFiles\cs\net45\{relative path}" Condition="Exists('$(NuGetPackageRoot){package ID}\{package version}\contentFiles\cs\net45\{relative path}')">
      <NuGetPackageId>{package ID}</NuGetPackageId>
      <NuGetPackageVersion>{package version}</NuGetPackageVersion>
      <NuGetItemType>Compile</NuGetItemType>
      <Private>False</Private>
      <Link>{relative path}</Link>
    </Compile>

Note that the link item just has the relative path of the cs file. Add some prefix to the link item, such as "NuGetContent{package Id}":

      <Link>NuGetContent\{package Id}\{relative path}</Link>

Unfortunately, I think this approach would only work item types like Compile that aren't binplaced; items that are copied to the output directory would get messed up if they have some kind of prefix added to them, because it would affect the output path.

davidmatson commented 7 years ago

The idea of the prefix was inspired by this technique from the pre-NuGet 3 days: https://nikcodes.com/2013/10/23/packaging-source-code-with-nuget/

Though I'd suggest omitting the version from the path as only one version of a package can be installed anyway (and making the path longer might contribute to MAX_PATH issues; not sure if that matters in a Link though).

davidmatson commented 7 years ago

Actually it looks like there's a more fundamental problem here and something like the NuGetContent approach mentioned above is essential to correct behavior, due to collisions with multiple packages using the same file name. See #5048 for details.

tmat commented 7 years ago

I'm using the above workaround like so:

<Compile Update="@(Compile)">
  <Link Condition="'%(NuGetPackageId)' != ''">%(NuGetPackageId)\%(Link)</Link>
</Compile> 

This serves two purposes: 1) It's nicer to not have a bunch of links from referenced packages pollute the project root. 2) Correctness in case there are two files of the same name in the referenced packages.

This should imo be the default.

leak commented 6 years ago

What is the state of this issue? MySql.Data is shipping tons of content files that are polluting my solution explorer in VS 2017. I cannot remove or edit those file and folder links in any way without getting tons of errors.

emgarten commented 6 years ago

@leak use @tmat's example to remove the links.

leak commented 6 years ago

I did and changed it to "Content", but it does not get rid of folders for me.

mscrivo commented 6 years ago

Are the above workarounds supposed to work for dependencies of a referenced nuget package? They don't seem to.

For example, I am referencing the CefSharp.Common package which has a dependency on CefSharp.Redist.x64 which has a bunch of compiled C++ libs that are to be pulled into the bin folder upon building, but are also showing up in the root of my project structure in Solution Explorer, and cannot figure out how to make those go away.

amaitland commented 5 years ago

For example, I am referencing the CefSharp.Common package which has a dependency on CefSharp.Redist.x64 which has a bunch of compiled C++ libs that are to be pulled into the bin folder upon building, but are also showing up in the root of my project structure in Solution Explorer, and cannot figure out how to make those go away.

For reference the CefSharp Items are included as <None/> entries, perhaps you can try https://github.com/dotnet/project-system/issues/1126#issuecomment-271741179

Barsonax commented 4 years ago

We are also running into this in our .net based game engine in our sample packages that also include files like shaders, textures etc: 84692048-81974480-af45-11ea-9f32-1565373e3615

This is very confusing behavior and I would like these files to be either:

I also noticed that these files are editable and its possible to save these edits which shouldn't be possible as they are supposed to be readonly. This is a quite serious bug as this package could possibly be shared with other projects!!

JVimes commented 3 years ago

I love David's idea of showing content files under their package in Dependencies:

image

Kryptos-FR commented 3 years ago

Copying my proposal from https://github.com/dotnet/project-system/issues/6290#issuecomment-660458976

Whether a file is "visible" or not shouldn't even be a responsibility of the package/library maker. Some users of those packages might decide they want to display, some might not. So there should be an option to show/hide this on a per-project basis.

Look at dotnet/project-system#3302 for an idea of the mess it can be. I have to scroll down more than 60 items before seeing the first class of my project.

So here is a proposal:

<PackageReference Include="ThirdParty" Version="1.0">
    <ContentFilter Include="*.config" Visible="False" />
    <ContentFilter Include="*.dll" Visible="False" />
</PackageReference>

Note that this proposal is compatible with the above proposal to show those files under the package node.

kzu commented 3 years ago

I think making contentFiles visible by default under the dependency node makes the most sense.

drewnoakes commented 3 years ago

@kzu since VS 16.7, contentFiles should be already be visible under package nodes. This was merged in https://github.com/dotnet/project-system/pull/6066, though the implementation has since moved to NuGet.Client.

This issue is just tracking removal of content files from the rest of the tree.

StingyJack commented 3 years ago

I think making contentFiles visible by default under the dependency node makes the most sense.

if I cant change them or use them, I dont want to see them.

I've finally been more or less coerced into using package reference style of nuget after holding out with packages.config for as long as I could (3+ years) and yet there are still these obvious, productivity crippling bugs with package reference. How much time does this cost users trying to navigate project structure to "just deal with it", and how much time is it costing for those who tried to troubleshoot before they find this issue report? It cost me few hours already and is still accruing.

remcoros commented 3 years ago

Any update on this?

Navigating a project like this is pretty awful: (this project references a nuget package with a lot of contentFiles)

Adding '<Visible>False</Visible>' to the content item does not seem to do anything (this is what the MSTest package also does, but doesn't have any effect in the solution explorer)

image

kzu commented 3 years ago

You have turned on "Show All Files", that icon:

image

So that's expected I think.

remcoros commented 3 years ago

@kzu how embarrassing... I totally missed that.

StingyJack commented 3 years ago

Even so, they aren't part of the project. You can't do anything with them except be confused by them.

abatishchev commented 3 years ago

4 years later, any progress on this one, please?

luojunyuan commented 2 years ago

I still waiting for this

AdamVicente commented 1 year ago

This is what worked for me, I used Nuget Package Explorer (NPE) but this should work with other packers as well. Create a new nuget package in NPE. Add a folder "contentFiles" and "Build". In the "contentFiles" folder add a folder "any" and inside that another folder "any." (The any any has to do with the target build, if it does not matter any any is safest). Add all unmanaged DLLs/files in this inner most folder. Edit the metadata source code (.nuspec file) so that it looks like this:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>Example.Code</id>
    <version>1.0.0</version>
    <title></title>
    <authors>Me</authors>
    <owners>Me</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Description</description>
    <contentFiles>
      <files include="any\any\File1.dll" buildAction="None" copyToOutput="true" flatten="false" />
      <files include="any\any\File2.txt" buildAction="None" copyToOutput="true" flatten="false" />
    </contentFiles>
  </metadata>
</package>

Where all unmanaged files are listed in the content file section. If build action is needed that can be set, make sure copyToOutput is set to True, and flatten can be set to false unless folder structure must be retained.

In the "Build" folder you will need to add a .props file with the same name as your project. The .props file should be set up as following:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <None Include="File1.dll">
      <Visible>false</Visible>
    </None> 
    <None Include="File2.txt">
      <Visible>false</Visible>
    </None> 
  </ItemGroup>
</Project>

Where again you list every file. The Visible false tag will keep these from showing up in the solution explorer in Visual studio. If the build action for a file is not None in the .nuspec file then you may have to use a Compile instead of None ItemGroup.

tqk2811 commented 1 year ago

Thanks for .props file

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <ItemGroup>
        <None Update="@(None)">
            <Visible Condition="'%(NuGetItemType)' == 'None' and '%(NuGetPackageId)' == 'My.Package.Id'">False</Visible>
        </None>
                <Compile Update="@(Compile)">
            <Visible Condition="'%(NuGetItemType)' == 'Compile' and '%(NuGetPackageId)' == 'My.Package.Id'">False</Visible>
        </Compile>
    </ItemGroup>
</Project>
AdamVicente commented 1 year ago

Now this method will work to add unmanaged files to the bin directory only on project build. If you are sending the executable of your project somewhere else additional steps will be required. The .nuspec file can stay the same with None items for each required file but the items must be EmbeddedResource in the .props file. When you add the nuget file to a project this will add the unmanaged files as an embedded resource for the project. Now before calling these unmanaged DLLs they must be copied to the bin directory. I do this using a constructor on the containing class. Example is in C#:

using System.Reflection;
using System.IO;

public class UnManagedDLL{

//constructor
public UnManagedDLL()
{
            Assembly assem = Assembly.GetExecutingAssembly();
            string[] embededFiles = assem.GetManifestResourceNames();
            string[] unManagedFiles = new string[]{
                "File1.Dll",
                "File2.txt"
                  };
            string outPutDirectory = Path.GetDirectoryName(assem.CodeBase);
            outPutDirectory = outPutDirectory.Replace("file:\\", "");
            foreach (string file in unManagedFiles)
            {
                string path = Path.Combine(outPutDirectory, file);
                    foreach (string eFile in embededFiles)
                    {
                        if (eFile ==  file)
                        {
                            using (Stream fs = assem.GetManifestResourceStream(eFile))
                            {
                                byte[] fsBytes = new byte[fs.Length];
                                fs.Read(fsBytes, 0, fsBytes.Length);
                                File.WriteAllBytes(path, fsBytes);
                            }
                            break;
                        }
                }
            }
}

//variables for unmanaged dll
public int n {get;set;}

//unmanaged dll specific functions
[DllImport("UnManaged.dll", EntryPoint = "Func")]
protected static extern void Func(int n);

}

Note that the IO operations performed here are not thread safe. This will try to write the embedded files to the output directory every time a new instance of the containing class is created. To save time during execution I also check to see if the files already exist and if they do I don't write over them.

abhinavmathur06 commented 7 months ago

6 years later, any progress on this one, please?

tqk2811 commented 7 months ago

6 years later, any progress on this one, please?

you can done it with .props file