Mulitargeting C# project files with Mono and MonoDevelop
Asked Answered
K

1

8

I have a collection of csproj files that all refer to the same set of source files, but have slightly different target data making it so I need to keep the project files separate. E.g. there are WinPhone, XBox, Desktop, MonoTouch variants of the same project.

For things that really are duplicated, like the list of .cs files to compile, I'd like to consolidate the list into a single file so I don't keep having to make sure that all variations are kept in sync. I originally tried doing this by removing the sources from the .csprojs and putting them into a .targets file that got imported by all the csprojs, but that made the source files disappear from both VS and MonoDevelop.

My second attempt was by making the Desktop csproj file the primary one, and letting all the variations import that csproj with some conditional logic. This keeps the source files editable from the main csproj and they build into all flavors. Now Visual Studio understands what I was trying to do but MonoDevelop can't build it. In the MonoDevelop solution the iOS version of the core DLL is grayed out and says "(not built in active configuration)"

I've also tried xbuilding the csproj and solution, which seems to get past the problems that MonoDevelop has but hiccups on other things related to resolving monotouch assemblies. I had thought MonoDevelop used xbuild, but maybe not?

Since this works in the Windows msbuild it seems like it's either a bug or a not supported feature in Mono. Or maybe there's a better way to tackle the whole scenario... Thought I'd ask here.

For specifics, My Core.iOS.csproj file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<Project
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
    DefaultTargets="Build"
    ToolsVersion="4.0" >
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProductVersion>10.0.0</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{AE37B15F-F4BE-48DE-9F20-F00A601EC89E}</ProjectGuid>
    <ProjectTypeGuids>{6BC8ED88-2882-458C-8E55-DFD12B67127B};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
    <AssemblyName>Core.iOS</AssemblyName>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="monotouch" />
  </ItemGroup>
  <Import Project=".\Core.csproj" />
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

And my Core.csproj file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<Project
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
    DefaultTargets="Build"
    ToolsVersion="4.0">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Core</RootNamespace>
  </PropertyGroup>
  <!-- Using AssemblyName's presence to check for whether this is imported. --> 
  <PropertyGroup Condition=" '$(AssemblyName)' == '' ">
    <ProductVersion>8.0.50727</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{FC495BD8-11B1-46B0-A9DE-F245A0CBEE94}</ProjectGuid>
    <AssemblyName>Core</AssemblyName>
    <SignManifests>false</SignManifests>
  </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' ">
    <!-- properties similar to Debug -->
  </PropertyGroup>
  <ItemGroup Condition=" '$(Platform)' == 'AnyCPU' ">
    <Reference Include="System" />
    <Reference Include="System.Core" />
  </ItemGroup>
  <Import Condition=" '$(AssemblyName)' == 'Core' " Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
  <ItemGroup>
    <Compile Include="[...]" />
    <Compile Include="[...]" />
  </Project>

And, like I said, a variation of this seems to be working correctly when using VS Express for WinPhone and XBox360 projects. Is this something that should work? Is there a better way to do this?

Thanks,

Keener answered 28/4, 2012 at 21:36 Comment(2)
Looks good, and Monodevelop does use xbuild internally. What version do you have installed?Stapes
Thanks for looking. As I was researching this to answer the question myself, I had gotten the impression that MonoTouch projects bypassed xbuild and directly invoked mcs, but I may have misread it or been looking at an old article, but even if that was the case I don't know how much the IDE would use xbuild to decide what targets are valid... My version of MonoDevelop is 2.8.8.4. Please let me know if there's anything I can help with if you need a repro.Keener
P
9

Short answer:

This won't work because although MonoDevelop uses the MSBuild file format, it doesn't use the real MSBuild/xbuild engine for all project types yet. I'd suggest using links instead of an include.

Full background:

MonoDevelop has an old internal build engine that's derived from the SharpDevelop 1.0 build engine, i.e it predates the existence of MSBuild. We're in the process of migrating to MSBuild, but this has taken several stages, and is not yet complete.

A few years ago, MonoDevelop switched its project file format to a Visual Studio compatible subset of MSBuild. This was done by serializing/deserializing known MSBuild properties and items into MD's internal project model, but doing the build using the old build engine. This meant that any MSBuild projects that only used features accessible from the Visual Studio UI worked fine. However, it did not support the more advanced MSBuild features that are only accessible by hand-editing the MSBuild XML.

Later MD gained experimental support for using the xbuild/MSBuild build engine, but at the time xbuild was not mature, and it did not have MSBuild targets for all project types. It remained experimental, and build code for new project types (MonoTouch, etc) was written using the MD internal build engine.

Mono for Android needed to be supported in Visual Studio, so had to have MSBuild targets. Instead of writing and maintaining build code for two build engines, we finished up xbuild and MonoDevelop's MSBuild engine integration so it could be used for Mono for Android projects. However, we could not enable the xbuild build engine by default in MD, since many other project types did not yet have xbuild targets. Instead, we allowed project addins to force the use of the xbuild engine on a per-project-type basis.

This is essentially the current state - the xbuild engine is used for Mono for Android projects, and newer project types such as iPhone Binding projects and PLP projects, and is recommended for new project types. But older project types such as MonoTouch, MonoMac, ASP.NET etc have not yet been migrated at the time of writing.

Pasquale answered 30/4, 2012 at 3:48 Comment(3)
Is this still true with the new Xamarin 2 release?Belfry
Yes. See also mjhutchinson.com/journal/2012/08/19/…Pasquale
iOS, Mac and ASP.NET project types have now migrated to the MSBuild build engine. The Roslyn branch (not yet released) uses an MSBuild-based project model.Pasquale

© 2022 - 2024 — McMap. All rights reserved.