AArnott / CodeGeneration.Roslyn

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

Trying to generate my code gen analyzer build time package #104

Closed mwpowellhtx closed 5 years ago

mwpowellhtx commented 5 years ago

Hello, I'd like to generate my own Build Time package, and I think it must include a reference to CodeGeneration.Roslyn.BuildTime, right? I'd like to do so using the new CSPROJ furnished NuGet properties. Basically the project is a do-nothing, placeholder project that does nothing more than make the necessary references.

Previously, I was doing this using a nuspec file, but I would like to incorporate my BumpAssemblyVersions in the process.

What I am finding is that when I make the reference, BuildTime is trying to actually generate some code. Is there a way I can configure it not to do this, because at the moment things are failing. In other words, there is no source, there is nothing to generate. I just need the package reference so that it gets picked up by NuGet when packaging occurs.

Thanks!

AArnott commented 5 years ago

I'd be interested to know why you want your own build time package.

I think your question is how to reference my buildtime package from yours, such that you don't yourself consume it, but your downstream folks do. Is that it? If so, try this in your csproj:

   <PackageReference Include="CodeGeneration.Roslyn.BuildTime" PrivateAssets="none" ExcludeAssets="build" />

That should prevent you from consuming the build authoring that is breaking your build, while (hopefully) still propagating that build authoring to your own consumers.

mwpowellhtx commented 5 years ago

@AArnott Because I have a set of attributes, analyzers, etc, that I want to bundle in similar fashion... That depends on CodeGeneration.Roslyn.BuildTime, but which itself does not use it.

mwpowellhtx commented 5 years ago

@AArnott What is the purpose of the using and extern alias bits added in TransformationContext?

I furnished empty ranges for them and my unit tests seemed to work, though.

AArnott commented 5 years ago

Those are for those generators that need to add using or extern directives at the top of the generated C# file. Most generators probably don't need to do this, so empty lists are fine.

mwpowellhtx commented 5 years ago

Of course; if there's something conventional, that's probably a-ok. In my case, I think I am putting those in myself as a matter of course. But it's good to know.

Question is, how do you specify them by convention? I'm not sure my generator ever sees the TransformationContext, at least not directly, correct?

AArnott commented 5 years ago

Sure it does. When your generator is invoked, the TransformationContext is passed in:

https://github.com/AArnott/CodeGeneration.Roslyn/blob/95003217cec33f70f5a127e6da389b285ac63c59/src/CodeGeneration.Roslyn/ICodeGenerator.cs#L26

mwpowellhtx commented 5 years ago

No, my generator implements GenerateAsync. In turn is passed the context. I do not actually do the contextualization. In the unit test I do, of course; but not in the production code.

AArnott commented 5 years ago

I don't understand. I was answering your question:

I'm not sure my generator ever sees the TransformationContext, at least not directly, correct?

I was showing you how in fact your generator does see the TransformationContext, by virtue of being handed it when the method I quoted is invoked, which you implement.

amis92 commented 5 years ago

Actually, these properties from TransformationContext include usings and externs already added to the generated document (by previously invoked generators). I believe the comments are pretty clear:

https://github.com/AArnott/CodeGeneration.Roslyn/blob/95003217cec33f70f5a127e6da389b285ac63c59/src/CodeGeneration.Roslyn/TransformationContext.cs#L47-L51

These are necessary for an example scenario:

Generator A impements ICodeGenerator but depending on whether the generated code has using System.IO or not, it'll emit qualified or unqualified references to Path class. It can check that by accessing the context.CompilationUnitUsings collection.

Generator B wants to add using System.Linq at compilation unit level (top-level/document level), as it uses the extension methods from that namespace. So it implements IRichCodeGenerator and, if the context.CompilationUnitUsings doesn't contain that using yet, returns result with the using added to RichGenerationResult.Usings.

Now, if the generator B didn't check the using was already included in generated document, it'd add duplicate using, which is unnecessary and may lead to warnings etc.

mwpowellhtx commented 5 years ago

@amis92 You don't understand? From the GENERATOR's perspective. TransformationContext is passed into the generator. I do not actually have any control over that context, but at least at the moment I am implementing ICodeGenerator only.

My tests are invoking GenerateAsync, but I don't think I am actually creating any contexts in the actual production code. I just implement the GenerateAsync method.

@AArnott So the question stands. How do I specify the using bits, aliases, etc, for the context? The key is implementing IRichCodeGenerator instead of or in addition to ICodeGenerator?

mwpowellhtx commented 5 years ago

@amis92 Even the so-called "rich" interface does not create any contexts. Nor does it furnish any using bits.

Task<RichGenerationResult> GenerateRichAsync(TransformationContext context, IProgress<Diagnostic> progress, CancellationToken cancellationToken);
amis92 commented 5 years ago

You don't create the context, except for unit tests, as intended.

Thus, you don't specify (assign the values) of context's properties, including CompilationUnit -Usings and -Externs.

They are the context in which your generator is invoked. You cannot change it.

If your question is how to add compilation unit usings/externs to the generated document, you need to implement the IRichCodeGenerator and pass them in the return value. RichGenerationResult has the following assignable properties:

https://github.com/AArnott/CodeGeneration.Roslyn/blob/95003217cec33f70f5a127e6da389b285ac63c59/src/CodeGeneration.Roslyn/RichGenerationResult.cs#L15-L33

amis92 commented 5 years ago

As an implementation detail, if you're implementing IRichCodeGenerator, the ICodeGenerator.GenerateAsync method isn't invoked. It can throw.

mwpowellhtx commented 5 years ago

:bulb: I see it, thanks for pointing that out. Bit of a rabbit hole to be honest.

@amis92 I was following the RecordGenerator model concerning BuildTime. So far the only way I can see is to define my nuspec. I'm not sure there is a way for me to neatly stitch together the dependencies via the new csproj furnished method, at least not without incurring actual build-time Code Generation, which is what I don't want. My build-time assembly just needs to be a "do nothing" placeholder for the NuGet build-time packaging, I think.

Thoughts? Is there a way to circumvent and/or overcome the issue?

amis92 commented 5 years ago

Honestly, I think that this issue derailed into the issue/discussion about Rich Generation usage. It's hard to stitch together that with the original issue. Maybe rename that one to what it actually was about and open a new one that compiles all the things concerning the package you want to create? It's hard for me to connect the dots right now, since the context spans across several posts at the beginning and the one here.

mwpowellhtx commented 5 years ago

@amis92 Thoughts? Seems as though the nuspec approach is the best possible approach, but I was curious if it could be done with more up to date csproj package generation.

amis92 commented 5 years ago

Well, if you're generating a package without any dlls contained, just bundling/aggregating other references, I'd advise using nuspec, since it's more "appropriate" (it's not a C# project). If you insist on using csproj (and I can see why, I like it better as well and there's versioning tooling support), @AArnott's first comment should work?

If you want not to package any dlls (e.g. because they don't actually contain anything), I'm pretty sure there's an MSBuild switch to exclude packaging artifacts. In fact, what you're trying to achieve is already done for BuildTime package here:

https://github.com/AArnott/CodeGeneration.Roslyn/blob/95003217cec33f70f5a127e6da389b285ac63c59/src/CodeGeneration.Roslyn.Tasks/CodeGeneration.Roslyn.Tasks.csproj#L9-L10

amis92 commented 5 years ago

Closing in favour of #113