Multiplatform Builds mit MSBuild

100_1979Wenn 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

Multitargeting1

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.

Multitargeting2

 

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: ,,
DotNetKicks-DE Image
Published Montag, 4. Oktober 2010 23:11 von ThorstenHans
Abgelegt unter: , ,

Kommentare

# re: Multiplatform Builds mit MSBuild

Dienstag, 5. Oktober 2010 08:39 von Benjamin

Hallo Thorsten,coole Sache!

Wir haben auch versucht das MSBuild Skript so ähnlich zu gestalten, allerdings haben wir dazu eigene Configurations verwendet z.B. "Desktop Debug" oder "Silverlight Release". Leider kommt aber VisualStudio nicht damit klar, wenn man die Configuration umschaltet und jeweils ein anderes Target einbindet. Letztendlich haben wir für jede Plattform eine eigene csproj und linken die Source Dateien in das Projekt, so sieht es auch hübsch in VS aus.

Benjamin

# Das MSBuild Universum - .NET rocks

Donnerstag, 24. Februar 2011 11:06 von Das MSBuild Universum - .NET rocks

Ping Antwort von  Das MSBuild Universum - .NET rocks

Kommentar abgeben

(verpflichtend) 
(verpflichtend) 
(optional)
(verpflichtend)