Msbuild for copying assemblies to a few folders of applications? - c#

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?

Related

Nuget package.targets not able to find files in copy task

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>

Multiple binaries from a single DotNet Core project configuration file

I have code for a bunch of CLI utilities made to test/showcase a network library. The library is compiled into an assembly - DotNet Core DLL.
I have several CLI examples showing how to use the library, for example, one search is using paging functionality and another returns everything etc. Basically, each is a short standalone CLI program.
I have CS source files and csproj file targeting dotnet core. Below is the configuration:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Reference Include="mylib>
<HintPath>../../bin/Debug/netstandard2.0/publish/mylib.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
I want to have one executable for each source file e.g. PGSample.cs will get compiled into PGSample.exe etc. How would I about achieving this?
I'm afraid you can have only one output per csproj, but there are some tricks to manage multiple projects easier.
You can put common build settings into a file named Directory.build.props in the root project folder, e.g.:
<Project>
<PropertyGroup>
<Authors>Your name here</Authors>
<Company>Your company</Company>
<Product>The project name</Product>
<Version>1.6.18</Version>
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
<!-- e.g. bin/Release_net48/foo_cli/ -->
<OutputPath>$(SolutionDir)bin\$(Configuration)_$(TargetFramework)\$(MSBuildProjectName)</OutputPath>
<TargetFrameworks>net48</TargetFrameworks>
</PropertyGroup>
</Project>
Then, add one subdirectory and minimal csproj file per output, e.g.
libfoo/libfoo.csproj: <Project Sdk="Microsoft.NET.Sdk" /> (really, that's it!)
foo_cli/foo_cli.csproj:
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><OutputType>Exe</OutputType></PropertyGroup>
<ItemGroup><ProjectReference Include="..\libfoo\libfoo.csproj" /></ItemGroup>
</Project>
Iff you have only one library and a lot of executables, you might even add your executable settings to the Directory.build.props:
[..]
<PropertyGroup Condition=" $(MSBuildProjectName) != 'libfoo'">
<OutputType>exe</OutputType>
</PropertyGroup>
<ItemGroup Condition=" $(MSBuildProjectName) != 'libfoo'">
<ProjectReference Include="..\libfoo\libfoo.csproj" />
</ItemGroup>

How to get nuget restore in TFS build

I can't make it work TFS build. It is nuget restore issue. Nuget is not restoring reference dll files.
Here is belwo my build configuration. Please advise me how I can make this works.
As per this blog post on Nuget's website you can use the command line you mentioned, but it has to be part of a custom target using a Build.proj file.
You need to add a Build.proj and put this as the contents:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutDir Condition=" '$(OutDir)'=='' ">$(MSBuildThisFileDirectory)bin\</OutDir>
<Configuration Condition=" '$(Configuration)'=='' ">Release</Configuration>
<SourceHome Condition=" '$(SourceHome)'=='' ">$(MSBuildThisFileDirectory)src\</SourceHome>
<ToolsHome Condition=" '$(ToolsHome)'=='' ">$(MSBuildThisFileDirectory)tools\</ToolsHome>
</PropertyGroup>
<ItemGroup>
<Solution Include="$(SourceHome)*.sln">
<AdditionalProperties>OutDir=$(OutDir);Configuration=$(Configuration)</AdditionalProperties>
</Solution>
</ItemGroup>
<Target Name="RestorePackages">
<Exec Command=""$(ToolsHome)NuGet\NuGet.exe" restore "%(Solution.Identity)"" />
</Target>
<Target Name="Clean">
<MSBuild Targets="Clean"
Projects="#(Solution)" />
</Target>
<Target Name="Build" DependsOnTargets="RestorePackages">
<MSBuild Targets="Build"
Projects="#(Solution)" />
</Target>
<Target Name="Rebuild" DependsOnTargets="RestorePackages">
<MSBuild Targets="Rebuild"
Projects="#(Solution)" />
</Target>
</Project>
Alternatively, you could call it from a custom Pre-Build Script.
Or, customise the XAML template and add a Foreach loop to invoke:
nuget.exe restore path\to\solution.sln
on each solution in the build definition.
Here are some steps (described here which was mentioned in Dave's post) you need to follow in order to have these NuGet packages restored during the VSO (TFS) build process.
Add following items to the solution. (Content of the nuget.config and .tfignore file can be found here)
Add one build.proj file under the root path of the solution folder. (Content of the build.proj file can be found here)
Create one folder named tools under the root path of the solution folder. Create NuGet sub-folder under tools folder, download and save nuget.exe under tools\NuGet path.
Check in nuget.config, .tfignore, build.proj and tools\NuGet\nuget.exe into TFS version control.
Modify the build definition to choose to build the build.proj file.
Then you will have NuGet packages restored successfully during the TFS build process.

adding extra files to published MVC API project

I am trying to add an extra XML file to a publishing process. I have a MVC API project which also has another project (V1.0) for controllers. We are using the self documenting help functionality which creates the .XML files for each project. When building on the local machine it all works but when publishing (with wizard) it will not include this file.
I have been trying to update the publish profile (.pubxml) file as described here:
http://www.asp.net/mvc/tutorials/deployment/visual-studio-web-deployment/deploying-extra-files
but without success. I can see the following is happening:
I do a clean to ensure nothing hanging around.
I publish with wizard
I can see in apiproject\bin\ there are all the files including the apiprojectv1 xml and dll files
I can see in apiproject\obj\x86\Release\AspnetCompileMerge\Source\bin it has the apiprojectv1 dll but not the xml file
I can see the same as above in apiprojet\obj\x86\Release\AspnetCompileMerge\TempBuildDir\bin
I can see the same as above in apiproject\obj\x86\Release\Package\PackageTmp\bin
I am not sure why the file is not being copied across. This is my full pubxml file:
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit http://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<publishUrl>\\myserver\wwwroot\apiproject</publishUrl>
<DeleteExistingFiles>False</DeleteExistingFiles>
</PropertyGroup>
<Target Name="CustomCollectFiles">
<ItemGroup>
<_CustomFiles Include="..\bin\apiprojectv1.XML" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>%(RecursiveDir)%(Filename)%(Extension) </DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
</Project>
EDIT
I had forgot one major part, to put the below at the bottom of the pubxml file:
<PropertyGroup>
<CopyAllFilesToSingleFolderForPackageDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForPackageDependsOn>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
I do not get the file, but now get an error regarding the file not being found, (which I can now debug).
I had missed two things:
The second property group to actually tell it to perform the action.
The path was not right, had to use project directory path
Now looks like this and works:
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit http://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<publishUrl>\\myserver\wwwroot\apiproject</publishUrl>
<DeleteExistingFiles>False</DeleteExistingFiles>
</PropertyGroup>
<Target Name="CustomCollectFiles">
<ItemGroup>
<_CustomFiles Include="$(MSBuildProjectDirectory)\bin\apiprojectv1.XML" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension) </DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
<PropertyGroup>
<CopyAllFilesToSingleFolderForPackageDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForPackageDependsOn>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
</Project>

MS Build Import Question Path

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.

Categories