Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for source generators in Razor compiler/SDK #15756

Merged
merged 4 commits into from
Feb 11, 2021
Merged

Conversation

captainsafia
Copy link
Member

@captainsafia captainsafia commented Feb 9, 2021

Migrates the contents of dotnet/aspnetcore#28807 into the SDK repo with:

  • Fixes for packaging of source generator assemblies into outputs
  • Layout changes to output source-generators in SDK

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@captainsafia captainsafia added the Area-AspNetCore RazorSDK, BlazorWebAssemblySDK, dotnet-watch label Feb 9, 2021
@captainsafia
Copy link
Member Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@captainsafia captainsafia force-pushed the safia/rsg branch 2 times, most recently from 059e3f0 to 9a2b607 Compare February 9, 2021 20:31
@captainsafia captainsafia marked this pull request as ready for review February 10, 2021 00:12
@captainsafia
Copy link
Member Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

</Target>

<Target Name="MoveRazorSdkTools" AfterTargets="PublishRazorSdkTools">
<ItemGroup>
<_RazorToolOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor.Tool\$(Configuration)\$(SdkTargetFramework)\publish\*.*" />
<_MvcRazorExtensionOutput Include="$(ArtifactsBinDir)$(Configuration)\Sdks\Microsoft.NET.Sdk.Razor\tasks\$(SdkTargetFramework)\Microsoft.AspNetCore.Mvc.Razor.Extensions.dll" />
<_RazorSourceGeneratorsOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor.SourceGenerators\$(Configuration)\netstandard2.0\publish\*.*" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly relevant: dotnet/installer#9635 (comment).

src/RazorSdk/SourceGenerators/RazorSourceGenerator.cs Outdated Show resolved Hide resolved
}

Directory.CreateDirectory(Path.GetDirectoryName(file.GeneratedOutputPath));
File.WriteAllText(file.GeneratedOutputPath, csharpDocument.GeneratedCode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up: this compiles in to the assembly. There might be some runtime work involved here. See dotnet/aspnetcore#29862

<_TargetingNET60OrLater Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND
$([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '6.0')) ">true</_TargetingNET60OrLater>

<_UseRazorSourceGenerator Condition="'$(_TargetingNET60OrLater)' == 'true'">true</_UseRazorSourceGenerator>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a sense for what it would take to un-jankify this file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a stab at cleaning this up. Let me know what you think about using the Choose/When pattern for conditional setting these properties.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is alright. Maybe a further step would be to try and isolate < 5 and this further to make sure the build order is easier to grock across tfms and we don't accidentally break things.

@captainsafia captainsafia merged commit 12631a8 into master Feb 11, 2021
return null;
}

var fileWriteTime = File.GetLastWriteTimeUtc(portableExecutableReference.FilePath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible I'm misreading the flow here, but are we assuming that the last write time of a .dll on disk has some relationship to whether our cache is up-to-date?

What happens when someone changes a reference to an older version of a package (which was already cached locally) - don't we need to regenerate the compilation output then?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it's the same issue we've sorted multiple times with the Blazor SDK. We could make this more robust, but we're also waiting for better support for this sort of incrementality from the compiler (tracked dotnet/roslyn#51257).

Copy link
Member

@jaredpar jaredpar Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directly accessing the file system from a generator violates the assumptions we have around compilation. Performance is important but correctness is more so. 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could make this more robust

Yes, I'd think we need to do so without waiting for new compiler features. I don't know if there was already an issue filed to track this, so just in case I filed https://github.com/dotnet/sdk/issues/15913

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, now I realise that @captainsafia has already filed dotnet/aspnetcore#30234

Would it make sense to keep https://github.com/dotnet/sdk/issues/15913 open to track the actual correctness issue (which should be addressed in the short term), and dotnet/aspnetcore#30234 to track the broader performance/architectural issue of accessing the filesystem at all (which can wait longer)?

If @captainsafia or @pranavkm prefers there being just one issue for all of this, please feel free to close mine as a duplicate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SteveSandersonMS Having another issue is fine. If only to capture this particular side-effect of requiring read/write interactions to compute the cache. Ultimately, whatever work is done here will be usurped by what we do long term do deal with the FS-access issue.

var descriptor = new DiagnosticDescriptor(
razorDiagnostic.Id,
razorDiagnostic.GetMessage(CultureInfo.CurrentCulture),
razorDiagnostic.GetMessage(CultureInfo.CurrentCulture),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the right pattern here. The compiler doesn't generate diagnostics based on CultureInfo.CurrentCulture but rather by the culture that's passed into the compiler options. In general you should be using LocalizableResourceString here. That will do the heavy lifting for you.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tip -- I've tracked this in dotnet/aspnetcore#30237 since I don't think this will happen for preview2 but we can sort out the diagnostics story in preview3.

globalOptions.TryGetValue("build_property.DesignTimeBuild", out var designTimeBuild);
if (!globalOptions.TryGetValue("build_property._RazorReferenceAssemblyTagHelpersOutputPath", out var refsTagHelperOutputCachePath))
{
throw new InvalidOperationException("_RazorReferenceAssemblyTagHelpersOutputPath is not specified.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than throwing consider adding a diagnostic with error severity. That will produce a better customer experience here

}

Directory.CreateDirectory(Path.GetDirectoryName(file.GeneratedOutputPath));
File.WriteAllText(file.GeneratedOutputPath, csharpDocument.GeneratedCode);
Copy link
Member

@jaredpar jaredpar Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generators should not access the file system directly. That breaks several assumptions by the build system here. This is something we need to address.

Parallel.For(0, files.Count, GetParallelOptions(), i =>
{
var file = files[i];
if (File.GetLastWriteTimeUtc(file.GeneratedDeclarationPath) > File.GetLastWriteTimeUtc(file.AdditionalText.Path))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we accessing the file system here? Generators should not be interacting with the file system.

@jaredpar
Copy link
Member

Can we file an issue to track the direct access to the file system here? That is fine short term but near term it's something we need to address and find a different approach for.

@captainsafia
Copy link
Member Author

@jaredpar Sure! I've created dotnet/aspnetcore#30234 and jotted a few notes about where things stand long term with this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-AspNetCore RazorSDK, BlazorWebAssemblySDK, dotnet-watch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants