Creating a Release drop - msbuild

So, I have used MSBuild but this was years ago.
I want to create a Release build for a solution where once built, it will copy all files into a variable set folder "ReleaseDrop" and zip up the contents.
Before zipping, I want to make sure it copies only the necessary files (i.e no pdb, no sln, no csproj, no .cs files (but .cshtml is allowed) or only certain directories and exclude other directories within a directory.
how can I do this?

This should be a start. It specifies a bunch of files to include in a release, copies them to a directory and zips them. For the zip part I used MSBuild Extension Pack since I have it installed anyway, but you could just as well use a prtable version of 7z or so and incoke it with the Exec task.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks"/>
<!--default values for properties if not passed-->
<PropertyGroup>
<ProjectDir Condition="'$(ProjectDir) == ''">C:\Projects\MyProject</ProjectDir>
<ReleaseDrop Condition="'$(ReleaseDrop) == ''">c:\Projects\MyProject\ReleaseDrop</ReleaseDrop>
</PropertyGroup>
<!--build list of files to copy-->
<ItemGroup>
<SourceFiles Include="$(ProjectDir)\bin\*.exe" Exclude="$(ProjectDir)\bin\*test*.exe"/>
<SourceFiles Include="$(ProjectDir)\bin\*.cshtml" />
</ItemGroup>
<!--copy files-->
<Target Name="CopyFiles">
<MakeDir Directories="$(ReleaseDrop)" />
<Copy SourceFiles="#(SourceFiles)" DestinationFolder="$(ReleaseDrop)" />
</Target>
<!--after files are copied, list them then zip them-->
<Target Name="MakeRelease" DependsOnTargets="CopyFiles">
<ItemGroup>
<ZipFiles Include="$(ReleaseDrop)\*.*"/>
</ItemGroup>
<Zip ZipFileName="$(ReleaseDrop)\release.zip" Files="#(ZipFiles)" WorkingDirectory="$(ReleaseDrop)"/>
</Target>
</Project>
can be invoked like
msbuild <name of project file> /t:MakeRelease /p:ProjectDir=c:\projects

Related

Get a property value out of an MSBuild Task

My scenario is as follows.
I have a .targets file that does some stuff, for example,
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Some UsingTask here -->
<PropertyGroup>
<CustomProperty1>MyFirstValue</CustomProperty1>
<CustomProperty2>MySecondValue</CustomProperty2>
<Target Name="AfterResolveReferences">
<MSBuild Projects="MyProjectLocation\MyProject.csproj" Properties="CustomProperty1=$(CustomProperty1);CustomProperty2=$(CustomProperty2)" Targets="Rebuild" />
<Message Text="CodeGenerated = $(CodeGenerated)" />
</Target>
<Target Name="CodeGeneratedSpecificStuff" Condition="$(CodeGenerated) == 'true'">
<!-- Stuff happens -->
</Target>
<!-- More targets and bits and pieces -->
</Project>
And MyProject.csproj looks like this:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<!-- Some property groups with some properties -->
<!-- Also Compile includes etc as expected in a .csproj file -->
<Target Name="BeforeBuild" DependsOnTargets="GenerateCode">
<ItemGroup Condition="$(CodeGenerated) == 'true'">
<Compile Include="MyGeneratedCode.cs" />
</ItemGroup>
</Target>
<Target Name="GenerateCode">
<MyCodeGenerationCustomTask MyOperationalProperty="$(CustomProperty1)">
<Output TaskParameter="CodeGenerated" PropertyName="CodeGenerated" />
</MyCodeGenerationCustomTask>
</Target>
</Project>
I am trying to bubble the "CodeGenerated" property that comes from my MyCodeGenerationCustomTask in the .csproj file to get out into my .targets file.
So far I've tried:
Passing in the "CodeGenerated" property as empty at the MSBuild call in the .targets file.
Specifying an "Output" element under the MSBuild task (it doesn't work as the MSBuild task doesn't have a "CodeGenerated" property...)
I have also tried setting "Outputs" on both the "BeforeBuild" target and on the "Project" element in the .csproj file to "$(CodeGenerated)" (MSBuild didn't complain about the existence of the Outputs tag on the Project element, so I thought I might as well give it a go), but the value does not bubble up to the .targets file. In the .targets file I did also change the MSBuild task to look more like this:
<MSBuild Projects="MyProjectLocation\MyProject.csproj" Properties="CustomProperty1=$(CustomProperty1);CustomProperty2=$(CustomProperty2)" Targets="Rebuild">
<Output TaskParameter="TargetOutputs" PropertyName="CodeGenerated" />
</MSBuild>
But as I would expect this just contained the list of generated files from the MSBuild task.
Important to note is the code generation is working fine, and the CodeGenerated property within the .csproj file functions correctly and conditionally includes the cs file for compilation.
Is this possible? Am I just hitting my head against a brick wall? Or am I missing some MSBuild magic?
I really want to avoid checking the specific .cs location at every level above for its existence.

Remove Files and Folders Copied From AfterBuild Target

I would like to avoid hard coding the dll and folder names in the AfterClean target, is there a dynamic way to do this? Ideally it would only delete the files and folders created by the Copy in the AfterBuild target.
I tried to simplify this by changing the DestinationFolder to include a subdirectory in the OutputPath. The AfterClean target would only have to remove that subdirectory at this point. However, some of the library's DLLImport paths don't take that subdirectory into consideration which results in a crash.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="AfterBuild">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)..\lib\native\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(NativeLibs)" DestinationFolder="$(OutputPath)\%(RecursiveDir)" />
</Target>
<Target Name="AfterClean">
<Delete Files="$(OutputPath)\LumiAPI.dll" />
<Delete Files="$(OutputPath)\LumiCore.dll" />
<Delete Files="$(OutputPath)\LumiInOpAPI.dll" />
<RemoveDir Directories="$(OutputPath)\SPM" />
<RemoveDir Directories="$(OutputPath)\plugin" />
</Target>
</Project>
Project Structure:
src
ConsumingProject
ConsumingProject.csproj
ConsumingProject.sln
packages
my-project.5.7.0.12
build
lib
native
plugin
VenusDvc.dll
SPM
sSPM_1.bin
LumiAPI.dll
LumiCore.dll
LumiInOpAPI.dll
net45
my-project.5.7.0.12.nupkg
Essentially I want to delete all the files and folders that were copied from the native folder to the output of the project (ie LumiAPI.dll, LumiCore.dll, SPM (folder), eSPM_1.bin, etc). However I want it to be generic enough so that if I add another folder to the native directory, it will delete those folders/files as well.
Use a seperate target which lists input and output files, then use that list in both other targets. Note this uses the DestinationFiles attribute from the Copy task instead of DestinationFolders. And it might print some messages about non-existing directories being passed to RemoveDir because the top directory gets removed already before child directories.
update since you don't want to remove the root output directory as it still has files, figured applying the 'only remove output directory if it's empty' principle for any destination directory is probably the safest way to go. Credits go to the answer here.
<Target Name="GetMyOutputFiles">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)..\lib\native\**\*.*" />
<!--Now add some metadata: output dir and output file-->
<NativeLibs>
<DestinationDir>$(OutputPath)\%(RecursiveDir)</DestinationDir>
<Destination>$(OutputPath)\%(RecursiveDir)%(FileName)%(Extension)</Destination>
</NativeLibs>
</ItemGroup>
</Target>
<Target Name="AfterBuild" DependsOnTargets="GetMyOutputFiles">
<!--Copy one-to-one-->
<Copy SourceFiles="#(NativeLibs)" DestinationFiles="#(NativeLibs->'%(Destination)')" />
</Target>
<Target Name="AfterClean" DependsOnTargets="GetMyOutputFiles">
<Delete Files="#(NativeLibs->'%(Destination)')" />
<!--Find number of files left in each destination directory-->
<ItemGroup>
<NativeLibs>
<NumFiles>0</NumFiles>
<!--Condition is to avoid errors when e.g. running this target multiple times-->
<NumFiles Condition="Exists(%(DestinationDir))">$([System.IO.Directory]::GetFiles("%(DestinationDir)", "*", System.IO.SearchOption.AllDirectories).get_Length())</NumFiles>
</NativeLibs>
</ItemGroup>
<!--Only remove empty directories, use 'Distinct' to skip duplicate directories-->
<RemoveDir Directories="#(NativeLibs->'%(DestinationDir)'->Distinct())" Condition="%(NumFiles)=='0'" />
</Target>

Copy files kept on local machine onto a shared location on a remote machine using MSBuild

I have created a build file using MSBuild which builds solution and keep all the data into a folder. Now i want to copy all the data to a remote machine accessed via shared folder.
<PropertyGroup>
<PublishDir>\\remoteMachineName\QA</PublishDir>
<ServiceLocationQA>remoteMachineName\QA</ServiceLocationQA>
<MachineName>remoteMachineName</MachineName>
</PropertyGroup>
<ItemGroup>
<Source Include=".\buildartifacts\**\*.*"/>
<ServiceFilesToDeploy Include=".\buildartifacts\**\*.*" />
</ItemGroup>
<Copy SourceFiles=".\buildartifacts\**\*.*"
DestinationFiles="#(ServiceFilesToDeploy->'$(PublishDir)\%(RecursiveDir)%(Filename)%(Extension)')"
ContinueOnError="false" />
After executing the the build script, i get following error:
"DestinationFiles" refers to 48 item(s), and "SourceFiles" refers to 1 item(s). They must have the same number of items."
I just want to copy files kept on local machine onto a shared location on a remote machine using MSBuild. Please help
You need to iterate the files:
<Copy SourceFiles="%(ServiceFilesToDeploy.Identity)"
DestinationFiles="#(ServiceFilesToDeploy->'$(PublishDir)\%(RecursiveDir)%(Filename)%(Extension)')"
ContinueOnError="false" />
That way the copy task will be called for each file in ServiceFilesToDeploy.
You dont even need to do batching as the copy task understands itemgroups:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Test">
<PropertyGroup>
<PublishDir>\\remotemachine\test</PublishDir>
<BuildArtifacts>.\buildartifacts</BuildArtifacts>
</PropertyGroup>
<ItemGroup>
<Source Include="$(BuildArtifacts)\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(Source)"
DestinationFolder="$(PublishDir)\%(RecursiveDir)"/>
</Target>
</Project>

Calling MSbuild Publish

Is it possible to call MSbuild publish during build or in pre-buildevent or in post-buildevent? I'm trying to publish two of the web projects from a solution. I'm using file system publishing.Requirement here is , building solution should take care of publish those two web projects. Can any one please suggest ?
I wouldn't put too much deploy logic in a post-build event. It becomes "fragile".
Create a separate .msbuild file, and do the "extra" logic in it, instead of messing too much with the .csproj file.
Below is a basic example.
Place the xml below in an file call "MyBuildAndDeploy.msbuild", put it in the same folder as your .sln (or .csproj) file, and then use
msbuild.exe "MyBuildAndDeploy.msbuild" from the command line.
So below is a basic example of building the primary solution and then copying the files somewhere.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapper">
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
<BuildResultsRootFolder>$(WorkingCheckout)\..\BuildResults</BuildResultsRootFolder>
</PropertyGroup>
<Target Name="AllTargetsWrapper">
<CallTarget Targets="BuildSolution" />
<CallTarget Targets="CopyBuildOutputFiles" />
</Target>
<Target Name="BuildSolution">
<MSBuild Projects="$(WorkingCheckout)\MySuperCoolSolution.sln" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputsItemName"/>
</MSBuild>
<Message Text="BuildSolution completed" />
</Target>
<Target Name="CopyBuildOutputFiles">
<MakeDir Directories="$(BuildResultsRootFolder)\$(Configuration)" Condition="!Exists('$(BuildResultsRootFolder)\$(Configuration)\')"/>
<ItemGroup>
<BuildOutputFilesExcludeFiles Include="$(WorkingCheckout)\**\*.doesnotexist" />
<BuildOutputFilesIncludeFiles Include="$(WorkingCheckout)\**\*.dll" Exclude="#(BuildOutputFilesExcludeFiles)" />
<BuildOutputFilesIncludeFiles Include="$(WorkingCheckout)\**\*.exe" Exclude="#(BuildOutputFilesExcludeFiles)" />
<BuildOutputFilesIncludeFiles Include="$(WorkingCheckout)\**\*.config" Exclude="#(BuildOutputFilesExcludeFiles)" />
<BuildOutputFilesIncludeFiles Include="$(WorkingCheckout)\**\*.pdb" Exclude="#(BuildOutputFilesExcludeFiles)" />
</ItemGroup>
<Copy SourceFiles="#(BuildOutputFilesIncludeFiles)"
DestinationFolder="$(BuildResultsRootFolder)\$(Configuration)\"/>
</Target>
</Project>

Additional paths in msbuild script

How to specify additional assembly reference paths for the MSBuild tasks?
I have following script so far, but can't figure out how to specify additional search paths.
<ItemGroup>
<ProjectsToBuild Include="..\Main\Main.sln" />
</ItemGroup>
<!-- The follwing paths should be added to reference search paths for the build tasks -->
<ItemGroup>
<MyAddRefPath Include="$(MSBuildProjectDirectory)\..\..\Build\Lib1" />
<MyAddRefPath Include="$(MSBuildProjectDirectory)\..\..\Build\Lib2" />
</ItemGroup>
<MSBuild
Projects="#(ProjectsToBuild)"
Properties="Configuration=Debug;OutputPath=$(BuildOutputPath)">
</MSBuild>
UPDATE:
Please show one complete working script which invokes original project, such as an SLN with multiple additional reference paths.
No suggestions on how to improve the project structure please.
I know how to build a good structure, but now it's the task of building an existing piece of crap.
I have finaly figured out how to do it:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectsToBuild Include="ConsoleApplication1\ConsoleApplication1.csproj" />
</ItemGroup>
<ItemGroup>
<AdditionalReferencePaths Include="..\Build\ClassLibrary1" />
<AdditionalReferencePaths Include="..\Build\ClassLibrary2" />
</ItemGroup>
<PropertyGroup>
<BuildOutputPath>..\Build\ConsoleApplication1</BuildOutputPath>
</PropertyGroup>
<Target Name="MainBuild">
<PropertyGroup>
<AdditionalReferencePathsProp>#(AdditionalReferencePaths)</AdditionalReferencePathsProp>
</PropertyGroup>
<MSBuild
Projects="ConsoleApplication1\ConsoleApplication1.csproj"
Properties="ReferencePath=$(AdditionalReferencePathsProp);OutputPath=$(BuildOutputPath)"
>
</MSBuild>
</Target>
The property you want to modify is AssemblySearchPaths. See the ResolveAssemblyReference task more information.
<Target Name="AddToSearchPaths">
<CreateProperty Value="x:\path\to\assemblies;$(AssemblySearchPaths)">
<Output PropertyName="AssemblySearchPaths" TaskParameter="Value" />
</CreateProperty>
</Target>
Making use of item groups, as in your example, it would look like:
<Target Name="AddToSearchPaths">
<CreateProperty Value="#(MyAddRefPath);$(AssemblySearchPaths)">
<Output PropertyName="AssemblySearchPaths" TaskParameter="Value" />
</CreateProperty>
</Target>
Looking in %WINDIR%\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets, you can see that the ResolveAssemblyReference Task is executed as part of the ResolveAssemblyReferences target. Thus, you want the newly added target to modify the AssemblySearchPaths property before ResolveAssemblyReferences is executed.
You've stated that you want to be able to modify the assembly search paths without modifying the project files directly. In order to accomplish that requirement you need to set an environment variable that will override the AssemblySearchPaths. With this technique you will need to provide every assembly reference path used by all the projects in the solutions. (Modifying the projects or copies of the projects would be easier. See final comments.)
One technique is to create a batch file that runs your script at sets the environment variable:
set AssemblySearchPaths="C:\Tacos;C:\Burritos;C:\Chalupas"
msbuild whatever.msbuild
Another way is to define a PropertyGroup in your custom msbuild file (otherwise known as the "hook" needed to make this work):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectsToBuild Include="..\Main\Main.sln" />
</ItemGroup>
<PropertyGroup>
<AssemblySearchPaths>$(MSBuildProjectDirectory)\..\..\Build\Lib1;$(MSBuildProjectDirectory)\..\..\Build\Lib2</AssemblySearchPaths>
</PropertyGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectsToBuild)" Properties="AssemblySearchPaths=$(AssemblySearchPaths);Configuration=Debug;OutputPath=$(OutputPath)" />
</Target>
</Project>
Now if it were me, and for whatever unexplained reason I couldn't modify the project files to include the updated references that I am going to build with, I would make copies of the project files, load them into the IDE, and correct the references in my copies. Synching the projects becomes a simple diff/merge operation which is automatic with modern tools like mercurial (heck I'm sure clearcase could manage it too).
...and remember that you don't need to use a target for this, you can use project-scoped properties or items, as...
<ItemGroup>
<MyAddRefPath Include="$(MSBuildProjectDirectory)\..\..\Build\Lib1" />
<MyAddRefPath Include="$(MSBuildProjectDirectory)\..\..\Build\Lib2" />
</ItemGroup>
<PropertyGroup>
<MyAddRefPath>$(MSBuildProjectDirectory)\..\..\Build\Lib3</MyAddRefPath>
<!-- add in the property path -->
<AssemblySearchPaths>$(MyAddRefPath);$(AssemblySearchPaths)</AssemblySearchPaths>
<!-- add in the item paths -->
<AssemblySearchPaths>#(MyAddRefPath);$(AssemblySearchPaths)</AssemblySearchPaths>
</PropertyGroup>
...and if you do need to do this in a target to pick up paths from a dynamically populated item group, use inline properties, not the CreateProperty task (if you are not stuck in v2.0)
<Target Name="AddToSearchPaths">
<PropertyGroup>
<!-- add in the item paths -->
<AssemblySearchPaths>#(MyDynamicAddRefPath);$(AssemblySearchPaths)</AssemblySearchPaths>
</PropertyGroup>
</Target>