AArnott / CodeGeneration.Roslyn

Assists in performing Roslyn-based code generation during a build.
Microsoft Public License
408 stars 60 forks source link

feat: Allow override of the generated files output path #228

Closed robkroll closed 4 years ago

robkroll commented 4 years ago

Allow override of the output directory where CGR.Tool writes generated files. Defaults to existing value $(IntermediateOutputPath) so no change for existing users.

Partially fixes #95

amis92 commented 4 years ago

I'd also like to suggest that maybe a warning should be raised (with a code to help suppression) when:

  1. There are multiple TargetFrameworks specified and TargetFramework value is not part of the specified Path.
  2. Configuration value is not part of the Path.

These two cases will result in files being overwritten and an undefined result in the end.

robkroll commented 4 years ago

Thanks for the feedback, I'll work on those changes. One question in the meantime: why does CGR generate an AssemblyAttributes file (.e.g .NETStandard,Version=v2.0.AssemblyAttributes.APNPx2rD.generated.cs).

The file content is only the auto-generated banner and then:

using System;
using System.Reflection;

But it seems that this file is picked up for generation again by CGR. So if the CodeGenerationRoslynToolOutputPath folder is set to $(MsBuildProjectFolder) it will regenerate this file over and over (appending .generated.generated...) until it reaches the 260 char limit and breaks.

amis92 commented 4 years ago

So if the CodeGenerationRoslynToolOutputPath folder is set to $(MsBuildProjectFolder) it will regenerate this file over and over (appending .generated.generated...) until it reaches the 260 char limit and breaks.

This will be the case anyway if you'll not exclude those files from default Compile include manually. This should also probably be highlighted in documentation.

CGR runs before CoreCompile (at which time the AssemblyAttributes.cs is already generated), and it takes in all Compile items (ItemGroup->Compile).

So, by default, if you set the CodeGenerationRoslynToolOutputPath to $(MsBuildProjectFolder), only running build once is a supported scenario (and only if you're not multitargeting).

Because every subsequent build will include the generated files in the Compile item by default, and we'll generate duplicates of those files, as you've noticed.

Our compilation setup wasn't ever meant to have the files saved to the project directory. It's designed from the ground up as a per-Compilation runner.

robkroll commented 4 years ago

CGR runs before CoreCompile (at which time the AssemblyAttributes.cs is already generated), and it takes in all Compile items (ItemGroup->Compile).

Ok, so all files are passed to CGR.Tool before the compile. But if a generator outputs, say, an empty class definition (and the generated class is therefore not attributed for code generation) it will get included in a subsequent call to CGR.Tool, but as it's not marked for generation it's not passed to a generator and nothing happens with it -- no new files are produced.

Only if a generated file is attributed for code generation do we get this recursive .generated.generated.generated... problem. For example this happens with the DuplicateWithSuffixGenerator from the example. This for sure would be worth documenting.

Except for the AssemblyAttributes.cs file, this seems to be generated regardless.

amis92 commented 4 years ago

as it's not marked for generation it's not passed to a generator and nothing happens with it -- no new files are produced

Speculating here (no proof), but I'm pretty sure we create the new files no matter if there were any generators run or trigger attributes found. It may be the case that they get re-generated and overwritten. It all just adds to this being a totally not supported scenario. :(

amis92 commented 4 years ago

I think we could mitigate at least partially the infinite-regeneration scenario by changing slightly how we consume the Compile list:

  1. Map input paths to generated paths
  2. Exclude input paths that exist in the generated paths collection.
  3. Generate g.cs only for items left on Compile (filtered).

@robkroll would you be interested in adding this in this PR?

@AArnott do you think this'd work?

AArnott commented 4 years ago

Yes, I think that's promising @amis92. Another option, IIRC, is that we only generate for "all" source files right now because of an ItemDefinitionGroup that turns it on by default for all Compile items. A project that's doing something special like @robkroll proposes likely wants to turn this default off and explicitly select with metadata which Compile items should actually result in code generation.