LA.27
LA.27

Reputation: 2238

MSBuild: check that referenced projects match convention

In my repository there are several projects. Some of them reference others in the standard way. As an example, my standard *.csproj file looks like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net7.0-windows</TargetFrameworks>
    <OutputType>Library</OutputType>
    <ProjectGuid>{6b123456-412f-7941-cfcc-f021137c2341}</ProjectGuid>
  </PropertyGroup>

  <ItemGroup>
    <Compile ... />
    <Compile ... />
    ...
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Project1\Project1.csproj" />
    <ProjectReference Include="..\Project2\Project2.csproj" />
  </ItemGroup>
</Project>

I want to add an MSBuild pre-build step which ensures that all the referenced projects meet specific convention. In my case this means that referenced projects' names can't end with ".Execute".

I started defining the Target, but I have no idea how to:

My current code:

  <Target Name="EnsureNonExecute" BeforeTargets="Build">
    <!-- How to iterate over the list of referenced projects? -->
  </Target>

Upvotes: 1

Views: 498

Answers (1)

Jonathan Dodds
Jonathan Dodds

Reputation: 5068

MSBuild is a declarative language. There are no iterative loops. Instead, MSBuild uses 'batching' to work with item lists. The ItemGroup element is used to create item lists.

In your project, an item list named ProjectReference is created that contains items for 'Project1' and 'Project2'.

For demonstration purposes let's say that ProjectReference has a project whose name ends with '.Execute', e.g.:

  <ItemGroup>
    <ProjectReference Include="..\Project1\Project1.csproj" />
    <ProjectReference Include="..\Project2\Project2.csproj" />
    <ProjectReference Include="..\Project3\Project3.Execute.csproj" />
  </ItemGroup>

An item has metadata. One of the standard metadata values is Filename, which is the filename without the file extension. The following target will display the Filename of each item by batching:

  <Target Name="EnsureNonExecute" BeforeTargets="Build">
    <Message Text="%(ProjectReference.Filename)" />
  </Target>

The output will be:

EnsureNonExecute:
  Project1
  Project2
  Project3.Execute

MSBuild properties support string methods like EndsWith() but metadata values are not properties. There is a common 'trick' to create a property from a metadata value. The static string Copy method is called passing the metadata value.

The EnsureNonExecute target may be written as follows:

  <Target Name="EnsureNonExecute" BeforeTargets="Build">
    <PropertyGroup>
      <ExecuteNameSuffix>.Execute</ExecuteNameSuffix>
    </PropertyGroup>
    <Error
      Text="Project $(MSBuildProjectFile) has a reference to &quot;@(ProjectReference)&quot;. Referenced projects' names can't end with &quot;$(ExecuteNameSuffix)&quot;."
      Condition="$([System.String]::Copy('%(Filename)').EndsWith('$(ExecuteNameSuffix)'))" />
  </Target>

If your project is named 'Fred.csproj', then the target will report an error as follows:

error : Project Fred.csproj has a reference to "../Project3/Project3.Execute.csproj". Referenced projects' names can't end with ".Execute".

I would suggest that you want to fail quick if the error condition exists. Instead of BeforeTargets="Build", consider BeforeTargets="BeforeBuild".

Upvotes: 2

Related Questions