The following three XMLs are key, and I recommend to import them always to take benefit to pre-defined Visual Studio tasks.
$(VCTargetsPath)\Microsoft.Cpp.default.props
$(VCTargetsPath)\Microsoft.Cpp.props
$(VCTargetsPath)\Microsoft.Cpp.targets
Property (=? enviroment variable) can be set by using PropertyGroup
.
Metadata can be defined and set by using following elements:
MSBuild supports the following three expansion for variable/metadata.
Use to expand property/variable. $(PATH) to expands to PATH variable.
Use to expand metadata item list to comma separated path sequence.
<ItemGroup>
<ClCompile Include="a.cpp;b.cpp" />
<ClCompile Include="d.cpp" />
</ItemGroup>
<PropertyGroup>
<ClCompileList>@(ClCompile)</ClCompileList>
</PropertyGroup>
On the above example, ClCompileList
property will be a.cpp;b.cpp;d.cpp
.
The following project will print xml files on project directory.
<Project DefaultTargets="PrintXmlFiles" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<XmlFiles Include="*.xml" />
</ItemGroup>
<Target Name="PrintXmlFiles">
<Exec Command="echo %(XmlFiles.Identity)" />
</Target>
</Project>
In contrast with comma separeted @()
, run command for each of files as follows:
echo app.xml
app.xml
echo build.xml
build.xml
echo config.xml
config.xml
echo test.xml
test.xml
Identity
is a well-known item metadata described at https://msdn.microsoft.com/en-us/library/ms164313.aspx
Metadata of CL
Task (ClCompile
element) and Link
Task are defined and described at:
$(VCTargetsPath)\1033\cl.xml
$(VCTargetsPath)\1033\link.xml
For example, PrecompiledHeader
metadata can be used as follow:
<ItemGroup>
<ClCompile>
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
For some tasks, must use UsingTask
on your project XML.
Sequences of UsingTask
for C++ are defined at $(VCTargetsPath)\Microsoft.CppCommon.targets
(Import $(VCTargetsPath)\Microsoft.Cpp.targets
<!-- $(VCTargetsPath)\Microsoft.CppCommon.targets -->
<UsingTask TaskName="VCMessage" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="LIB" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="MIDL" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="RC" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="Mt" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="XSD" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="XDCMake" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="BscMake" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="CustomBuild" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="CL" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="Link" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="FXC" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
<UsingTask TaskName="MultiToolTask" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll"/>
Platform
name will be manually supplied by developer, and its identifier not unique and case-insensitive. (eg. Win32
and AnyCPU
can be thought as aliases for x86
), so directly using it to the conditional diverging is dangerous.
Using alternatively-defined property is a one of solution of this. PlatformTarget
will be strictly defined at $(VCTargetsPath)\Platforms\*\Platform.default.props
.
x86
(Lower-case)x64
(Lower-case)ARM
(Upper-case)
Note that they are avaiable after <Import Project="$(VCTargetsPath)\Microsoft.Cpp.default.props" />
.
<!-- $(VCTargetsPath)\Platforms\ARM\Platform.default.props -->
<PropertyGroup>
<PlatformShortName>ARM</PlatformShortName>
<PlatformArchitecture>ARM</PlatformArchitecture>
<PlatformTarget>ARM</PlatformTarget>
<!-- $(VCTargetsPath)\Platforms\Win32\Platform.default.props -->
<PropertyGroup>
<PlatformShortName>x86</PlatformShortName>
<PlatformArchitecture>32</PlatformArchitecture>
<PlatformTarget>x86</PlatformTarget>
<!-- $(VCTargetsPath)\Platforms\x64\Platform.default.props -->
<PropertyGroup>
<PlatformShortName>x64</PlatformShortName>
<PlatformArchitecture>64</PlatformArchitecture>
<PlatformTarget>x64</PlatformTarget>
By default, pre-defined Build task will try to output EXE file. If you want to change this behavior, set ConfigurationType
property before importing $(VCTargetsPath)\Microsoft.Cpp.default.targets
.
<PropertyGroup>
<!-- <ConfigurationType>Application</ConfigurationType> (Build as EXE) -->
<ConfigurationType>DynamicLibrary</ConfigurationType> <!-- (Build as DLL) -->
<!-- <ConfigurationType>StaticLibrary</ConfigurationType> (Build as LIB) -->
</PropertyGroup
Or,
msbuild /p:ConfigurationType=DynamicLibrary
This reason can be found at $(VCTargetsPath)\Microsoft.Cpp.default.props
, $(VCTargetsPath)\Microsoft.Cpp.Common.props
, and the other XMLs.
<!-- $(VCTargetsPath)\Microsoft.Cpp.default.props -->
<!-- This is the Cpp defaults settings mapping file. It defines all the project properties values
(equivalent of System Macros) and also all the ItemDefinitionGroup defaults for each known
ItemGroup (known as the default of the defaults in the current Project System) -->
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<ConfigurationType Condition="'$(ConfigurationType)' == ''">Application</ConfigurationType>
</PropertyGroup>
<!-- $(VCTargetsPath)\Microsoft.Cpp.Common.props -->
<!-- Specific values -->
<PropertyGroup Condition="'$(ConfigurationType)' == 'Application'">
<LinkCompiled>true</LinkCompiled>
<TargetExt>.exe</TargetExt>
<OutputType>exe</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(ConfigurationType)' == 'DynamicLibrary'">
<LinkCompiled>true</LinkCompiled>
<!-- $(GenerateImportLib) should be set to true when you want to generate the import library as part of the BuildCompile pass rather than wait
until the BuildLink pass for Linker to generate it. This allows circular dependencies between dlls to be satisfied when building using passes -->
<ImpLibCompiled Condition="'$(GenerateImportLib)'=='true'">true</ImpLibCompiled>
<TargetExt>.dll</TargetExt>
<OutputType>library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(ConfigurationType)' == 'StaticLibrary'">
<LibCompiled>true</LibCompiled>
<TargetExt>.lib</TargetExt>
<OutputType>staticlibrary</OutputType>
</PropertyGroup>
Exec
Task does not inherit the ending-state (variables and working directory) of prior Exec
. For example, the following two cd
will print the different location.
<Target Name="AfterBuild">
<Exec Command='cd /D Z:\foobar && cd' />
<Exec Command='cd' />
</Target>
In addition, variables are reset. The next first command will print foo
, but x86
or x64
will be printed on second command.
<Target Name="AfterBuild">
<Exec Command='set Platform="foo" && echo %Platform%' />
<Exec Command='echo %Platform%' />
</Target>
All states seems to reset to the state of beginning of each Target.
NOTE: Major command will use ampersand &
, so do not forget to escape it by obeying XML attribute syntax. (Use &
)
MSBuild properties are does not expose as environment variables, so the following command will print nothing.
<Target Name="AfterBuild">
<Exec Command="echo_Platform.bat" />
<!-- echo_Platform.bat (echo %Platform%) -->
</Target>
In this case, expose wanted properites as variables by using SetEnv
task.
<Target Name="AfterBuild">
<SetEnv Name="Platform" Prefix="false" Value=$(Platform) />
<Exec Command="echo_Platform.bat" />
</Target>
Variables set by SetEnv
will be inherited between target and target, beware of their side effect.
On the following example, TargetA
prints nothing, but TargetB
prints x86
.
<Project DefaultTargets="TargetA;TargetB" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Platform>x86</Platform>
<PlatformToolset>v140</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Target Name="TargetA">
<Exec Command="echo %Platform%" />
<SetEnv Name="Platform" Prefix="false" Value="$(Platform)" />
</Target>
<Target Name="TargetB">
<Exec Command="echo %Platform%" />
</Target>
</Project>
If want to prevent environment from pollution, consider to place set
command before batch file instead of SetEnv
.
Next first command will print x86
but second one nothing.
<Target Name="AfterBuild">
<Exec Command="set Platform=$(Platform) & echo_Platform.bat" />
<Exec Command="echo_Platform.bat" />
</Target>
Note that Command="set Platform=$(Platform) & echo %Platform%
print nothing because of DOS variable expansion behavior. (See EnableDelayedExpansion
). Try set foo=bar & echo %foo%
on your vanilla Command Prompt.
In this case, simply use MSBuild substitution echo $(Platform)
.