Monday, August 31, 2020

C# preprocessor directive symbols from the dotnet build command line via DefineConstants

Invoking the C# compiler directly allows one to pass in symbols for the preprocessor via a command option (-define or -d). But it's not at all obvious how to do this with the dotnet build command. There is no 'define' flag, so how do you do it?

Let me first show you how this works using the C# compiler directly:

Create a new file 'Program.cs' with this code:

using System;

namespace CscTest
{
    class Program
    {
        static void Main(string[] args)
        {
#if FOO
            Console.WriteLine("Hello FOO!");
#else
            Console.WriteLine("NOT FOO!");
#endif
        }
    }
}

Now compile it with CSC:

>csc -d:FOO Program.cs

And run it:

>Program
Hello FOO!

Happy days.

It is possible to do the same thing with dotnet build, it relies on populating the MSBuild DefineConstants property, but unfortunately one is not allowed to access this directly from the command line:

If you invoke this command:

dotnet build -v:diag -p:DefineConstants=FOO myproj.csproj

It has no effect, and somewhere deep in the diagnostic output you will find this line:

The "DefineConstants" property is a global property, and cannot be modified.

Instead one has to employ a little indirection. In your csproj file it is possible to populate DefineConstants. Create a project file, say 'CscTest.csproj', with a DefineConstants PropertyGroup element with the value FOO:

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <DefineConstants>FOO</DefineConstants>
  </PropertyGroup>

</Project>

Build and run it with dotnet run:

>dotnet run .
Hello FOO!

The csproj file is somewhat like a template, one can pass in arbitrary properties using the -p flag, so we can replace our hard coded FOO in DefineConstants with a property placeholder:

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <DefineConstants>$(MyOption)</DefineConstants>
  </PropertyGroup>

</Project>

And pass in FOO (or not) on the command line. Unfortunately it now means building and running as two individual steps:

>dotnet build -p:MyOption=FOO .
...
>dotnet run --no-build
Hello FOO!

And all is well with the world. It would be nice if the MSBuild team allowed preprocessor symbols to be added directly from the command line though.

1 comment:

Dave Shaw said...

Hi, for the dotnet cli you have to add quotes around the compile directive:

dotnet build /p:DefineConstants="FOO"

Works just fine.

IIRC it took me a while to figure this out.