0

What's the simple/idiomatic way of passing a value using msbuild /p:MyValue=Foo at compile time, and using it at run time?

The resulting program looks like this

System.Console.WriteLine($"Value is '{Something.MyValue}'");

And if it is built with

msbuild /p:MyValue=Foo

then the output is

Value is 'Foo'

And if it is compiled without the parameter then the output is

Value is ''

The requirements are

  • If the value is not passed, the compilation should still succeed, and the value should be e.g. null for a string.
  • It must have no detrimental effect on up2date checks for the csproj.
  • Ideally it should be something simpler than hand-rolling an msbuild task to dump a generated g.cs file into the obj directory. That has too many moving parts wrt. stale values and so on.
  • Has to be portable between net6+ and net48
Anders Forsgren
  • 10,827
  • 4
  • 40
  • 77
  • 2
    Well, you could use assembly attributes, as per https://stackoverflow.com/questions/10980249 - that *does* end up dumping a generated g.cs file into the obj directory, but it's a well-known path which doesn't require hand-rolling an msbuild task. It also involves retrieving the attribute at execution time, which you may well want to add an abstraction for. – Jon Skeet Oct 28 '22 at 06:36
  • Frankly, I would suggest doing exactly, what you sort of ruled out with your last bullet point: write a (simple!) MSBuild task that creates a source file, that you include in your build, that contains the definitions, eg. `public const string Whatever = "FromCommandLine"`, you need. That might be easier to understand than using existing stuff, that was not meant for it. – Christian.K Oct 28 '22 at 12:07
  • I just fear there are lots of foogtuns when ot comes to stale values, FastUp2DateCheck etc (lots of scars from slow builds when using generated code). What's the best practice for e.g. Api keys and similar when they can't be Env vars on a server because e.g. it's a mobile app, but you still want to keep them out of source control? That is my use case here. – Anders Forsgren Oct 28 '22 at 12:27

1 Answers1

0

If you want the thing to be very configurable, you should go with Roslyn Source Generators.

You can create a new project containing roslyn analyzers / source generators etc in your solution. Once you have a ISourceGenerator loading up and generating code, the next task is to pass the MSbuild variable to it. You can achieve this via the item group in msbuild.

<!-- inside you .csproj that has your program -->
<PropertyGroup>
  <Foo>Bar</Foo>
</PropertyGroup>
<ItemGroup>
  <CompilerVisibleProperty Include="Foo" />

  <!-- reference the project that implements the source generator --> 
  <ProjectReference
        Include="MyAnalyzers.csproj"
        PrivateAssets="all"
        ReferenceOutputAssembly="false"
        OutputItemType="Analyzer" />
</ItemGroup>

The source generator implementation can then use it like this:

void ISourceGenerator.Execute(GeneratorExecutionContext context)
{
    if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(
            "build_property.Foo",
            out var fooValue))
    {
        // add code with that generates Something.MyValue = fooValue to your compilation
    }
}

There are some gotchas/bugs around the encoding of the property value at the moment. Then you can do msbuild /p:Foo=Baz to override Foo's value, that gets passed to the code generator.

m0sa
  • 10,712
  • 4
  • 44
  • 91