Skip to content
This repository has been archived by the owner on Dec 12, 2020. It is now read-only.

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

Closed
mwpowellhtx opened this issue Oct 14, 2018 · 18 comments
Closed

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

mwpowellhtx opened this issue Oct 14, 2018 · 18 comments

Comments

@mwpowellhtx
Copy link

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!

@mwpowellhtx mwpowellhtx changed the title Trying to generate my code gen analyzer built time package Trying to generate my code gen analyzer build time package Oct 14, 2018
@AArnott
Copy link
Owner

AArnott commented Oct 14, 2018

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
Copy link
Author

@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
Copy link
Author

@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
Copy link
Owner

AArnott commented Oct 15, 2018

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
Copy link
Author

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
Copy link
Owner

AArnott commented Oct 15, 2018

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

Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationContext context, IProgress<Diagnostic> progress, CancellationToken cancellationToken);

@mwpowellhtx
Copy link
Author

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
Copy link
Owner

AArnott commented Oct 15, 2018

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
Copy link
Collaborator

amis92 commented Oct 15, 2018

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:

/// <summary>Gets a collection of using directives already queued to be generated.</summary>
public IEnumerable<UsingDirectiveSyntax> CompilationUnitUsings { get; }
/// <summary>Gets a collection of extern aliases already queued to be generated.</summary>
public IEnumerable<ExternAliasDirectiveSyntax> CompilationUnitExterns { get; }

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
Copy link
Author

@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
Copy link
Author

@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
Copy link
Collaborator

amis92 commented Oct 15, 2018

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:

/// <summary>
/// Gets or sets the <see cref="MemberDeclarationSyntax"/> to add to generated <see cref="CompilationUnitSyntax"/>.
/// </summary>
public SyntaxList<MemberDeclarationSyntax> Members { get; set; }
/// <summary>
/// Gets or sets the <see cref="UsingDirectiveSyntax"/> to add to generated <see cref="CompilationUnitSyntax"/>.
/// </summary>
public SyntaxList<UsingDirectiveSyntax> Usings { get; set; }
/// <summary>
/// Gets or sets the <see cref="ExternAliasDirectiveSyntax"/> to add to generated <see cref="CompilationUnitSyntax"/>.
/// </summary>
public SyntaxList<ExternAliasDirectiveSyntax> Externs { get; set; }
/// <summary>
/// Gets or sets the <see cref="AttributeListSyntax"/> to add to generated <see cref="CompilationUnitSyntax"/>.
/// </summary>
public SyntaxList<AttributeListSyntax> AttributeLists { get; set; }

@amis92
Copy link
Collaborator

amis92 commented Oct 15, 2018

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

@mwpowellhtx
Copy link
Author

💡 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
Copy link
Collaborator

amis92 commented Oct 15, 2018

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
Copy link
Author

@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
Copy link
Collaborator

amis92 commented Oct 15, 2018

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:

<IncludeBuildOutput>false</IncludeBuildOutput>
<DevelopmentDependency>true</DevelopmentDependency>

@amis92
Copy link
Collaborator

amis92 commented Jan 22, 2019

Closing in favour of #113

@amis92 amis92 closed this as completed Jan 22, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants