Wenn man Komponenten für verschiedene Platformen erstellt, hat man entweder die Möglichkeit die diversen Platformen mit unterschiedlicher Codebase zu verwalten, oder man entscheidet sich dazu das MSBuild File etwas zu erweitern. Welche Alternative die bessere ist, liegt wohl klar auf der Hand ;)
In diesem Beispiel möchte ich eine einfache Komponente für die Platformen
- .NET Framework 4.0
- Silverlight 4.0
- Silverlight 4.0 for Windows Phone
erstellen. Mit dem fertigen MSBuild-Script kann man dann einfach über eine Property (welche als Parameter an den MSBuild.exe – Aufruf übergeben werden kann) zwischen den einzelnen Platformen umschalten.
Die Ausgangsposition für dieses Beispiel dient eine einfache C# ClassLibrary, das Ausgangsprojekt könnt ihr hier downloaden. Da die Änderungen lediglich im ProjectFile der ClassLibrary geschehen, bietet es sich an das ProjectFile entweder mit Notepad++ zu öffnen oder in Visual Studio den folgenden Workaround durchzuführen um das ProjectFile zu editieren.
Workaround – Bearbeiten eines ProjectFiles in Visual Studio
Zunächst muss man im SolutionExplorer im KontextMenü des gewünschten Projektes den Eintrag Unload Project auswählen

Danach muss auf den ausgegrauten Eintrag im SolutionExplorer erneut ein Rechtsklick gemacht werden, diesmal präsentiert sich ein etwas kleineres KontextMenü aus dem der Eintrag “Edit $$ProjectFileName$$” ausgewählt wird.

Zurück zum Thema – Das ProjectFile
Das gerade geöffnete “Standard” ProjectFile sollte wie folgt aussehen
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{66A02AE2-91B4-4A70-AC92-1B41B73A09B2}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>DotNetRocks.MSBuildMultitargeting</RootNamespace>
<AssemblyName>DotNetRocks.MSBuildMultitargeting</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Log.cs" />
<Compile Include="ILogMessage.cs" />
<Compile Include="ILogTarget.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
Als ersten Schritt sollten die unnötigen Assembly Verweise entfernt werden, also die Zeilen
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
Ok, nun aber zurück zum eigentlichen Ziel des Scripts. Zunächst wird eine neue Property benötigt, welche definiert welches Zielframework verwendet werden soll. Hierzu kann entweder die Standard-PropertyGroup verwendet werden, oder eine neue PropertyGroup erstellt werden. Aus Gründen der Übersichtlichkeit gehe ich an dieser Stelle den Weg mit einer eigenen PropertyGroup.
Das folgende XML Fragment sollte demnach vor der ersten PropertyGroup eingefügt werden.
<PropertyGroup>
<TargetFX></TargetFX>
</PropertyGroup>
Im nächste Schritt müssen entsprechend des Zielframeworks einige Standardwerte von MSBuild überschrieben werden. Sowohl für Silverlight als auch für Silverlight for Windows Phone Assemblies müssen die Werte der Eigenschaften
- TargetFrameworkIdentifier
- SilverlightVerion
- SilverlightApplication
- TargetFrameworkProfile
angepasst werden. In MSBuild ist dies dank Conditions relativ einfach. Dieses XML Fragment sollte unterhalb der Standard-MSBuild PropertyGroups eingebunden werden.
<PropertyGroup
Condition="$(TargetFX) == 'Silverlight' OR $(TargetFX) == 'WP7'">
<SilverlightApplication>false</SilverlightApplication>
<TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
<SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>
<TargetFrameworkProfile
Condition="$(TargetFX) == 'WP7'">WindowsPhone</TargetFrameworkProfile>
</PropertyGroup>
Wichtig in dieser PropertyGroup ist, dass die Properties nur evaluiert werden, wenn explizit ein TargetFX angegeben wird.
Als letzten Schritt müssen noch die Importanweisungen für die abweichenden Frameworks ausgetauscht werden. Auch an dieser Stelle bietet MSBuild, dank Conditions, alles was benötigt wird.
<!-- vorhandene Import Anweisung anpassen -->
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"
Condition="$(TargetFX) ==''" />
<!-- HINWEIS!! -->
<!-- Die Zeilenumbrüche innerhalb der Attribute dienen lediglich der Übersichtlichkeit!! -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\
$(TargetFrameworkVersion)\Microsoft.Silverlight.$(TargetFrameworkProfile).Overrides.targets"
Condition="$(TargetFX) == 'WP7'" />
<!-- HINWEIS!! -->
<!-- Die Zeilenumbrüche innerhalb der Attribute dienen lediglich der Übersichtlichkeit!! -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\
$(TargetFrameworkVersion)\Microsoft.Silverlight.CSharp.targets"
Condition="$(TargetFX) == 'WP7'" />
<!-- HINWEIS!! -->
<!-- Die Zeilenumbrüche innerhalb der Attribute dienen lediglich der Übersichtlichkeit!! -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight\
v4.0\Microsoft.Silverlight.CSharp.targets"
Condition="$(TargetFX) == 'Silverlight'" />
Die vorhandene Standard-C#-Import-Anweisung muss durch diese vier “gefilterten” Importanweisungen ausgetauscht werden. Mehr Anpassungen sind nicht notwendig. Das neue Buildscript sollte nun so aussehen
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetFX></TargetFX>
</PropertyGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{66A02AE2-91B4-4A70-AC92-1B41B73A09B2}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>DotNetRocks.MSBuildMultitargeting</RootNamespace>
<AssemblyName>DotNetRocks.MSBuildMultitargeting</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup
Condition="$(TargetFX) == 'Silverlight' OR $(TargetFX) == 'WP7'">
<SilverlightApplication>false</SilverlightApplication>
<TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
<SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>
<TargetFrameworkProfile
Condition="$(TargetFX) == 'WP7'">WindowsPhone</TargetFrameworkProfile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Log.cs" />
<Compile Include="ILogMessage.cs" />
<Compile Include="ILogTarget.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<!-- vorhandene Import Anweisung anpassen -->
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"
Condition="$(TargetFX) ==''" />
<!-- HINWEIS!! -->
<!-- Die Zeilenumbrüche innerhalb der Attribute dienen lediglich der Übersichtlichkeit!! -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\
$(TargetFrameworkVersion)\Microsoft.Silverlight.$(TargetFrameworkProfile).Overrides.targets"
Condition="$(TargetFX) == 'WP7'" />
<!-- HINWEIS!! -->
<!-- Die Zeilenumbrüche innerhalb der Attribute dienen lediglich der Übersichtlichkeit!! -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\
$(TargetFrameworkVersion)\Microsoft.Silverlight.CSharp.targets"
Condition="$(TargetFX) == 'WP7'" />
<!-- HINWEIS!! -->
<!-- Die Zeilenumbrüche innerhalb der Attribute dienen lediglich der Übersichtlichkeit!! -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight\
v4.0\Microsoft.Silverlight.CSharp.targets"
Condition="$(TargetFX) == 'Silverlight'" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
Last but not least - Verwenden des Buildscripts
Wenn man das Projekt in Visual Studio baut, so wird immer die Standard-Variante für das volle .NET Framework erstellt. Alle weiteren Frameworktypen werden über die MSBuild.exe adressiert.
msbuild DotNetRocks.MSBuildMultitargeting.csproj /p:TargetFX=Silverlight
msbuild DotNetRocks.MSBuildMultitargeting.csproj /p:TargetFX=WP7
Natürlich kann man an dieser Stelle noch mehrere Parameter setzen, wie zum Beispiel abweichende Output-Pfade usw… Aus Gründen der Lesbarkeit spare ich dies hier ein. :)
Abschließend könnt ihr hier noch das Projekt mit der angepassten Buildscriptvariante downloaden.
Happy building
Technorati-Markierungen:
MSBuild,
Silverlight,
WP7Dev