Background: I have a library that's meant to be used with a C# game engine. That game engine has an editor that picks up plugins via a Plugins/ directory of the project (is not included in the build output). In order to make life easy for users I want to package my library and the associated editor plugins packaged and deployed together in a single nuget file. This way users don't have to manually manage the versions of two different sets of files.
Problem:
Nuget no longer has the ability to copy content files over by default. Based on searching the only way to accomplish this is with tasks set up in an msbuild package.targets file. So I have the following in my csproj
<ItemGroup>
<Content Include="../Parme.Frb.Example/Plugins/**/*">
<Pack>true</Pack>
<PackagePath>content\Plugins</PackagePath>
</Content>
<Content Include="Parme.Frb.targets">
<Pack>true</Pack>
<PackagePath>build</PackagePath>
</Content>
</ItemGroup>
The Parme.Frb.Example/Plugins folder contains the plugin files I want to include in the nuget file. My Parme.Frb.targets msbuild file contains:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<GluePluginFiles>$(MSBuildThisFileDirectory)..\content\Plugins\**\*</GluePluginFiles>
</PropertyGroup>
<Target Name="CopyGluePlugin" BeforeTargets="PreBuildEvent">
<Copy SourceFiles="$(GluePluginFiles)"
DestinationFolder="$(ProjectDir)Plugins\%(GluePluginFiles.RecursiveDir)"
SkipUnchangedFiles="true" />
</Target>
</Project>
The intention is that it will copy all files from the <nuget-cache>/<package>/content/Plugins directory and recursively copy it tho the project's Plugins/ directory.
When I look at the built nuget file I see all the included content files
So I load this nuget file into a blank project and run a build, which produces the following errors:
Build started 4/12/2021 4:56:42 PM.
Logging verbosity is set to: Normal. 1>Project "C:\Users\me\RiderProjects\NugetTest\NugetTest\NugetTest.csproj" on node 1 (build target(s)).
1>C:\Users\me\.nuget\packages\parme.frb\0.8.3-test10\build\Parme.Frb.targets(7,9): error MSB3030: Could not copy the file "C:\Users\me\.nuget\packages\parme.frb\0.8.3-test10\build\..\content\Plugins\**\*" because it was not found.
1>Done Building Project "C:\Users\me\RiderProjects\NugetTest\NugetTest\NugetTest.csproj" (build target(s)) -- FAILED.
Build FAILED.
"C:\Users\me\RiderProjects\NugetTest\NugetTest\NugetTest.csproj" (build target) (1) ->
(CopyGluePlugin target) ->
C:\Users\me\.nuget\packages\parme.frb\0.8.3-test10\build\Parme.Frb.targets(7,9): error MSB3030: Could not copy the file "C:\Users\me\.nuget\packages\parme.frb\0.8.3-test10\build\..\content\Plugins\**\*" because it was not found.
0 Warning(s)
1 Error(s)
Time Elapsed 00:00:00.33
However, files most definitely do exist in this directory:
PS C:\Users\me> ls C:\Users\me\.nuget\packages\parme.frb\0.8.3-test10\build\..\content\Plugins
Directory: C:\Users\me\.nuget\packages\parme.frb\0.8.3-test10\content\Plugins
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 4/12/2021 4:56 PM Parme.Frb.GluePlugin
I've tried reorganizing the nuget package to remove the .., I've tried removing the GluePlugins. part of the RecursiveDir (which most tasks don't seem to have but that causes a different error`, etc.. I've tried a ton of stuff and can't get this to work.
Does anyone have any idea on how I can get this copying, without manually specifying each file?
I don't know specifically why this works, but after a lot of trial and error I got it working via:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<GluePluginFile Include="$(MSBuildThisFileDirectory)..\content\Plugins\**\*" />
</ItemGroup>
<Target Name="CopyGluePlugin" BeforeTargets="PreBuildEvent">
<Message Importance="normal" Text="Copying Glue plugin files" />
<Copy SourceFiles="#(GluePluginFile)"
DestinationFolder="$(ProjectDir)\Plugins\%(RecursiveDir)"
SkipUnchangedFiles="true" />
</Target>
</Project>
Related
I am using Visual Studio 2019 and creating NuGet packages successfully with this method:
https://learn.microsoft.com/en-us/nuget/quickstart/create-and-publish-a-package-using-visual-studio?tabs=netcore-cli
All going well, but there are some settings (.json) files contained within a directory PageSettings/
When I publish my NuGet package and then install it into a new project, this directory appears in VS as a linked item (see pic). So as a user I can access the files, but they don't "exist" when the project is run.
This means if I run this project without physically copying and adding these files I get ArgumentException: The directory name 'Path-To-project\pagesettings' does not exist. (Parameter 'Path')
I can see why this is happening, but can't work out how to change it, or if it is possible.
The article linked above suggests adding code to the csproj file like:
<ItemGroup>
<Content Include="readme.txt">
<Pack>true</Pack>
<PackagePath>\</PackagePath>
</Content>
</ItemGroup>
But that doesn't work and in fact seems unnecessary since the Pack command is including my files, just not creating them properly when installing.
Also - it would be extremely handy if there was a way to tell VS to prompt whether to install this file or not. Since it is settings, if a user changes the settings and then later installs an updated version of my NuGet package I would not want it to overwrite their customised settings... perhaps this is why the link design happens... if so, if someone could confirm?
Actually, you should create a .props file in your nuget package.
1) create a file called <package_id>.props file in your nuget project.
Like this:
Note: if your created nuget package called test.1.0.0.nupkg, the file should be named as test.props so that it will work.
2) add these in the test.props file:
<Project>
<Target Name="CopyFiles" BeforeTargets="Build">
<ItemGroup>
<File Include="$(MSBuildThisFileDirectory)..\Pagesettings\*.*"></File>
</ItemGroup>
<Copy SourceFiles="#(File)" DestinationFolder="$(ProjectDir)Pagesettings"></Copy>
</Target>
</Project>
3) add these in xxx.csproj file:
<ItemGroup>
<None Include="Pagesettings\*.*(the json files' path of your nuget project)" Pack="true" PackagePath="Pagesettings">
</None>
<None Include="build\*.*" Pack="true" PackagePath="build"></None>
</ItemGroup>
then reapck your project.
4) then clean your nuget caches or delete all files under C:\Users\xxx(current user)\.nuget\packages.
5) when you insall this new version of the nuget package, please build your main project again to run the target to generate the files.
Besides, there is also a similar issue about this.
I'm busy moving my code from .Net Framework libraries to .netstandard2.0 libraries. So far it's going pretty well, but now i'm stuck with the in the .csproj file.
The existing project file has this defined
<Target Name="Rebuild">
<Exec Command="echo Now Rebuilding the package" />
</Target>
the actual command executes an exe that generates a bunch of xml classes based on an xsd.
I cannot get this to work in a .netstandard2.0 project?
I've searched everywhere but i cannot find a reason for this not working...
I suspect that in your specific instance, the Rebuild target will be overwritten by the sdk targets that are implicitly imported after your code. If you want to overwrite SDK-provided tasks, you need to change to explicit SDK imports (instead of <Project Sdk="...">):
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<!-- other project content -->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<Target Name="Build">
<!-- overwrite Build target here -->
</Target>
<Target Name="Rebuild">
<!-- overwrite Rebuild target here -->
</Target>
</Project>
The Exec target is supported though the echo command may or may not work depending on the platform you are running it on (since echo may be just a built-in command of the shell but no executable that can be run).
Make sure that:
The command starts with the path to an executable that is found on the PATH or is specified absolute or relative to the csproj file being built.
The target is actually executed. E.g. some programs could use /t:Clean;Build instead of /t:Rebuild.
I'm thinking that the final result is going to be "it can't be that easily done", but just seems like it should be. I have a personal project I am working on. I'd hate to have to manually (or even in script) change versions, company, copyright, and all that on ALL the assembly.cs files and would like all that to be either in a script or in a file I can change (so the script stays the same mostly) when I want to update the version. But it seems like MSBuild is mostly a "build as is specified in Visual Studio". I'd just hate to have all that history of these files where I change just the version and possibly even make a mistake as this project will continue to get bigger and bigger. I'd like to just be able to add a new project to Visual studio and have whatever command line in my powershell script just say "compile this, but give it this company name and this file version instead of whatever is listed in the code file".
Google has NOT proven fruitful in this. I've even found it difficult to build my files to a specific folder. I've had to so far make sure all my projects are 2 folders deep and was able to say to build them at ....\, but I would like to be able to change that randomly if I like and have them built elsewhere if I so desire.
Is MSBuild perhaps not the way to go? Is there someway else to build visual studio that would be better from command line? Eventually I also want to auto build the install with wix and be able to match its version with the binary versions.
thank you
Since csproj is xml, you can use XmlUpdate "helpers" to modify the values inside the csproj file before you do your build.
For other files, you can use some other Tasks to do the job.
Here is one helpful target:
http://msbuildtasks.tigris.org/ and/or https://github.com/loresoft/msbuildtasks has the ( FileUpdate (and SvnVersion task if that is your Source-Control) ) tasks.
<Target Name="BeforeBuild_VersionTagIt_Target">
<ItemGroup>
<AssemblyInfoFiles Include="$(ProjectDir)\**\*AssemblyInfo.cs" />
</ItemGroup>
<!--
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="$(SVNToolPath)">
<Output TaskParameter="Revision" PropertyName="MyRevision" />
</SvnVersion>
-->
<PropertyGroup>
<MyRevision>9999</MyRevision>
</PropertyGroup>
<FileUpdate Files="#(AssemblyInfoFiles)"
Regex="AssemblyFileVersion\("(\d+)\.(\d+)\.(\d+)\.(\d+)"
ReplacementText="AssemblyFileVersion("$1.$2.$3.$(MyRevision)" />
</Target>
Below is an example of manipulating the csproj(xml).
How to add a linked file to a csproj file with MSBuild. (3.5 Framework)
But basically, when you build, you can put all the repetative stuff in a msbuild definition file (usually with the extension .proj or .msbuild)...and call msbuild.exe MyFile.proj.
Inside the .proj file, you will reference your .sln file.
For example:
$(WorkingCheckout) would be a variable (not defined here)...that has the directory where you got a copy of hte code from your source-control.
<Target Name="BuildIt" >
<MSBuild Projects="$(WorkingCheckout)\MySolution.sln" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputsItemName"></Output>
</MSBuild>
<Message Text="BuildItUp completed" />
</Target>
So below is the more complete example.
You would save this as "MyBuild.proj" and then call
"msbuild.exe" "MyBuild.proj".
Start .proj code. (Note, I did not import the libraries for the FileUpdate Task)
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="BeforeBuild_VersionTagIt_Target" />
<CallTarget Targets="BuildItUp" />
</Target>
<Target Name="BuildItUp" >
<MSBuild Projects="$(WorkingCheckout)\MySolution.sln" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputsItemName"></Output>
</MSBuild>
<Message Text="BuildItUp completed" />
</Target>
<Target Name="BeforeBuild_VersionTagIt_Target">
<ItemGroup>
<AssemblyInfoFiles Include="$(ProjectDir)\**\*AssemblyInfo.cs" />
</ItemGroup>
<!--
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="$(SVNToolPath)">
<Output TaskParameter="Revision" PropertyName="MyRevision" />
</SvnVersion>
-->
<PropertyGroup>
<MyRevision>9999</MyRevision>
</PropertyGroup>
<FileUpdate Files="#(AssemblyInfoFiles)"
Regex="AssemblyFileVersion\("(\d+)\.(\d+)\.(\d+)\.(\d+)"
ReplacementText="AssemblyFileVersion("$1.$2.$3.$(MyRevision)" />
</Target>
</Project>
To enhance the above, you would create a new target that would run before "BeforeBuild_VersionTagIt_Target", that would pull your code from source-control and put it in the $(WorkingCheckout) folder.
The basic steps would then be: 1. Checkout code from Source-Control. 2. Run the targets that alter the AssemblyVersion (and whatever else you want to manipulate) and 3. Build the .sln file.
That's the basics of a .proj file. You can do much more. Usually by using helper libraries that already exists.
I have a VS solution that contains a few applications and public APIs to be published along with shared libraries. I have some shallow experience in crafting msbuild file like this one.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TPath>C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks</TPath>
<ETPath>C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\</ETPath>
</PropertyGroup>
<ItemDefinitionGroup />
<ItemGroup>
<SolutionFile Include="MyApplications.sln" />
</ItemGroup>
<Target Name="Build" Outputs="#(CollectedBuildOutput)">
<MSBuild Projects="#(SolutionFile)" Targets="Rebuild" BuildInParallel="True"
Properties="BuildingSolutionFile=true; Configuration=$(Configuration); Platform=$(Platform); TargetFrameworkVersion=$(TargetFrameworkVersion); WarningLevel=3"
SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)">
<Output TaskParameter="TargetOutputs" ItemName="CollectedBuildOutput"/>
</MSBuild>
</Target>
</Project>
Then I run
C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe MyProjects.msbuild /p:outdir=C:\VSProjectsRelease\MyApplications\Release\
So all assemblies will go to the same directory. So far so good. However, if I want to zip files for each application, or harvest files for Wix setup projects, troubles will emerge.
For example, in MyApplications.sln, I have 10 projects, 3 of which are applications say AppA, AppB and AppC.
I would like to run a single msbuild file which will create 3 folders of applications, and have assemblies copied to there without explicitly defining dependencies since Sln and csproj files already have the knowledge. And I would want msbuild will build each project only once. How to do this?
Quick question about MSBuild. I have the following MSBuild file in directory d:\MyDirectory
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<Import Project="d:\MyDirectory\BuildTest\BuildTest\BuildTest.csproj"></Import>
</Project>
When I run this the build fails because BuildTest.csproj can't find the .cs source files and it seems to be looking in d:\MyDirectory. I was expecting the working directory to be set to that of the BuildTest.csproj and thus be able to resolve the references. What am I doing wrong?
I think ,the import element is used to import other MSBuild projects into this one (see here).
If you want to specify C# projects to build, you should do it like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<ItemGroup>
<ProjectReferences Include="d:\MyDirectory\BuildTest\BuildTest\BuildTest.csproj" />
<!--more-->
</ItemGroup>
Then if you want to build all referenced projects, you can for example do this:
<Target Name="BuildAllProjects">
<MSBuild ContinueOnError="False" Projects="#(ProjectReferences)"
Targets="Clean;ReBuild">
</MSBuild>
</Target>
Remember though, that this is just 1 of many ways of doing things with MSBuild. There are a lot of good articles on MSDN on this topic, should you require more customization.