Skip to content

Instantly share code, notes, and snippets.

@pcewing
Last active August 9, 2019 17:27
Show Gist options
  • Select an option

  • Save pcewing/9299d8d3e9a8e37db2b5ca38baa5433f to your computer and use it in GitHub Desktop.

Select an option

Save pcewing/9299d8d3e9a8e37db2b5ca38baa5433f to your computer and use it in GitHub Desktop.
Demo of microsoft/msbuild issue #4600 - Publish does not respect intermediate paths

Goal

Set up a .NET Core/Standard solution with multiple projects such that they all build and publish into a centralized directory without polluting the source directory.

Solution Setup

These setup steps should be followed before attempting to reproduce the issue using either method below.

$ mkdir MyApp && cd MyApp
$ mkdir src && cd src
$ dotnet new console -o MyApp.Cli
$ dotnet new classlib -o MyApp.Lib
$ dotnet new mstest -o MyApp.Tests
$ dotnet new sln -n MyApp
$ dotnet sln add MyApp.*/*.csproj
$ cd ..

Method 1 - Directory.Build.props

In this method, we try to define the output/publish paths in Directory.Build.props file.

Setup

Create the ./src/Directory.Build.props file and add the output/publish path properties:

<Project>
  <PropertyGroup>
    <PublishDir>$(MSBuildThisFileDirectory)\..\build\publish\$(MSBuildProjectName)</PublishDir>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <OutputPath>$(MSBuildThisFileDirectory)\..\build\$(MSBuildProjectName)\bin\Release</OutputPath>
    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\..\build\$(MSBuildProjectName)\obj\Release</IntermediateOutputPath>
    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)\..\build\$(MSBuildProjectName)\obj\Release</BaseIntermediateOutputPath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <OutputPath>$(MSBuildThisFileDirectory)\..\build\$(MSBuildProjectName)\bin\Debug</OutputPath>
    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\..\build\$(MSBuildProjectName)\obj\Debug</IntermediateOutputPath>
    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)\..\build\$(MSBuildProjectName)\obj\Debug</BaseIntermediateOutputPath>
  </PropertyGroup>
</Project>

Test

Build the solution:

$ dotnet build src/MyApp.sln
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restoring packages for /home/foo/MyApp/src/MyApp.Cli/MyApp.Cli.csproj...
  Restoring packages for /home/foo/MyApp/src/MyApp.Lib/MyApp.Lib.csproj...
  Restoring packages for /home/foo/MyApp/src/MyApp.Tests/MyApp.Tests.csproj...
  Generating MSBuild file /home/foo/MyApp/src/../build/MyApp.Lib/obj/Debug/MyApp.Lib.csproj.nuget.g.props.
  Generating MSBuild file /home/foo/MyApp/src/../build/MyApp.Cli/obj/Debug/MyApp.Cli.csproj.nuget.g.props.
  Generating MSBuild file /home/foo/MyApp/src/../build/MyApp.Lib/obj/Debug/MyApp.Lib.csproj.nuget.g.targets.
  Generating MSBuild file /home/foo/MyApp/src/../build/MyApp.Cli/obj/Debug/MyApp.Cli.csproj.nuget.g.targets.
  Restore completed in 153.15 ms for /home/foo/MyApp/src/MyApp.Lib/MyApp.Lib.csproj.
  Restore completed in 153.15 ms for /home/foo/MyApp/src/MyApp.Cli/MyApp.Cli.csproj.
  Generating MSBuild file /home/foo/MyApp/src/../build/MyApp.Tests/obj/Debug/MyApp.Tests.csproj.nuget.g.props.
  Generating MSBuild file /home/foo/MyApp/src/../build/MyApp.Tests/obj/Debug/MyApp.Tests.csproj.nuget.g.targets.
  Restore completed in 223.42 ms for /home/foo/MyApp/src/MyApp.Tests/MyApp.Tests.csproj.
  MyApp.Lib -> /home/foo/MyApp/build/MyApp.Lib/bin/Debug/netstandard2.0/MyApp.Lib.dll
  MyApp.Tests -> /home/foo/MyApp/build/MyApp.Tests/bin/Debug/netcoreapp2.2/MyApp.Tests.dll
  MyApp.Cli -> /home/foo/MyApp/build/MyApp.Cli/bin/Debug/netcoreapp2.2/MyApp.Cli.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.48

As a result, all of the build artifacts are correctly placed in the ./build directory:

$ find . -iname 'bin' -or -iname 'obj'
./build/MyApp.Tests/obj
./build/MyApp.Tests/bin
./build/MyApp.Cli/obj
./build/MyApp.Cli/bin
./build/MyApp.Lib/obj
./build/MyApp.Lib/bin

Publish the console application project:

dotnet publish src/MyApp.Cli/MyApp.Cli.csproj 
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restoring packages for /home/foo/MyApp/src/MyApp.Cli/MyApp.Cli.csproj...
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Cli/obj/MyApp.Cli.csproj.nuget.g.props.
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Cli/obj/MyApp.Cli.csproj.nuget.g.targets.
  Restore completed in 119.63 ms for /home/foo/MyApp/src/MyApp.Cli/MyApp.Cli.csproj.
  MyApp.Cli -> /home/foo/MyApp/src/MyApp.Cli/bin/Debug/netcoreapp2.2/MyApp.Cli.dll
  MyApp.Cli -> /home/foo/MyApp/build/publish/MyApp.Cli/

Notice this line in the output where the intermediate dll is written in the source tree instead of the intermediate output path:

MyApp.Cli -> /home/foo/MyApp/src/MyApp.Cli/bin/Debug/netcoreapp2.2/MyApp.Cli.dll

And if we look at our working directory, bin and obj folder directories exist in the src tree:

$find . -iname 'bin' -or -iname 'obj'
./build/MyApp.Tests/obj
./build/MyApp.Tests/bin
./build/MyApp.Cli/obj
./build/MyApp.Cli/bin
./build/MyApp.Lib/obj
./build/MyApp.Lib/bin
./src/MyApp.Cli/obj
./src/MyApp.Cli/bin

Method 2 - SDK Import Order

In this method, we try to define the output/publish paths in the project files directly and change how the SDK is imported.

Setup

Add path properties to project files and make sure SDK targets are imported in the correct order:

./src/MyApp.Cli/MyApp.Cli.csproj

-<Project Sdk="Microsoft.NET.Sdk">
+<Project>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
+    <PublishDir>$(MSBuildThisFileDirectory)\..\..\build\publish\$(MSBuildProjectName)</PublishDir>
  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <OutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\bin\Release</OutputPath>
+    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Release</IntermediateOutputPath>
+    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Release</BaseIntermediateOutputPath>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <OutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\bin\Debug</OutputPath>
+    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Debug</IntermediateOutputPath>
+    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Debug</BaseIntermediateOutputPath>
+  </PropertyGroup>

+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />
+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />
</Project>

./src/MyApp.Lib/MyApp.Lib.csproj

-<Project Sdk="Microsoft.NET.Sdk">
+<Project>

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <OutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\bin\Release</OutputPath>
+    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Release</IntermediateOutputPath>
+    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Release</BaseIntermediateOutputPath>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <OutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\bin\Debug</OutputPath>
+    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Debug</IntermediateOutputPath>
+    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Debug</BaseIntermediateOutputPath>
+  </PropertyGroup>

+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />
+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />
</Project>

./src/MyApp.Tests/MyApp.Tests.csproj

-<Project Sdk="Microsoft.NET.Sdk">
+<Project>

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <OutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\bin\Release</OutputPath>
+    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Release</IntermediateOutputPath>
+    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Release</BaseIntermediateOutputPath>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <OutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\bin\Debug</OutputPath>
+    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Debug</IntermediateOutputPath>
+    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)\..\..\build\$(MSBuildProjectName)\obj\Debug</BaseIntermediateOutputPath>
+  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
    <PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
  </ItemGroup>

+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />
+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />
</Project>

Test

Build the solution:

$ dotnet build src/MyApp.sln
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restoring packages for /home/foo/MyApp/src/MyApp.Cli/MyApp.Cli.csproj...
  Restoring packages for /home/foo/MyApp/src/MyApp.Tests/MyApp.Tests.csproj...
  Restoring packages for /home/foo/MyApp/src/MyApp.Lib/MyApp.Lib.csproj...
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Cli/../../build/MyApp.Cli/obj/Debug/MyApp.Cli.csproj.nuget.g.props.
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Lib/../../build/MyApp.Lib/obj/Debug/MyApp.Lib.csproj.nuget.g.props.
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Cli/../../build/MyApp.Cli/obj/Debug/MyApp.Cli.csproj.nuget.g.targets.
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Lib/../../build/MyApp.Lib/obj/Debug/MyApp.Lib.csproj.nuget.g.targets.
  Restore completed in 154.76 ms for /home/foo/MyApp/src/MyApp.Cli/MyApp.Cli.csproj.
  Restore completed in 154.77 ms for /home/foo/MyApp/src/MyApp.Lib/MyApp.Lib.csproj.
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Tests/../../build/MyApp.Tests/obj/Debug/MyApp.Tests.csproj.nuget.g.props.
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Tests/../../build/MyApp.Tests/obj/Debug/MyApp.Tests.csproj.nuget.g.targets.
  Restore completed in 233.18 ms for /home/foo/MyApp/src/MyApp.Tests/MyApp.Tests.csproj.
  MyApp.Lib -> /home/foo/MyApp/build/MyApp.Lib/bin/Debug/netstandard2.0/MyApp.Lib.dll
  MyApp.Tests -> /home/foo/MyApp/build/MyApp.Tests/bin/Debug/netcoreapp2.2/MyApp.Tests.dll
  MyApp.Cli -> /home/foo/MyApp/build/MyApp.Cli/bin/Debug/netcoreapp2.2/MyApp.Cli.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.98

As a result, all of the build artifacts are again correctly placed in the ./build directory:

$ find . -iname 'bin' -or -iname 'obj'
./build/MyApp.Tests/obj
./build/MyApp.Tests/bin
./build/MyApp.Cli/obj
./build/MyApp.Cli/bin
./build/MyApp.Lib/obj
./build/MyApp.Lib/bin

Publish the console application project:

$ dotnet publish src/MyApp.Cli/MyApp.Cli.csproj 
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restoring packages for /home/foo/MyApp/src/MyApp.Cli/MyApp.Cli.csproj...
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Cli/obj/MyApp.Cli.csproj.nuget.g.props.
  Generating MSBuild file /home/foo/MyApp/src/MyApp.Cli/obj/MyApp.Cli.csproj.nuget.g.targets.
  Restore completed in 117.97 ms for /home/foo/MyApp/src/MyApp.Cli/MyApp.Cli.csproj.
  MyApp.Cli -> /home/foo/MyApp/src/MyApp.Cli/bin/Debug/netcoreapp2.2/MyApp.Cli.dll
  MyApp.Cli -> /home/foo/MyApp/build/publish/MyApp.Cli/

Notice this line in the output where the intermediate dll is written in the source tree instead of the intermediate output path:

MyApp.Cli -> /home/foo/MyApp/src/MyApp.Cli/bin/Debug/netcoreapp2.2/MyApp.Cli.dll
$find . -iname 'bin' -or -iname 'obj'
./build/MyApp.Tests/obj
./build/MyApp.Tests/bin
./build/MyApp.Cli/obj
./build/MyApp.Cli/bin
./build/MyApp.Lib/obj
./build/MyApp.Lib/bin
./src/MyApp.Cli/obj
./src/MyApp.Cli/bin
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment