Optional PreBuildEvent in MSBuild? - build

Is it possible to have an optional <PreBuildEvent> in a *.csproj file? I have the following:
<PropertyGroup>
<PreBuildEvent>git rev-parse HEAD >../../git-hash.txt</PreBuildEvent>
</PropertyGroup>
This outputs the latest git hash to a file, which is embedded in the executable elsewhere.
Since I'm a University student, I'm often writing code on the University machines (and not my linux machine at home) which have SVN and not git, causing the build process to fail. Is it possible to make the above <PreBuildEvent /> optional so that if git isn't installed the build process doesn't fail?

Just skipping the build event would leave you with an empty git-hash.txt so that doesn't seem the best idea. Instead you could just try to run the git command, and if it fails write a dummy hash to the file. I don't know the command line syntax to do that (a PreBuildEvent runs under cmd.exe) so here's an msbuild solution. Because of the BeforeTargets="Build" it will run before the build as well.
<Target Name="WriteGitHash" BeforeTargets="Build">
<Exec Command="git --work-tree=$(Repo) --git-dir=$(Repo)\.git rev-parse HEAD 2> NUL" ConsoleToMSBuild="true" IgnoreExitCode="True">
<Output TaskParameter="ConsoleOutput" PropertyName="GitTag" />
</Exec>
<PropertyGroup>
<GitTag Condition="'$(GitTag)' == ''">unknown</GitTag>
</PropertyGroup>
<WriteLinesToFile File="$(Repo)\git-hash.txt" Lines="$(GitTag)" Overwrite="True"/>
</Target>
Some notes:
The 2> NUL redirects standard error to the output so GitTag will be empty in case of an error, in which case it's set to 'unknown'
Relying on the current directory is nearly always a bad idea so specify the directory to run git in explicitly in a property
Same for the output file

Related

How do I put a condition on msbuild built-in targets like Build/Rebuild?

I am working differentially building a huge monolithic solution that includes about 80 projects. In my build pipeline right now I include a step to build the entire solution. But what I'd like to do is to build the solution but provide conditions as msbuild arguments so that I can exclude some of the projects that might not have any changes associated with them. I already have scripts to go through my commits and realize what changed and which projects need to be built.
I just need a way to send that info to MSBuild so that it does not build all projects everytime. I tried building projects separately but that takes a whole lot more time than just building the solution together.
So, I'm looking for any solutions out there through which I can specify to MSBuild that skip a specific project would help a lot. Thanks much!
I already have scripts to go through my commits and realize what
changed and which projects need to be built.
Since I could get clearly know that which script are you using to realize what changed and which projects need to be built. I am assuming that you are using MSbuildTarget script which in the xx.csproj to do these judgement.
=If I did not have misunderstanding, you can get help from this similar issue (See ilya's answer).
See this document and you'll find the build action is performed by these three targets, BeforeBuild,CoreBuild and AfterBuild. So assuming you have a target to go through my commits and realize what changed and if a project need to be built, you can add script like below to xx.csproj:
<PropertyGroup>
<BuildWrapperDependsOn>$(BuildDependsOn)</BuildWrapperDependsOn>
<BuildDependsOn>CheckIfBuildIsNeeded;BuildWrapper</BuildDependsOn>
</PropertyGroup>
<Target Name="CheckIfBuildIsNeeded">
<!-- Execute command here that checks if proceed with the build and sets the exit code -->
<Exec Command="exit /b 1" WorkingDirectory="$(SourcesPath)" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
</Exec>
<Message Text="Exit Code: $(ExecExitCode)" Importance="high" />
<PropertyGroup Condition="'$(ExecExitCode)' == '1'">
<DoBuild>false</DoBuild>
</PropertyGroup>
</Target>
<Target Name="BuildWrapper" Condition=" '$(DoBuild)' != 'false' " DependsOnTargets="$(BuildWrapperDependsOn)" Returns="$(TargetPath)" />
Above is the script from ilys, and hope my description can help you understand it. With this script, when we start a build target, it will firstly run the targets it depends on, so it will run the CheckIfBuildIsNeeded target and BuildWrapper target. And only when the DoBuild property is true, the BuildWrapper will actually execute. And since buildwrapper depends on original $(BuildDependsOn), it will continue the real build process.
The total logic is: Run CheckIfBuildIsNeeded script and output value to indicates whether need to build=>Try to Run BuildWrapper=>IF need to build, then run the real build success(BeforeBuild, Corebuild,Afterbuild), if the value is false, finish the build process. So I think you can do some little changes to this script then it can work for your situation. (Not sure what your script looks like, I can't complete it for you)
And since you have many projects, you don't need to add this script to every project manually. You can create a Directory.Build.props file, copy the script into it, and place the file in solution folder, then it will work for all projects in the solution.

MSBuild CL Task output directory

i am writing a MSBuild script which compiles a C++ file using MSBuild CL Task, something like:
<CL Sources="c:\temp\myfile.cpp" />
How do i control where the output (myfile.obj) goes? By default, it goes to the path from where the script is present (and also happens to be the path from where i run the script): i.e. if i run the script (c:\someFolder\build.proj) from "c:\someFolder\" then myfile.obj is placed at "c:\someFolder\myfile.obj".
Going by a wild (illogical) guess, i also tried fidgeting with OutDir property(?!), something like:
<PropertyGroup>
<OutDir>d:\somePlace\<OutDir>
</PropertyGroup>
...
<CL Sources="c:\temp\myfile.cpp" />
Didn't work.
The answer to the original question about how to control the output of the CL task is by using the ObjectFileName argument (as provided by Hans Passant).
However, it now seems that using a minimum standard vcxproj is a better idea (as suggested by both stijn and Hans Passant).

MSBuild: parallel builds and .Net projects

I've written a MSBuild project files which try to build in parallel all the configs of my VS2010 solution:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<ItemGroup>
<BuildFile Include="$(SourceRoot)MyProject.sln" />
<Config Include="Debug">
<Configuration>Debug</Configuration>
</Config>
<Config Include="Release">
<Configuration>Release</Configuration>
</Config>
</ItemGroup>
<Target Name="BuildAll" Outputs="%(Config.Configuration)">
<Message Text="Start building for configuration: %(Config.Configuration)" />
<MSBuild Projects="#(BuildFile)"
Properties="Configuration=%(Config.Configuration)"
Targets="Build" />
</Target>
</Project>
And I launch msbuild with:
msbuild /m /p:BuildInParallel=true /t:BuildAll buildall.proj
The problem is that my solution have many .Net projects which all have the same output folder. These projects use also the same external assemblies.
So very often, two output executables are generated at the same time and their dependencies copied at the same time. This leads to errors like:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(3001,9):
error MSB3021:
Unable to copy file "xxx\NLog.dll" to "D:\src\Blackbird\shared\bin\debug\NLog.xml". The process
cannot access the file 'xxx\NLog.dll because it is being used by another process.
which I think means: "2 different projects use NLog and try to copy its assembly in the output folder at the same time"...
Is there a way to get around that? I really would like to avoid to modify all the projects in the solution.
Looking at the task source code "C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(3001,9)", I've seen that it is possible to make msbuild retry the copy:
<Copy
SourceFiles="#(ReferenceCopyLocalPaths)"
DestinationFiles="#(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForCopyLocalIfPossible)"
Condition="'$(UseCommonOutputDirectory)' != 'true'"
>
I've try to set the variables CopyRetryCount, CopyRetryDelayMilliseconds, ... I was hoping that if the copy fails, another copy done a few milliseconds later would succeed. But I've been unable to set these parameters. How can I change them?
Is there another solution?
I've found the solution
<MSBuild Projects="#(BuildFile)"
Properties="Configuration=%(Config.Configuration);Retries=10;RetryDelayMilliseconds=50"
Targets="Build" />
It works as expected but now it generates a warning before retrying the copy
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(3001,9):
warning MSB3026: Could not copy
"D:\src\xxx\System.Data.SQLite.pdb" to "..\Debug\System.Data.SQLite.pdb".
Beginning retry 1 in 50ms. The process cannot access the file
'..\Debug\System.Data.SQLite.pdb' because it is being used by another process.
During my last test, it generated 36 times this warning! Is there a way for suppressing the warning MSB0326?
In general, anything that runs during build must not acquire an exclusive lock on its inputs - only shared-read lock. It appears that something in your build process (probably NLog, whatever that is) violates this - it takes exclusive lock on the input "xxx\NLog.dll", so when another msbuild node tries to copy the same input it fails.
Retry is a reasonable workaround for the particular symptom you have - though it's not guaranteed to always succeed.
I had the same problem. I needed to add appropriate exception to my AntiVirus to avoid sacanning DLLs produced by MsBuild.

Compiling SSIS Projects with Team City

Has anyone done this? If so, what tools/techniques/approaches did you use?
Is it possible to do with installing the SQL Business Studio Version of Visual Studio?
Thanks in advance!
Got it folks...
1) Install MSBuild Extensions
2) Created a Build.Xml file as so...
<Project ToolsVersion="3.5" DefaultTargets="Default" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>
<Target Name="Default">
<PropertyGroup>
<OutputRoot>../../../build-artifacts</OutputRoot>
</PropertyGroup>
<ItemGroup>
<SSISProjectFile Include="SSISProject.dtproj"/>
<SSISProject Include="#(SSISProjectFile)">
<OutputDirectory>$(OutputRoot)</OutputDirectory>
</SSISProject>
</ItemGroup>
<ItemGroup>
<Namespaces Include="Mynamespace">
<Prefix>DTS</Prefix>
<Uri>www.microsoft.com/SqlServer/Dts</Uri>
</Namespaces>
</ItemGroup>
<MSBuild.ExtensionPack.Xml.XmlFile
TaskAction="UpdateElement"
File="EnclarityDataImport.dtsx"
XPath="//DTS:Property[#DTS:Name='ConfigurationString']"
InnerText="$(MSBuildProjectDirectory)\EnclarityDataImport.dtsConfig"
Namespaces="#(Namespaces)"/>
<MSBuild.ExtensionPack.SqlServer.BuildDeploymentManifest InputProject="#(SSISProject)"/>
</Target>
The only trick was the last part of the build here. By default visual studio adds the absolute path to your config and connection string files for your dtsx package. Team City will use these in conjunction with MSBuild extensions to build the package so a local path will break the build because the paths to the build directories in Team City are automatically generated. So using the code above and the $(MSBuildProjectDirectory) you can twiddle the value of the path on your dtsx file so that it points to the path where your compilation is exectuing.
Like booyaa says SSIS projects don't need to be compiled, but what i have done is make the .dtconfigs configurable by the build/deployment process.
I do this so that i can run the packages on deployment in different environments. So the build will copy a template of the dtconfig file.
this contains tokens- $(Servername) $(ConnectionString)
And then i do the replacement on deployment and then execute by wrapping the dtexec in an command.
Not sure about 2012.

How to run exe file on CruiseControl

I've been looking through CruiseControl documentation and I found tag and
for running scripts. But when I am trying to run exe-file from that tags it does not work as well as it described in documantation.
I also tried to put call of the exe in batch file and execute it from CruiseControl but also did not work as I expected. So how can I run exe-file from CC? I also need to be able to include output of this file work in my email notification is it possible at all?
E.g. I have file UnitTests.exe which prints something like this:
Unit tests are passed.
47 Tests was successful
How can I do this? Or how can I at least get an returning code from that executable file?
Run the exec in ant.
In cruisecontrol:
<schedule>
<ant anthome="/usr/apache-ant-1.8.2" buildfile="/usr/ant-build-files/my-ant-build-file.build" target="do-task" uselogger="true">
</ant>
</schedule>
In /usr/ant-build-files/my-ant-build-file.build
...
<target name="do-task">
<exec executable="/<path to dir containing exe>/UnitTests.exe" failonerror="true">
<arg line="<args to UnitTests.exe>"/>
</exec>
There is option to execute .bat or .exe files using the following tag.
<exec executable="c:/something.exe" />
You can place the above line in any target of the xml files that your build script is going to call.
<target name="target-to-call-an-exe">
<exec executable="c:/cygwin/bin/bash.exe" />
</target>
Hope this helps, Thanks.