By utility I mean a project that does not have any C# files, does not produce a .NET assembly, but implements some custom build logic.
I could have arranged it as an AfterBuild target in the C# project of interest, but I do not want to increase the build time of that C# project. Instead, I want msbuild to run this logic in parallel with other dependents of that C# project.
One solution would be to create a dummy C# project that would truly build some dummy code and put my logic in the AfterBuild target. But that is ugly.
So, here is my solution (Spoiler Alert - it does not work):
Directory structure
C:\work\u [master]> tree /F
Folder PATH listing for volume OSDisk
Volume serial number is F6C4-7BEF
C:.
│ .gitignore
│ Deployer.sln
│
├───Deployer
│ Deployer.csproj
│
├───DeploymentEngine
│ DeploymentEngine.csproj
│
└───Utility
Utility.csproj
C:\work\u [master]>
Deployer.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{B451936B-54B7-41D1-A359-4B06865248CE}</ProjectGuid>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<OutputType>Library</OutputType>
<BaseOutputPath>bin</BaseOutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DeploymentEngine\DeploymentEngine.csproj">
<Project>{901487BE-C604-4251-8485-3E96D5993145}</Project>
<Name>DeploymentEngine</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="TakeTime" AfterTargets="Build">
<Exec Command="powershell -NoProfile -Command Start-Sleep -Seconds 5" />
</Target>
</Project>
Yes, it is a legacy style project because the real solution is a mix of legacy and SDK style projects.
DeploymentEngine.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<Target Name="TakeTime" AfterTargets="Build">
<Exec Command="powershell -NoProfile -Command Start-Sleep -Seconds 5" />
</Target>
</Project>
Utility.csproj
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<EnableDefaultItems>False</EnableDefaultItems>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
</PropertyGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<Target Name="Build">
<Message Text="*** Good" Importance="high" Condition="Exists('..\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll')" />
<Message Text="*** Bad" Importance="high" Condition="!Exists('..\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll')" />
</Target>
<Target Name="Clean" />
<Target Name="Rebuild" DependsOnTargets="Clean;Build" />
<ItemGroup>
<ProjectReference Include="..\DeploymentEngine\DeploymentEngine.csproj" />
</ItemGroup>
</Project>
Deployer.sln
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31205.134
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deployer", "Deployer\Deployer.csproj", "{B451936B-54B7-41D1-A359-4B06865248CE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeploymentEngine", "DeploymentEngine\DeploymentEngine.csproj", "{901487BE-C604-4251-8485-3E96D5993145}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utility", "Utility\Utility.csproj", "{9369D18D-D81D-4CA3-A287-C62C89BFB751}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B451936B-54B7-41D1-A359-4B06865248CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B451936B-54B7-41D1-A359-4B06865248CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B451936B-54B7-41D1-A359-4B06865248CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B451936B-54B7-41D1-A359-4B06865248CE}.Release|Any CPU.Build.0 = Release|Any CPU
{901487BE-C604-4251-8485-3E96D5993145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{901487BE-C604-4251-8485-3E96D5993145}.Debug|Any CPU.Build.0 = Debug|Any CPU
{901487BE-C604-4251-8485-3E96D5993145}.Release|Any CPU.ActiveCfg = Release|Any CPU
{901487BE-C604-4251-8485-3E96D5993145}.Release|Any CPU.Build.0 = Release|Any CPU
{9369D18D-D81D-4CA3-A287-C62C89BFB751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9369D18D-D81D-4CA3-A287-C62C89BFB751}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9369D18D-D81D-4CA3-A287-C62C89BFB751}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9369D18D-D81D-4CA3-A287-C62C89BFB751}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A70FF6AB-85B1-49F0-B2B0-25E20256A88F}
EndGlobalSection
EndGlobal
Notes:
- I placed an artificial delay into the two "real" C# projects.
- The Utility project outputs
*** Bad
when it is run NOT after its declared dependency, i.e. NOT after the DeploymentEngine project.
Now let us run it:
C:\work\u [master]> git clean -qdfx ; msbuild /v:m /restore /m
Microsoft (R) Build Engine version 16.11.0+0538acc04 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
Restored C:\work\u\DeploymentEngine\DeploymentEngine.csproj (in 171 ms).
Restored C:\work\u\Utility\Utility.csproj (in 172 ms).
*** Bad
DeploymentEngine -> C:\work\u\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll
CSC : warning CS2008: No source files specified. [C:\work\u\Deployer\Deployer.csproj]
Deployer -> C:\work\u\Deployer\bin\Debug\Deployer.dll
C:\work\u [master]>
The output indicates the Utility project was built first, despite the declared intent of depending on the DeploymentEngine project.
Notice, if I run the build single threaded the output will be *** Good
, so the output logic does work correctly:
C:\work\u [master]> git clean -qdfx ; msbuild /v:m /restore
Microsoft (R) Build Engine version 16.11.0+0538acc04 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
Restored C:\work\u\Utility\Utility.csproj (in 172 ms).
Restored C:\work\u\DeploymentEngine\DeploymentEngine.csproj (in 172 ms).
DeploymentEngine -> C:\work\u\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll
CSC : warning CS2008: No source files specified. [C:\work\u\Deployer\Deployer.csproj]
Deployer -> C:\work\u\Deployer\bin\Debug\Deployer.dll
*** Good
C:\work\u [master]>
So just declaring ProjectReference
is not enough. Seems like I should implement some kind of a target to make it work.
So what am I missing? What should I add to let msbuild know that the Utility project must be built after the DeploymentEngine ?
EDIT 1
I know I can set dependencies in the solution file. However, I do not want to do it for various reasons.
EDIT 2
My ultimate goal is to have a bare bones utility project that runs after one or more "real" C# projects. I.e. as few .NET build imports as possible. And if it could have the .proj
extension, rather than .csproj
- the best.