How can I get the path of the call to MSBuild? - msbuild

Is it possible to access (in a build file) the directory from which MSBuild.exe was called?
I have only been able to get the path of the build file itself. I want the directory from which MSBuild was called instead.
Expected
D:\> msbuild foo\foo.proj
...
MSBuild was called from: D:\>
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BeforeBuild">
<Message Text="MSBuild was called from: ???" />
</Target>
</Project>
Why
I was wondering if one could replace the absolute path in
D:\workdir> msbuild foo\foo.proj /p:Parameter=d:\workdir\Projects\bar\
with a relative path
D:\workdir> msbuild foo\foo.proj /p:Parameter=Projects\bar\

See MSBuild Reserved and Well-Known Properties
It looks like MSBuildStartupDirectory will help you:
TestBuild.vcxproj snippet
<Target Name="BeforeBuild">
<Message Text="MSBuild was called from: $(MSBuildStartupDirectory)" />
</Target>
Build
d:\Data\Visual Studio 2013\Projects>msbuild /m .\TestMsBuild\TestMsBuild\TestMsBuild.vcxproj /verbosity:detailed /t:BeforeBuild
Task "Message"
MSBuild was called from: d:\Data\Visual Studio 2013\Projects

Related

TeamCity MSBuild refer to build counter

I have a property group which includes a property for the build_number which is being passed in from TeamCity as solely the Build Counter. The build number format being set in TeamCity as simply {0} for the counter.
<PropertyGroup>
<Major>10</Major>
<Minor>1</Minor>
<Build>$(BUILD_NUMBER)</Build>
<Release>0</Release>
...
</PropertyGroup>
The Major, Minor and Release properties are then updated from values in a file in source control.
So that TeamCity logs the build as the full 4 part build reference (not just the counter), I set it thus:
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
However, now when I reference my $(Build) property, it's now set to the 4 part build reference, and any property I have made which makes reference to $(BUILD_NUMBER) prior to setting using TeamCitySetBuildNumber also gets overwritten with the 4 part reference.
NB I've also changed it with a system message:
<Message Text="##teamcity[buildNumber '$(Major).$(Minor).$(Build).$(Release)']" />
but the overall effect is the same.
How Can I refer to the build counter (only) AFTER I have set the BuildNumber above?
If you're using a project file, you could try calling the TeamCitySetBuildNumber command in the AfterBuild section of the *.vbproj or *.csproj file:
<Target Name="AfterBuild">
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
</Target>
If you're using a solution file, I'd create a *.proj file that calls your solution file and then after that call the TeamCitySetBuildNumber command (not sure if you can call the TeamCitySetBuildNumber command within the target like this though...):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="SetBuildNumber">
<PropertyGroup>
<Major>10</Major>
<Minor>1</Minor>
<Build>$(BUILD_NUMBER)</Build>
<Release>0</Release>
</PropertyGroup>
<Target Name="Build">
<Message Text="Build task called... " Importance="high"/>
<MSBuild Projects="$(teamcity_build_checkoutDir)\your_solution.sln" Properties="Configuration=Release"/>
</Target>
<Target Name="SetBuildNumber" DependsOnTargets="Build">
<Message Text="Setting build number back to TeamCity... " Importance="high"/>
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
</Target>
</Project>

Working directory issue when importing msbuild file in another msbuild file

I am trying to specify some additional targets/tasks to an msbuild file by extending an existing msbuild file (a web applicartion .csproj file). The idea is to put configuration specific tasks in this "extended ms build file" and use this file in our build server (TeamCity). The way I tried to solve it at first was to add a folder "msbuildscripts" to my web project and put the extended ms build file there:
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<Import Project="../My.Web.csproj" />
...more stuff...
</Project>
and then build this file using something like:
c:\myweb\msbuild.exe msbuildscripts/extended.msbuild.file.xml
Now, this wont work because when importing the original ms build file, that csproj file will be "executed" in the "wrong" folder (msbuildscripts), and the csproj-build-file wont find any of its referenced folders/items.
Is there any way to tell msbuild.exe to use a specific working directory? I know it is possible to solve this problem using an execute task, but that doesnt seem like a good solution.
Use MSBuild task like this:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="MyBuild">
<ItemGroup>
<ProjectToBuild Include="../My.Web.csproj" />
</ItemGroup>
<Target Name="MyBuild">
<MSBuild Targets="Build" Projects="#(ProjectToBuild)"></MSBuild>
</Target>
</Project>

VCBuild task in MSBuild - change outputpath

I'm attempting to write an automated build for one of our products, and I've hit up against a wall for some of our VC++ projects: I need to be able to set the output path to where the assemblies will be copied once its done.
Here is a makeshift msbuild file:
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<Target Name="Build">
<VCBuild Projects="C:\src\SomeProject\SomeProject.vcproj"
ToolPath="C:\Program Files\Microsoft Visual Studio 9.0\VC\vcpackages"
Configuration="Debug" />
</Target>
</Project>
Stijn's Answer:
I thought I'd use this space to clarify how I personally used Stijn's answer to solve this. He has some code in his MSBuild file that writes the vsprops file for him. I decided to take a simpler approach and just write the file manually.
I created this file, called build.vsprops (my output path is V:)
<?xml version="1.0"?>
<VisualStudioPropertySheet ProjectType="Visual C++"
Version="8.00"
Name="Overrides"
OutputDirectory="V:\">
<Tool Name="VCCLCompilerTool"
AdditionalUsingDirectories="V:\" />
</VisualStudioPropertySheet>
Then I edited my MSBuild file to add the Override parameter:
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<Target Name="Build">
<VCBuild Projects="C:\src\SomeProject\SomeProject.vcproj"
ToolPath="C:\Program Files\Microsoft Visual Studio 9.0\VC\vcpackages"
Configuration="Debug"
Override="$(MSBuildProjectDirectory)\build.vsprops" />
</Target>
</Project>
have a look at the Override parameter for the VCBuild task. Basically you specify a property sheet which you can use to override whatever property you want (it has the same effect as adding a property sheet to the top of the list in a project within VS). You could even generate the override file using the WriteLinesToFile task.
Example:
<PropertyGroup>
<VCOverridesFile Condition=" '$(VCOverridesFile)'=='' ">overrides.vsprops</VCOverridesFile>
<VCOverridesOpen>%3C?xml version=%221.0%22?%3E%0D%0A%3CVisualStudioPropertySheet ProjectType=%22Visual C++%22 Version=%228.00%22 Name=%22My Overrides%22%3E</VCOverridesOpen>
<VCOverridesClose>%3C/VisualStudioPropertySheet%3E</VCOverridesClose>
<MyOutPath><Tool Name="VCLinkerTool" OutputFile ="c:\my.exe"/></MyOutPath>
</PropertyGroup>
<Target Name="WriteOverridesFile">
<WriteLinesToFile
File="$(VCOverridesFile)"
Lines="$(VCOverridesOpen);$(AdditionalVCOverrides);$(VCOverridesClose)"
Overwrite="true" />
</Target>
Then pass $(VCOverridesFile) to the Override property and make sure your VCBuild Task DependsOnTarget WriteOverridesFile.
Doing it the dirty way you can pass output directory path through command line arguments of msbuild.
msbuild yourProject /p:OutDir=yourPath
Although I suspect, there should be the better way to accomplish the task. The main idea is to set 'OutDir' property in such a way that it will not be overriden by your SomeProject.vcproj
if you are using Azure DevOps and needs to create a YAML do build a .net framework (vintage[old])
- task: VSBuild#1
inputs:
solution: '**\*.sln'
msbuildArgs: '/p:DeployOnBuild=true /p:SkipInvalidConfigurations=false /p:OutDir="$(System.DefaultWorkingDirectory)\publish_output"'
platform: 'Any CPU'
configuration: 'Release'

How do I use MSBuild on a C# project with no compilable code

I have a C# web app project which actually has no ASP.Net or C# in it. It's just a single html page with some Javascript, CSS, and a couple of images.
I want to use MSBuild to deploy a version of this app to an output folder with minified JS and CSS.
With the following code, I get an error "CSC: fatal error CS2008: No inputs specified." I'm guessing because the there is no actual C# code to compile but I'm not sure.
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<CssTidy>..\build_tools\csstidy.exe</CssTidy>
</PropertyGroup>
<PropertyGroup>
<DeploymentFolder>Test\</DeploymentFolder>
<SourceProject>..\..\Test\Test.csproj</SourceProject>
</PropertyGroup>
<Import Project="Common.Web.targets" />
<ItemGroup>
<CssFiles Include="..\..\Test\CSS\stylesheet.css" />
<ScriptFiles Include="..\..\Test\JavaScript\javascript.js"/>
</ItemGroup>
<Target Name="compress_css">
<Attrib Files="%(CssFiles.FullPath)" ReadOnly="false"/>
<Exec Command="$(CssTidy) %(CssFiles.FullPath) %(CssFiles.FullPath) --template=highest" />
</Target>
<Target Name="compress_js">
<Attrib Files="%(ScriptFiles.FullPath)" ReadOnly="false"/>
<JSCompress Files="%(ScriptFiles.FullPath)"></JSCompress>
</Target>
<Target Name="call_targets">
<CallTarget Targets="compress_css"/>
<CallTarget Targets="compress_js"/>
</Target>
</Project>
How can I accomplish this?
You could override the CoreCompile target and do nothing there:<Target name="CoreCompile" />. This will skip its activities and move on. You may have to override additional targets to avoid errors.
At the top of the file you have the DefaultTargets="Build"
Change "Build" to "call_targets" and you should be good to go.
What is inside "common.web.targets"? I assume that the error is generated from a target in that file (or another that it imports).
A quick fix for this would be to add a dummy page to the project. The build would work after that.

Getting the Content item from a csproj using the MSBuild task

I have an MSBuild file and I am building C# projects like this:
<ItemGroup>
<ProjectsToBuild Include="./source/ProjectA/ProjectA.csproj"/>
<ProjectsToBuild Include="./source/ProjectB/ProjectB.csproj"/>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectsToBuild)" Targets="Build">
<Output ItemName="ProjectOutputs" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#ProjectOutputs"/>
</Target>
I successfully get an Item containing all of the .dll files that were built:
Build:
c:\code\bin\ProjectA.dll;c:\code\bin\ProjectB.dll
I would also like to get the Content item from each project without modifying the .csproj files. After digging around in the Microsoft .targets files, I was almost able to get it working with this:
<MSBuild Projects="#(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#(ContentFiles->'%(RelativeDir)')"/>
The problem with this approach is the RelativeDir is not being set correctly. I am getting the full path instead of relative:
Build:
c:\ProjectA\MyFolder\MyControl.ascx;c:\ProjectB\MyOtherFolder\MyCSS.css;
instead of:
Build:
MyFolder\MyControl.ascx;MyOtherFolder\MyCSS.css;
Is there a property I can pass to the MSBuild task that will make RelativeDir behave correctly?
Or, even better, is there an easier way to get the Content item?
You can do this but it is not very intutive. I've discussed this type of technique a few times on my blog ( which is currently down :( ).
So create a new file, I named it GetContentFiles.proj which is shown here.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Projects Include="WindowsFormsApplication1\WindowsFormsApplication1.csproj"/>
</ItemGroup>
<!-- This target will be executed once for each file declared in the Project target -->
<Target Name="PrintFiles" Outputs="%(Projects.Identity)">
<Message Text="PrintFiles" Importance="high"/>
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="GetContentFiles"
Properties="ProjectToGetFiles=%(Projects.Identity)">
<Output ItemName="projContent" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="ProjContent: #(projContent)" Importance="high"/>
<!-- Transform the projContent to have correct path -->
<!--
Get the relative path to the project itself, this serves as the base for
the Content files path
-->
<PropertyGroup>
<_ProjRelativeDir>%(Projects.RelativeDir)</_ProjRelativeDir>
</PropertyGroup>
<!-- This item will contain the item with the corrected path values -->
<ItemGroup>
<ProjContentFixed Include="#(projContent->'$(_ProjRelativeDir)%(RelativeDir)%(Filename)%(Extension)')"/>
</ItemGroup>
<!-- Create a new item with the correct relative dirs-->
<Error Condition="!Exists('%(ProjContentFixed.FullPath)')"
Text="File not found at [%(ProjContentFixed.FullPath)]"/>
</Target>
<Import Project="$(ProjectToGetFiles)" Condition="'$(ProjectToGetFiles)'!=''"/>
<Target Name="GetContentFiles" Condition="'$(ProjectToGetFiles)'!=''" Outputs="#(Content)">
<Message Text="Content : #(Content)" Importance="high"/>
<Message Text="Inside GetContentFiles" Importance="high"/>
</Target>
</Project>
I will try and explain this, but it may be tough to follow. Let me know if you need me to expand on it. This file has two targets PrintFiles and GetContentFiles. The entry point into this file is the PrintFiles target, in the sense that this is the target that you are going to call. So you call the PrintFiles target which it then uses the MSBuild task to call the GetContentFiles target on itself, also it passes a value for the ProjectToGetFiles property. Because of that the Import elemnent will be executed. So what you are really doing is taking the project defined in the ProjectToGetFiles property and extending it to include the target GetContentFiles (and whatever other content is inside the GetContentFiles.proj file). So we are effectively extending that file. I'm calling this technique "MSBuild Inheritance" because. So inside the GetContentFiles target we can access all properties and items that are declared inthe ProjectToGetFiles property. So I take advantage of that by simply putting the content of the Content item into the outputs for the target, which can be accessed by the original file using the TargetOutputs from the MSBuild task.
You mentioned in your post that you wanted to correct the path values to be the right ones. The problem here is that in the .csproj file all items are declared relative to the original project file. So if you "extend" the project file in this way from a file in a different directory you must correct the file path values manually. I've done this inside the PrintFiles target, check it out.
If you execute the command msbuild GetContentFile.proj /fl /t:PrintFiles the result would be:
Build started 7/3/2009 12:56:35 AM.
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" on node 0 (PrintFiles target(s)).
PrintFiles
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (1) is building "C:\Data\Development\My Co
de\Community\MSBuild\FileWrites\GetContentFile.proj" (1:2) on node 0 (GetContentFiles target(s)).
Content : Configs\Config1.xml;Configs\Config2.xml
Inside GetContentFiles
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (GetContentFiles target(s)).
PrintFiles:
ProjContent: Configs\Config1.xml;Configs\Config2.xml
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (PrintFiles target(s)).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03
Sayed Ibrahim Hashimi
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
In case this helps someone else - use TargetPath instead of RelativeDir:
<MSBuild Projects="#(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#(ContentFiles->'%(TargetPath)')"/>
This will give you the relative path for each item.