Make visual studio build when output won't change? - visual-studio

In my project I have a json file I use for configuration that I have git set to ignore. When the repository is first cloned, the configuration file that is part of the project and that is copied to the output directory doesn't exist. I've gotten this to work using tasks in the 'BeforeBuild' target in the project that will copy the sample file to the actual config file if it doesn't exist.
<Target Name="BeforeBuild">
<ItemGroup>
<MySourceFiles Include="Configuration.sample.json" />
</ItemGroup>
<ItemGroup>
<MyDestinationFiles Include="Configuration.json" />
</ItemGroup>
<Message Importance="high" Condition="!Exists('#(MyDestinationFiles)')"
Text="Copying #(MySourceFiles) to #(MyDestinationFiles)" />
<Copy Condition="!Exists('#(MyDestinationFiles)')"
SourceFiles="#(MySourceFiles)"
DestinationFiles="#(MyDestinationFiles)" />
</Target>
So if I build the project, then delete the configuration file and do a build, nothing happens because no changes have been made that would change the outputs I think. Is there a way to change the project file so that a build will be flagged as necessary? It shouldn't come up very often and I can always do a 'Clean' or 'Rebuild' manually, but it's nagging at me since I'm just starting to learn MSBuild files.

From the documentation on a Target's Outputs attribute:
The files that form outputs into this target. Multiple files are
separated by semicolons. The timestamps of the files will be compared
with the timestamps of files in Inputs to determine whether the Target
is up to date
So if you add the paths to the outputfiles created by your Beforebuild target to it's Outputs attribute, at the start of every build msbuild will check if those files exist and if not it will start a build because now the project is considered to not be up-to-date anymore. In practice use:
<Target Name="BeforeBuild" Outputs="#(MyDestinationFiles)">

Related

Copy all TypeScript files to output directory after each build

I have a bunch of TypeScript files in my project, and I want them all to be copied to the output directory on each build, preserving their structure. Here's what I've tried, but it does not work:
<ItemGroup>
<TypeScriptFiles Include="Scripts\*.ts" />
</ItemGroup>
<Target Name="CopyTypeScriptsToOutput">
<Copy SourceFiles="#(TypeScriptFiles)" DestinationFolder="$(OutputDir)\Scripts" />
</Target>
I've also used Include="Scripts\**\*.ts" but no success. What could be wrong?
I've also used Include="Scripts***.ts" but no success. What could be
wrong?
The contents of the Include are the relative path of the files in your project.
The main problem is that you did not specify how the target runs. If you only use the Build UI to build your project, the target will not run. You should add build dependencies to the target, usually like BeforeTargets and AfterTargets, so that you run the target at build time.
Second, you have a problem with the properties of the target generated path like $(OutputDir). I tried to test this property in vs2015,2017,2019, MSBuild does not have this property by default. If the property is not defined by yourself, the value will never be reached. So I recommend that you can use $(OutputPath) and $(OutputDir).
In addition, please place TypeScriptFiles in the target to prevent confusion when the csproj file is first loaded. If you define it globally, it will be recognized by the system and mapped to the project again.
Sample
This is the target that I successfully completed.
<Target Name="CopyTypeScriptsToOutput" AfterTargets="Build">
<ItemGroup>
<TypeScriptFiles Include="Scripts\*.ts" />
</ItemGroup>
<Copy SourceFiles="#(TypeScriptFiles)" DestinationFolder="$(OutputPath)\Scripts" />
</Target>
Hope it could help you.
in my package.json, I ma using cpx to copy files in the dist folder
"scripts": {
"copy-media": "$(npm bin)/cpx media/**/*.* dist/media",
"build": "tsc && yarn copy-media",
},
So it builds first tsc, and then copy all files from folder media to dist

Adding umbraco folders to build with MsBuild

If I use msbuild to build my project, all the folders not included in my solution are not deployed. Is there a way of deploying the umbraco and umbraco_client folders using msbuild?
I have tried using Targets like:
https://gist.github.com/aaronpowell/6695293
How can we include the files created by ajaxmin in the msdeploy package created by MSBuild
https://blog.samstephens.co.nz/2010/10/18/msbuild-including-extra-files-multiple-builds/
But hey are not being copied to the output folder. Am I missing anything?
You can use a msbuild target(run after the build ends) in which it calls the msbuild copy task to copy necessary files or folders to output folder. Use AfterTargets="build" to let the target run after the build.
A target script which works in my machine looks like this:
<Target Name="Copyumbraco" AfterTargets="build">
<ItemGroup>
<UmbracoFiles Include="$(ProjectDir)**\umbraco\**\*" />
<Umbraco_ClientFiles Include="$(ProjectDir)**\umbraco_client\**\*" />
</ItemGroup>
<Copy SourceFiles="#(UmbracoFiles)" DestinationFolder="$(OutputPath)\%(RecursiveDir)"/>
<Copy SourceFiles="#(Umbraco_ClientFiles)" DestinationFolder="$(OutputPath)\%(RecursiveDir)"/>
</Target>
Using $(ProjectDir) property to define the path, so Msbuild can find those two folders if they are in project folder as you mentioned in comment.
The \%(RecursiveDir) set the msbuild copy task to copy the files to destination path with original folder structure. If what you want to just copy all files to Output folder, you don't need to set it, then the script should be:
<Target Name="Copyumbraco" AfterTargets="build">
<ItemGroup>
<UmbracoFiles Include="$(ProjectDir)**\umbraco\**\*" />
<Umbraco_ClientFiles Include="$(ProjectDir)**\umbraco_client\**\*" />
</ItemGroup>
<Copy SourceFiles="#(UmbracoFiles)" DestinationFolder="$(OutputPath)"/>
<Copy SourceFiles="#(Umbraco_ClientFiles)" DestinationFolder="$(OutputPath)"/>
</Target>
Add the target script into the your project's project file(xx.csproj), make sure you place the script in the format below, then it can work when you use msbuild to build the project.
<Project Sdk="Microsoft.NET.Sdk.Web">
...
<Target Name="Copyumbraco" AfterTargets="build">
...
</Target>
</Project>
In addition:
For normal projects like console app, class library, the $(OutputPath) represents the output path. But for web site project, we can use $(WebProjectOutputDir) , hint from Mario!

MSBuild missing output files in AfterBuild when solution is cleaned

I'm sure there is something small that I'm missing. Here's the problem:
I have a solution that has multiple projects which after each build will be zipped. Here is an example of the zip creation in one project (they are pretty much identical in others):
<ItemGroup>
<CopySourceFiles Include="$(OutDir)\**\*.*" Exclude="$(OutDir)\**\*.pdb;$(OutDir)\*.mdf;$(OutDir)\*.ldf;$(OutDir)\*.vshost.*" />
</ItemGroup>
...
<Target Name="AfterBuild" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<MakeDir Directories="$(OutDir)\..\zip_working" />
<!-- first copy the source files specified in the CorySourceFiles ItemGroup above. -->
<Copy SourceFiles="#(CopySourceFiles)" DestinationFiles="#(CopySourceFiles->'$(OutDir)\..\zip_working\%(RecursiveDir)%(Filename)%(Extension)')" />
<!-- Perform the zip by calling the UsingTask. Make sure the DestinationFiles above and the SourceDirectory below are pointing to the same place -->
<Zip SourceDirectory="$(OutDir)\..\zip_working" OutputFilename="$(OutDir)\..\zip\$(ProjectName).zip" />
<!-- Clean up. -->
<RemoveDir Directories="$(OutDir)\..\zip_working" />
</Target>
There is a final project which has links to the zipped files that it combines into a package. All appears normal, but apparently only when the bin and zip_working folders already exist. I.e. if I clean the solution, delete the bin folders and then rebuild, the final zip that is created in the "zip" folder for each project is empty...
And then the zip files have content only after I build again.
So I'm guessing that during the build process, the AfterBuild target is running before the build output files exist. Does that sound right? I trigger the builds purely from within Visual Studio.
Regardless, how can I ensure that I can run a task on build output files only after they've been created?
Applies to Visual Studio 2013 Update 5 / MSBuild 12.0
If you delete everything in OutDir and then build the project, a top-level (as in, not inside a target) ItemGroup is evaluated before the build even starts. Some info can be found here for example. In other words, before a build and with an empty OutDir $(OutDir)\**\*.* evaluates to nothing and your CopySourceFiles item is empty.
The solution is simply to move the ItemGroup inside of the AfterBuild target. It will then be evaluated after the build and hence gets a proper view on the current files in outDir.

MSBuild ItemGroup empty after Clean

I am seeing some strange behavior with the ItemGroup tag.
My application depends on several DLLs, and I am copying these DLLs as well as the executable to a deploy directory, which is used by NSIS to create an install package from a fresh build. However, I am running into an odd issue with this.
I define my ItemGroup as follows (at the top of the file before I define my build target:
<MyAppFiles Include="$(ProjectRoot)\$(OutputPath)\*.dll;$(ProjectRoot)\$(OutputPath)\MyApp.exe" />
So, this grabs all the DLLs in the directory as well as the binary MyApp.exe. But, if the project has been cleaned (i.e., there are no files in $(OutputPath)). The DLLs are not included in the ItemGroup list of files. Now, if I follow this up with another build, (i.e., there are files from the previous build in $(OutputPath)) the ItemGroup contains all the files I want.
Also, I have checked the output of the build script and the DLLs are copied to $(OutputPath) whether a clean happened or not.
So, my question is, how do I correct my build script such that ItemGroup always contains the DLLs? It seems like the ItemGroup populates with files before the build happens, so if the files aren't there before the build, they aren't included in the list, but if they exist before the build then they are.
For reference, here is the build target that I am using:
<PropertyGroup>
<MyAppRoot>..\MyApp</MyAppRoot>
<MyAppProject>$(MyAppRoot)\MyApp.csproj</MyAppProject>
<PropertyGroup>
<Target Name="BuildProject">
<Message Text="BEFORE: #(ProjectFiles)" />
<MSBuild Projects="$(MyAppProject)" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="MyAppAssembly"/>
</MSBuild>
<Message Text="AFTER: #(ProjectFiles)" />
</Target>
Presumably your item array is declared at the root level in your project, in XML as a child of the <Project> element. This means that MSBuild will evaluate the membership in the item array when the project file is first loaded. Whether or not the existance of those files changes during execution is irrelevant. If you want to populate the item array at a particular point in your build, you need to change the declaration from a static item array to a dynamic one, which means moving it inside a target, to the exact spot where you want to gather the files, as:
<Target Name="BuildProject">
...before message
...msbuild task
<ItemGroup>
<ProjectFiles Include="$(ProjectRoot)\$(OutputPath)\*.dll" />
<ProjectFiles Include="$(ProjectRoot)\$(OutputPath)\MyApp.exe" />
</ItemGroup>
...after message
</Target>
(excerpted from trick #62 in the book "MSBuild Trickery")

Copy built assemblies (including PDB, .config and XML comment files) to folder post build

Is there a generic way I can get a post-build event to copy the built assembly, and any .config and any .xml comments files to a folder (usually solution relative) without having to write a post-build event on each project in a solution?
The goal is to have a folder that contains the last successful build of an entire solution.
It would be nice to use the same build solution over multiple solutions too, possibly enabling/ disabling certain projects (so don't copy unit tests etc).
Thanks,
Kieron
You can set common OutputPath to build all projects in Sln in one temp dir and copy required files to the latest build folder. In copy action you can set a filter to copy all dlls without "test" in its name.
msbuild.exe 1.sln /p:Configuration=Release;Platform=AnyCPU;OutputPath=..\latest-temp
There exists more complicated and more flexible solution. You can setup a hook for build process using CustomAfterMicrosoftCommonTargets. See this post for example.
Sample targets file can be like that:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
PublishToLatest
</BuildDependsOn>
</PropertyGroup>
<Target Name="PreparePublishingToLatest">
<PropertyGroup>
<TargetAssembly>$(TargetPath)</TargetAssembly>
<TargetAssemblyPdb>$(TargetDir)$(TargetName).pdb</TargetAssemblyPdb>
<TargetAssemblyXml>$(TargetDir)$(TargetName).xml</TargetAssemblyXml>
<TargetAssemblyConfig>$(TargetDir)$(TargetName).config</TargetAssemblyConfig>
<TargetAssemblyManifest>$(TargetDir)$(TargetName).manifest</TargetAssemblyManifest>
<IsTestAssembly>$(TargetName.ToUpper().Contains("TEST"))</IsTestAssembly>
</PropertyGroup>
<ItemGroup>
<PublishToLatestFiles Include="$(TargetAssembly)" Condition="Exists('$(TargetAssembly)')" />
<PublishToLatestFiles Include="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" />
<PublishToLatestFiles Include="$(TargetAssemblyXml)" Condition="Exists('$(TargetAssemblyXml)')" />
<PublishToLatestFiles Include="$(TargetAssemblyConfig)" Condition="Exists('$(TargetAssemblyConfig)')" />
<PublishToLatestFiles Include="$(TargetAssemblyManifest)" Condition="Exists('$(TargetAssemblyManifest)')" />
</ItemGroup>
</Target>
<Target Name="PublishToLatest"
Condition="Exists('$(LatestDir)') AND '$(IsTestAssembly)' == 'False' AND '#(PublishToLatestFiles)' != ''"
DependsOnTargets="PreparePublishingToLatest">
<Copy SourceFiles="#(PublishToLatestFiles)" DestinationFolder="$(LatestDir)" SkipUnchangedFiles="true" />
</Target>
</Project>
In that targets file you can specify any actions you want.
You can place it here "C:\Program Files\MSBuild\v4.0\Custom.After.Microsoft.Common.targets" or here "C:\Program Files\MSBuild\4.0\Microsoft.Common.targets\ImportAfter\PublishToLatest.targets".
And third variant is to add to every project you want to publish import of custom targets. See How to: Use the Same Target in Multiple Project Files

Resources