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

Allow CopyToOutputDirectory to have a custom destination path #2795

Closed
yufeih opened this issue Dec 10, 2017 · 10 comments · Fixed by #6237
Closed

Allow CopyToOutputDirectory to have a custom destination path #2795

yufeih opened this issue Dec 10, 2017 · 10 comments · Fixed by #6237
Labels

Comments

@yufeih
Copy link

yufeih commented Dec 10, 2017

The destination of CopyToOutputDirectory attribute in msbuild is fixed, there are various questions about how to change the destination path:

https://stackoverflow.com/questions/10204370/can-i-specify-the-output-path-for-the-msbuild-content-tag
https://stackoverflow.com/questions/18591107/copytooutputdirectory-override-destination-path
https://stackoverflow.com/questions/5915610/msbuild-project-file-copy-item-to-specific-location-in-output-directory
https://stackoverflow.com/questions/1014207/copy-to-output-directory-copies-folder-structure-but-only-want-to-copy-files/21534669#21534669

The simplest accepted answer requires a custom msbuild task or command running after the build. Please add an additional OutputDirectory attribute.

Project file

<Project>
  <ItemGroup>
    <None Include="../folder1/**/*.*" CopyToOutputDirectory="PreserveNewest" OutputDirectory="folder2" />
  </ItemGroup>
</Project>

Directory contents:

/
- folder1/
     - a.cs
- proj/
     - proj.csproj

Command line

msbuild proj/proj.csproj

Expected behavior

Output directory contents:

bin/Debug/
- folder2/
        - a.cs

Actual behavior

Output directory contents:

bin/Debug/
- a.cs  
@dasMulli
Copy link
Contributor

dasMulli commented Dec 10, 2017

This currently works by using the Link metadata on the items:

<ItemGroup>
  <None Include="../folder1/**/*" Link="folder2\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest"  />
</ItemGroup>

SDK-based projects (default for .NET Core and .NET Standard) now support LinkBase metadata as per dotnet/sdk#1246.

If setting the Link metadata automatically, the value will be set to %(LinkBase)\%(RecursiveDir)%(Filename)%(Extension). If LinkBase or RecursiveDir are not defined, then those pieces will be left out of the value. So <Compile Include="..\Shared\**\*.cs" LinkBase="Shared" /> would show the items under the "Shared" folder in solution explorer, instead of the root, and would preserve any heirarchy under the shared folder.

https://stackoverflow.com/questions/45800697/new-csproj-format-how-to-specify-entire-directory-as-linked-file-to-a-subdi/45802399#45802399

I patched up the code from the SDK (minus a fix when referencing shared projects added later on) to be usable in classic projects in https://stackoverflow.com/questions/44423582/link-additional-files-in-visual-studio/44427519#44427519

The downside of using the Link metadata is that the items will be shown in the solution explorer under the constructed path which may or may not be what the user wants. Maybe the AssignTargetPaths task could be extended to check if there is already a TargetPath metadata on the items it processes. This is useful since in contrast to ContentWithTargetPath, _NoneWithTargetPath is private by convention. This could allow for additional TargetPathBase logic similar to LinkBase but without interfering with logic coming from the SDK.

Personally, I'm fine with the way Link and especially the SDK's LinkBase work, I just wished LinkBase would have been implemented in MSBuild directly.

Example showing usage of both ways:
linktest.zip

@yufeih
Copy link
Author

yufeih commented Dec 10, 2017

Thanks @dasMulli , LinkBase does solve the problem of linking files outside the project dir.

@yufeih yufeih closed this as completed Dec 10, 2017
@MJLHThomassen-Eurocom
Copy link

The link element behaves strangely for me in a .net 4.6.2 project.

I have a few files in a "wwwroot/build" folder that, when the project builds, i want to output to the /bin/Release/wwwroot folder. In my .csproj file i added the following:

<PropertyGroup>
		<AssignTargetPathsDependsOn>
			$(AssignTargetPathsDependsOn);
			NpmBuildFiles;
		</AssignTargetPathsDependsOn>
	</PropertyGroup>
	
	<!-- Gets the files build by 'npm run build' -->
	<Target Name="NpmBuildFiles" DependsOnTargets="NpmBuild">	
		<ItemGroup>
			<None Include="wwwroot/build/**/*"
				Link="wwwroot\%(RecursiveDir)%(Filename)%(Extension)"
				CopyToOutputDirectory="PreserveNewest"/>
		</ItemGroup>
	</Target>

However, after the build, the bin/Release/wwwroot folder contains a few files, but not all of them and some of them even files that do not exist in the wwwroot/build folder (but that do exist in the original wwwroot folder).

The msbuild output seems to indicate that a bunch of files are mapped onto a single file with the link element:

Added Item(s): 
12>    None=
12>        wwwroot\build\asset-manifest.json
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\favicon.ico
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\index.html
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\manifest.json
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\service-worker.js
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\css\main.fc2aea1a.css
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\css\main.fc2aea1a.css.map
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\js\main.00b5ebca.js
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\js\main.00b5ebca.js.map
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\flags.9c74e172.png
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.674f50d2.eot
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.912ec66d.svg
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.af7ae505.woff2
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.b06871f2.ttf
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.fee66e71.woff
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>Added Item(s): 
12>    None=
12>        wwwroot\build\asset-manifest.json
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\favicon.ico
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\index.html
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\manifest.json
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\service-worker.js
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\css\main.fc2aea1a.css
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\css\main.fc2aea1a.css.map
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\js\main.00b5ebca.js
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\js\main.00b5ebca.js.map
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\flags.9c74e172.png
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.674f50d2.eot
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.912ec66d.svg
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.af7ae505.woff2
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.b06871f2.ttf
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.fee66e71.woff
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env

The files listed each time above the "CopyToOutputDirectory" are correct. The files that Link=... indicates obviously are not. However, wwwroot\app.config and wwwroot.env are actual files i have present in my project, but i dont want to copy those to my output, just the contents of the build folder (which need to be put in the root of the wwwroot folder in /bin/Release since the build folder contains the actual files that need to be published).

@benbenwilde
Copy link

it's weird, as if the %(Filename)%(Extension) is only being evaluated only once and then being used for every item matching the include.

@Jaans
Copy link

Jaans commented Oct 21, 2019

For what it's worth, if I placed the <ItemGroup><None ... /></ItemGroup> within a target, I'm able to get the output processed without it showing up in my SDK based project. E.g.

    <ItemGroup>
        <WebPackBuildOutput Include="..\..\WebPackOutput\dist\**\*" />
    </ItemGroup>

    <Target Name="WebPackOutputContentTarget" BeforeTargets="BeforeBuild">
        <Message Text="FileWrites: @(WebPackBuildOutput)" Importance="high"/>
        <ItemGroup>
            <!-- Manually constructing Link metadata, works in classic projects as well -->
            <None Include="@(WebPackBuildOutput)" Link="dist\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
        </ItemGroup>
        
    </Target>

@rcdailey
Copy link

rcdailey commented Nov 2, 2020

I apologize for nudging such an old issue, but I thought I'd try my question here as opposed to opening another new issue.

In a .NET Core format csproj, I'm able to get Link working but not LinkBase. It is unclear to me why it does not work.

With Link (Works):

  <ItemGroup>
    <None Include="TestInput/*" CopyToOutputDirectory="PreserveNewest" Link="LegacyLogsTesterInput/%(RecursiveDir)%(Filename)%(Extension)" />
  </ItemGroup>

With LinkBase (Does not work):

  <ItemGroup>
    <None Include="TestInput/*" CopyToOutputDirectory="PreserveNewest" LinkBase="LegacyLogsTesterInput" />
  </ItemGroup>

In the former case, I observe that the directory $(OutDir)LegacyLogsTesterInput does exist. In the latter, it does not exist. On subtle detail I noticed in the linked pull request is this:

The item’s FullPath is outside of the project directory

Does this mean that I cannot use LinkBase with items that are at or under the directory containing the csproj? Any info on why this isn't working?

For context, I'm trying to "upgrade" my copy logic from the below XML code to use None elements as shown above. The expectation is that the source files, which are located relative to the project, are copied to the defined output directory where the rest of the binaries go.

<Target Name="PostBuild" BeforeTargets="PostBuildEvent">
  <Exec Command="xcopy /s /d /y &quot;$(ProjectDir)TestInput\*&quot; &quot;$(OutDir)LegacyLogsTesterInput\&quot;" />
</Target>

I'm using Visual Studio 2019 (latest updates as of this post).

@dasMulli
Copy link
Contributor

dasMulli commented Nov 3, 2020

The LinkBase metadata doesn't work on paths that are inside the project directory by design. I suggest opening an issue over at dotnet/sdk (where the source code for it resides) for discussion.

@GF-Huang
Copy link

Why not allow user edit in UI?

@chtenb
Copy link

chtenb commented Feb 19, 2021

@Jaans Your method sadly doesn't seem to work transitively across project references.

Forgind pushed a commit that referenced this issue Apr 5, 2021
Fixes #2795
and indirectly fixes https://developercommunity.visualstudio.com/t/copytooutputdirectorypreservenewest-ignored-inside/1332219?from=email&viewtype=all#T-ND1363347

Context
There's currently no way to include items in a project such that:

Visual studio sees them in a specific folder (via <Link>).
They are published to a user-defined path (currently controlled via <Link>)
Changes Made
Modify the AssignTargetPath task to return early if TargetPath metadata is already set on a particular item.

Testing
 Need to add one test covering this.
 Tested locally with bootstrapped MSBuild on command line
 Tested locally with a boostrapped msbuild on internal VS
Here's the repro I'm using:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Content Include="Files\**">
      <Link>Files\%(Filename)%(Extension)</Link>
      <TargetPath>%(Filename)%(Extension)</TargetPath>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</Project>
Notes
The other way of solving this problem has to do with Microsoft.Common.CurrentVersion.targets. We modify it so that the AssignTargetPath task look something like this:

    <AssignTargetPath Files="@(Content)" RootFolder="$(MSBuildProjectDirectory)" Condition="'%(Content.TargetPath)'==''">
      <Output TaskParameter="AssignedFiles" ItemName="ContentWithTargetPath" />
    </AssignTargetPath>
    <ItemGroup>
        <ContentWithTargetPath Include="@(Content)" Condition="'%(Content.TargetPath)'!=''"/>
    </ItemGroup>
This seems less efficient to me. AssignTargetPath is also called for all None, Content, and EmbeddedResource files. So if we go this batching route and want None or EmbeddedResource to have this feature, we'd need to batch those as well.
@Jasonlhy
Copy link

The link element behaves strangely for me in a .net 4.6.2 project.

I have a few files in a "wwwroot/build" folder that, when the project builds, i want to output to the /bin/Release/wwwroot folder. In my .csproj file i added the following:

<PropertyGroup>
		<AssignTargetPathsDependsOn>
			$(AssignTargetPathsDependsOn);
			NpmBuildFiles;
		</AssignTargetPathsDependsOn>
	</PropertyGroup>
	
	<!-- Gets the files build by 'npm run build' -->
	<Target Name="NpmBuildFiles" DependsOnTargets="NpmBuild">	
		<ItemGroup>
			<None Include="wwwroot/build/**/*"
				Link="wwwroot\%(RecursiveDir)%(Filename)%(Extension)"
				CopyToOutputDirectory="PreserveNewest"/>
		</ItemGroup>
	</Target>

However, after the build, the bin/Release/wwwroot folder contains a few files, but not all of them and some of them even files that do not exist in the wwwroot/build folder (but that do exist in the original wwwroot folder).

The msbuild output seems to indicate that a bunch of files are mapped onto a single file with the link element:

Added Item(s): 
12>    None=
12>        wwwroot\build\asset-manifest.json
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\favicon.ico
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\index.html
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\manifest.json
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\service-worker.js
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\css\main.fc2aea1a.css
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\css\main.fc2aea1a.css.map
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\js\main.00b5ebca.js
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\js\main.00b5ebca.js.map
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\flags.9c74e172.png
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.674f50d2.eot
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.912ec66d.svg
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.af7ae505.woff2
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.b06871f2.ttf
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>        wwwroot\build\static\media\icons.fee66e71.woff
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\app.config
12>Added Item(s): 
12>    None=
12>        wwwroot\build\asset-manifest.json
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\favicon.ico
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\index.html
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\manifest.json
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\service-worker.js
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\css\main.fc2aea1a.css
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\css\main.fc2aea1a.css.map
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\js\main.00b5ebca.js
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\js\main.00b5ebca.js.map
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\flags.9c74e172.png
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.674f50d2.eot
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.912ec66d.svg
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.af7ae505.woff2
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.b06871f2.ttf
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env
12>        wwwroot\build\static\media\icons.fee66e71.woff
12>                CopyToOutputDirectory=PreserveNewest
12>                Link=wwwroot\.env

The files listed each time above the "CopyToOutputDirectory" are correct. The files that Link=... indicates obviously are not. However, wwwroot\app.config and wwwroot.env are actual files i have present in my project, but i dont want to copy those to my output, just the contents of the build folder (which need to be put in the root of the wwwroot folder in /bin/Release since the build folder contains the actual files that need to be published).

I think link support support single file

@AR-May AR-May added the triaged label Feb 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants