Remove file from package reference - msbuild

I have a dependency on the NuGet library:
<ItemGroup>
<PackageReference Include="OpenCvSharp4.Windows" Version="4.5.3.20210817" />
</ItemGroup>
This package adds the file opencv_videoio_ffmpeg453_64.dll to the bin folder, which makes some problems. I don't use FFmpeg and want to remove that file (OpenCV said that it is safe to remove it if you don't use FFmpeg backend).
So, I added these lines to the csproj file:
<Target Name="RemoveOpenCvFFmpeg" AfterTargets="AfterBuild">
<Delete Files="$(OutDir)opencv_videoio_ffmpeg453_64.dll" />
</Target>
It works and everything is fine. But when I pack my project as a NuGet package, the consumers of our library still get that file in their bin folder and it also makes them some issues.
Is there any way to delete this file from the local build and from our package in some way, so our consumer won't get this file either?
Or pack them so the consumer will not restore OpenCvSharp4.Windows by its own (I use msbuild /t:pack for packing)

Related

NuGet Package Packing - Is it possible to copy files to a custom directory?

I'm trying to package a few files into a NuGet package, but the issue is that all of the files are sent to the "content" folder within the NuGet package by default when packaged. Normally this is okay, but for the JSON files I have in "ABCJsons" I'd like them to be sent to "content/NewFolderName".
In my example below, the first block is my AbcToolTester, which has all of its project files files being successfully sent to the content directory in the NuGet package. The second block, is where I attempted to copy all the json files with ABCLibrary (ABCLibrary has subfolders where the actual Jsons are located) to the destination folder "ABCJsons". I thought this would do the trick, but unfortunately the ABCJson files just get sent to the content folder along with all the other files.
<ItemGroup>
<Content Include="..\AbcToolTester\bin\Debug\netcoreapp3.1\**" Exclude="..\AbcToolTester\bin\Debug\netcoreapp3.1\*.pdb">
<IncludeInPackage>true</IncludeInPackage>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<ItemGroup>
<Target Name="CopyABCLibrary" AfterTargets="AfterBuild">
<ItemGroup>
<ABCJsonsInclude="..\..\tests\ABCLibrary\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(ABCJsons)" DestinationFolder="$(TargetDir)\ABCJsons" SkipUnchangedFiles="true" />
</Target>
It's hard to tell if the NuGet package you are creating is actually dependent on AbcToolTester project because there are easier ways to package that. That's another question though.
For your actual issue, you can simplify the copying process while also telling it where to pack the files. Replace your CopyABCLibrary target with this:
<ItemGroup>
<Content Include="..\..\tests\ABCLibrary\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Pack>true</Pack>
<PackagePath>ABCJsons\%(RecursiveDir)</PackagePath>
<!-- This line hides the items from showing in the solution explorer -->
<Visible>false</Visible>
</Content>
</ItemGroup>
This will put all those files into the root of the nuget package into the ABCJsons folder and preserve the directory structure. Change the path accordingly to put it somewhere else.

Linked files location on .Net core in Debug vs Publish

I have a shared.{Environment}.json file that is added as linked files in several .Net core 2.1 projects. When project is build or published file gets copied to output directory, in case of release its fine but on debug it doesn't work as when project run it looks up for that file in project directory not in output directory.
Whats the proper way to solve this issue for both debug and publish?
For linked files, it will not exist under the project directory.
For a workaround, you could try to copy the file with task in csproj like below:
<ItemGroup>
<Content Include="..\MVCPro\shared.{Environment}.json">
<Link>shared.{Environment}.json</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Target Name="CopyLinkedContentFiles" BeforeTargets="Build">
<Copy SourceFiles="..\MVCPro\shared.{Environment}.json" DestinationFolder=".\" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" />
</Target>

.NET Core 2.1 : How to trigger Copy task defined in the project file during debugging in the Visual Studio 2017?

There are some files residing in other directories that, I would like to copy to project folder automatically before build and publishing.
After some research and experimentation, I have come up with the following .csproj file.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.4</RuntimeFrameworkVersion>
<TieredCompilation>true</TieredCompilation>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>
<ItemGroup>
<APIDefinition Include="D:\SomePlace\*.API.*.yaml" />
</ItemGroup>
<Target Name="CopyFiles" BeforeTargets="Compile;Build;Publish">
<Copy SourceFiles="#(APIDefinition)" DestinationFolder="wwwroot" />
<Copy SourceFiles="D:\SomePlaceElse\BaseAPISettings.json" DestinationFolder="$(MSBuildProjectDirectory)" />
</Target>
<ItemGroup>
<Compile Remove="wwwroot\**\*;node_modules;bower_components" />
<None Update="**.user;**.vspscc">
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.4" />
</ItemGroup>
</Project>
Here I have defined CopyFiles target, which should be run before the targets I've placed there. This target uses Copy task to copy YAML format API definition files and base API settings to the project directory.
This works well during build, publish etc. Also, if I delete the local file in the IDE, it instantly recopies it from the source.
Sometimes I make changes to these files between debugging sessions. Then, when I start debugging from Visual Studio, since the project files aren't changed, obviously the already built project is run.
Since the project is not built, my copy tasks are not triggered, and I end up with stale files during debuging.
Is there anything I can do to have my Copy tasks triggered, when I do "Start Debugging F5" in the IDE, regardless of the project build state ?
P.S. : I'm using Visual Studio 2017 15.8.5 and targeting .NET Core 2.1.4 runtime, if it makes any difference.
To integrate fully into the up-to-date check of the project system inside Visual Studio, I susggest the following changes:
Make the items' source and target paths known before
Register them to the up-to-date check system. (Also needs a hack to make sure the project source code is recompiled so that the output will have a newer time stamp)
Make the MSBuild target itself incremental. This also helps for command-line builds when the files don't have to be copied.
The complete changes look like this:
<ItemGroup>
<CustomCopyFile Include="..\TestFiles\*.API.*.yaml"
TargetPath="wwwroot\%(Filename)%(Extension)" />
<CustomCopyFile Include="..\TestFiles\BaseAPISettings.json"
TargetPath="%(Filename)%(Extension)" />
<UpToDateCheckInput Include="#(CustomCopyFile)" />
<UpToDateCheckBuild Include="#(CustomCopyFile->'%(TargetPath)')"
Original="#(CustomCopyFile)" />
<CustomAdditionalCompileInputs Include="#(CustomCopyFile->'%(TargetPath)')" />
</ItemGroup>
<Target Name="CopyFiles"
BeforeTargets="BeforeBuild;BeforePublish"
Inputs="#(CustomCopyFile)"
Outputs="#(CustomCopyFile->'%(TargetPath)')">
<Copy SourceFiles="#(CustomCopyFile)"
DestinationFiles="#(CustomCopyFile->'%(TargetPath)')" />
</Target>
CustomCopyFile now collects all the source files and we put the expected destination file name into the TargetPath metadata.
UpToDateCheckInput items tell Visual Studio to rebuild the project if one of these items change.
UpToDateCheckBuild items instruct Visual Studio to only check these items against special source items. This is redundant for this example project but may be helpful if the target path wasn't inside the project directory but some intermediate output (obj..) folder and no re-evaluation would see these new files. It would also be helpful if the files were also modified as part of processing (e.g. replacing variables inside the files).
CustomAdditionalCompileInputs is a hack here since the items are copied to the project folder and are considered to be "inputs to the output" automatically.. So we force the project to recompile if our source files change. If we don't do so, it would never consider the project up-to-date after a change to the source yaml files since they would be newer than the compiled app.dll file.

How to use NuGet targets in standalone MSBuild script without separate call to Restore target

I'm invoking an MSBuild script that isn't a csproj from a bat script. I would like that script to be able to use the MSBuild Community Tasks, and I don't want to have to install it on every machine, nor do I want to include its binaries in my repo.
By adding these nodes to the script and calling the restore target, the package restores.
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<PackageReference Include="MSBuildTasks">
<Version>1.*</Version>
</PackageReference>
</ItemGroup>
To use the tasks it contains, I only need to use them. I don't need to import any other targets files:
<Target Name="MyTarget" DependsOnTargets="Restore">
<AssemblyInfo CodeLanguage="CS"
OutputFile="$(VersionInfoFile)"
AssemblyVersion="1.2.3.5"
/>
</Target>
However, the first time I run my script, the package restores, but then the script fails because it can't find the AssemblyInfo task. The second time, it succeeds. Is there any way to get this to work without calling the MSBuild script twice (the first time, specifically running the Restore target)?
You can force a re-evaluation of the imports generated by NuGet by calling the msbuild file from itself using the <MSBuild> task with a different set of global properties (!).
<Target Name="MyTarget" DependsOnTargets="Restore">
<MSBuild Projects="$(MSBuildProject)" Targets="MyTargetCore" Properties="Foo=Bar" />
</Target>
<Target Name="MyTargetCore">
<AssemblyInfo CodeLanguage="CS"
OutputFile="$(VersionInfoFile)"
AssemblyVersion="1.2.3.5"
/>
</Target>
Depending on the circumstances (solution build, project references), it may or may not work without the Properties="Foo=Bar" part.
However, note that this is a bit risky since not all msbuild caches can even be cleared using the arguments on the MSBuild task. MSBuild 15.5 is going to add a /restore switch that will execute the Restore target, clear all necessary caches and then do the other requested work. So in 15.5 you should be able to call msbuild /restore /t:MyTarget without any difficulties.

MSBuild nuget RestoreOutputPath how to make it work?

New msbuild csproj format have got integrated nuget commands. It's possible to change default path where project assets will be restored by using <RestoreOutputPath>obj\profile7</RestoreOutputPath> command in project file.
But if I add <RestoreOutputPath>obj\profile7</RestoreOutputPath> to csproj file consequent commands
dotnet restore myproj.sln
dotnet build myproj.sln
produce build errors
obj\project.assets.json' not found. Run a NuGet package restore to generate this file.
How to tell MSBuild to get nuget assets from this obj\Profile7 path during the build command?
The restore output path needs to be the same as MSBuildProjectExtensionsPath so that the nuget generated props and targets files will be imported by the common props and targets. as well as BaseIntermediateOutputPath will be the default for composing the path to ProjectAssetsFile.
At least for the NuGet imports, it is important that MSBuildProjectExtensionsPath or BaseIntermediateOutputPath is set before the SDK props file is imported.
The simplest way to solve all of these issues is to set BaseIntermediateOutputPath very early in the project so that all components will take its value as a default base path - this is essentially redirecting obj to somewhere else.
This conflicts with the <Project SDK="..."> syntax since there is no way to set properties before the SDK's props file. To work around this, the project can be changed like this:
<Project>
<!-- This needs to be set before Sdk.props -->
<PropertyGroup>
<BaseIntermediateOutputPath>obj\SomeSubDir\</BaseIntermediateOutputPath>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<!-- other content -->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>
An alternative would be to create a Directory.Build.props file that will be automatically imported early enough, but this would apply the value to all projects in the directory and take away the ability to specify the value per project.