Skip to content

External output path (1.1.0) - #2

Merged
TarasKovalenko merged 4 commits into
mainfrom
feat/external-output-path
Jul 26, 2026
Merged

External output path (1.1.0)#2
TarasKovalenko merged 4 commits into
mainfrom
feat/external-output-path

Conversation

@TarasKovalenko

Copy link
Copy Markdown
Owner

No description provided.

A source generator can only add code to the project it runs in, which does
not work for Clean Architecture layouts where EF configurations live in
Infrastructure but the constants belong in Domain. The generator now also
writes the constants to a file in another project, which that project
compiles as normal checked-in code.

Configured with MSBuild properties, made compiler visible by a targets file
shipped in the package: EntityLengthsOutputPath, EntityLengthsOutputNamespace
and EntityLengthsEmitInProjectOutput. In-project output is turned off as soon
as an output path is set, otherwise the same type would exist in two
assemblies. The file is only touched when its content changes, and write
failures are reported as ELG0001 instead of throwing out of the generator.

Also fixes issues found while reviewing the pipeline:

- Options were resolved inside the syntax transform from the Compilation,
  which is not equatable, so nothing downstream could be cached. Options are
  now their own equatable pipeline stage and namespace filtering moved to the
  emit stage.
- The pipeline model carried ITypeSymbol, which roots the Compilation and has
  no value equality. It now carries strings and an EquatableArray.
- A DbContext configuring several entities only produced constants for the
  first one; extractors now return every entity they find.
- AttributeExtractor could add the same property twice when both a column
  type and a StringLength attribute were present.
- Entities are no longer silently merged in the dark: colliding simple names
  report ELG0003 and conflicting lengths report ELG0004.
- Records were skipped entirely, and every class in the project went through
  the semantic model; the predicate now matches classes and records that have
  a base list or attributed members.
- The generator held an options provider in an instance field.
…e sample

Package versions move to Directory.Packages.props. EF Core is declared per
target framework so multi-targeted projects build against the EF line that
matches the framework (8.0.29 / 9.0.18 / 10.0.10) instead of only the newest.

Directory.Build.props holds $(SupportedTargetFrameworks) = net8.0;net9.0;net10.0,
used by the tests and both samples, so adding a framework is a one-line change.
CI installs all three runtimes. The generator itself stays on netstandard2.0,
which is what Roslyn loads analyzers from.

Adds samples/CleanArchitecture, a three-project sample for the new
EntityLengthsOutputPath feature: Domain holds plain entities and the committed
Generated/EntityLengths.cs, Infrastructure holds the EF configurations and the
output path settings, Api consumes the constants through Domain. Verified that
a clean checkout builds in one pass and that the committed file is byte for
byte what the generator produces.

Two fixes found while wiring this up:

- The writer used a fixed "<path>.tmp" temporary file, which collides when the
  inner builds of a multi-targeted project write the same output in parallel.
  The temporary name is now unique per write.
- Dropped PublishAot from the single-project sample: EF Core is not AOT or trim
  safe and it only produced IL2026/IL3050 warnings.
The Usage example claimed an output that declared SurnameLength twice, which
would not compile, and listed a 200 that no example configured. It is now one
entity whose five properties each use a different mechanism, matching the real
output including its alphabetical ordering, and a test locks that block so it
cannot drift again. The sample output at the end of the README was also missing
constants the sample actually generates.

Adds a Samples section, a note that conflicting lengths are reported as
ELG0004, and a Building this repository section covering central package
management and $(SupportedTargetFrameworks).

Verifying the README against a real build exposed two defects:

- ConstantsSourceBuilder emitted one literal "\r\n" while every other line went
  through AppendLine, so the file had mixed line endings. For a file that is
  meant to be committed that means it shows up as permanently modified on
  another platform. All lines now go through AppendLine, and .gitattributes
  normalizes the repository.
- The parallel inner builds of a multi-targeted project raced on the
  destination copy, not just the temporary file, and reported a spurious
  ELG0001. Writes now retry and are accepted when the file already holds the
  wanted content, which is the normal outcome of that race. Covered by a test
  that writes the same path from eight threads.
EntityTypeInfo changed shape in this release (ITypeSymbol and List<T> became
strings and an EquatableArray, so the model can flow through the incremental
pipeline with value equality). It is an implementation detail of the generator,
so making it internal now avoids shipping a public type whose shape already
broke once. EquatableArray<T> is new in this release and only exists to serve
that model, so it becomes internal too.

The public surface is now the five types that shipped in 1.0.3, verified by
reading the assembly metadata:

  EntityLengthsGeneratorAttribute
  EntityMaxLengthGenerator
  PropertyMaxLength
  EntityLengthsGeneratorOptions
  EntityLengthsScanningOptions
@TarasKovalenko
TarasKovalenko merged commit ea020bf into main Jul 26, 2026
1 check passed
@TarasKovalenko
TarasKovalenko deleted the feat/external-output-path branch July 26, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant