10
votes

Related

I have two projects in my VS solution, BookApp.Web and BookApp.Domain.

BookApp.Web references BookApp.Domain.

BookApp.Web has the following build configurations: debug, staging, prod-eu, prod-us and prod-as. We have three datacenters for production and a staging environment.

BookApp.Domain so far only has two build configurations, debug.

When building the solution from within Visual Studio, I can use the build configurator to make sure that no matter what build config is selected for the Web project, the debug config is always used for the Domain project.

However, when building with MSBuild on my continuous integration server, things go wrong. I use this in my rollout.msbuild file:

<MSBuild Projects="BookApp.Web\BookApp.Web.csproj" Properties="Configuration=Prod-us" />

When I run this, MSBuild expects all dependent projects to have the same build configuration. Since that's not the case (and shouldn't be IMO), it fails with this error message:

The OutputPath property is not set for project 'BookApp.Domain.csproj'.  Please check to make sure that you have specified a valid combination of Configuration and Platform for this project.  Configuration='Prod-us'  Platform='AnyCPU'.

An answer to a related question suggests creating separate .sln solutions for each build configuration and running that with MSBuild. To me that doesn't sound like a good idea.

Copying all the build configurations to the Domain project is also not ideal.

Is there a better way of telling MSBuild to use different build configs?

1

1 Answers

2
votes

Have a look at this answer it explains how the configurations are passed from project to Project through the MSBuild Task and Using the MetaData of the configuration to pass the desired configuration for the target project

here

UPDATE

I created a Solution with a class library(Sample.Domain) and ConsoleApplication(SampleApp.Console). I added two more configurations to the SamplApp.Console: prod-us;prod-eu, Sample.Domain remained with debug;release.

I then Changed the csproj file of the ConsoleApplication, like so:

ProjectReferences

  <!--<ItemGroup>
    <ProjectReference Include="..\Sample.Domain\Sample.Domain.csproj">
      <Project>{73e8a7fd-0a24-47c5-a527-7601550d4b92}</Project>
      <Name>Sample.Domain</Name>
    </ProjectReference>
  </ItemGroup>-->

  <ItemGroup>
    <ProjectReference Include="..\Sample.Domain\Sample.Domain.csproj" >
      <Targets>Build</Targets>
    </ProjectReference>
  </ItemGroup>

Added a switch case on the configuration passed to MSBuild, to configure some properties for Outputfiles and reference files:

  <Choose>
    <When Condition="'$(Configuration)' != 'Debug'">
      <PropertyGroup>
        <OutputProperty>$(OutputPath)\$(Configuration)</OutputProperty>
        <FileCopy>$(OutputProperty)</FileCopy>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup>
        <OutputProperty>$(OutputPath)</OutputProperty>
        <FileCopy>$(OutputProperty)</FileCopy>
      </PropertyGroup>
    </Otherwise>
  </Choose>

Created a Target to switch the Configuration passed to MSBuild, so that Debug will pass Debug to Sample.Domain, everything else it will pass Release

  <Target Name="MultiConfiguration" >
    <CreateProperty Value="Debug">
      <Output TaskParameter="Value" PropertyName="LibConfiguration" Condition="'$(Configuration)' == 'Debug'"/>
    </CreateProperty>

    <CreateProperty Value="Release">
      <Output TaskParameter="Value" PropertyName="LibConfiguration" Condition="'$(Configuration)' != 'Debug' "/>
    </CreateProperty>
  </Target>

The Build Target uses the Properties we have added so the Output and Copy of references files will have the right values according to Configuration value

  <!--Build Process-->
  <Target Name="Build" DependsOnTargets="Clean;MultiConfiguration;ComputeProjectReference" >
    <Csc Sources="@(Compile)" References="@(NewAssemblies)" TargetType="exe" OutputAssembly="$(OutputProperty)\$(AssemblyName).exe"/>
  </Target>

  <Target Name="ComputeProjectReference" Inputs="@(ProjectReference)" Outputs="%(ProjectReference.Identity)__Forced">
    <MSBuild Projects="@(ProjectReference)" Targets="%(ProjectReference.Targets)" Properties="Configuration=$(LibConfiguration);Platform=AnyCPU;OutputPath=bin\$(LibConfiguration)">
      <Output TaskParameter="TargetOutputs" ItemName="ResolvedProjectReferences"/>
    </MSBuild>
  </Target>

  <Target Name="AfterProjectReference"  AfterTargets="ComputeProjectReference">
    <CreateItem Include="@(ResolvedProjectReferences)">
      <Output TaskParameter="Include" ItemName="CopyFiles" />
    </CreateItem>

    <Copy SourceFiles="@(CopyFiles)" DestinationFolder="$(FileCopy)" SkipUnchangedFiles="false"  />

    <ItemGroup>
      <NewAssemblies Include="$(OutputProperty)\%(CopyFiles.FileName)%(CopyFiles.Extension)" />
    </ItemGroup>
  </Target>

To call the Debug configuration is done like this msbuild SampleApp.Console.csproj

To call (Release;prod-us;prod-eu;...) is done like this msbuild SampleApp.Console.csproj /p:Configuration="prod-us" /p:OutputPath="bin"

I'm sure it can be optimized, and might be some way easier, but it works.