Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,38 @@ Scribe provides **Quill**, a fluent source builder that eliminates raw `StringBu

## Getting Started

```shell
dotnet add package BulletsForHumanity.Scribe
### With the Scribe SDK (Recommended)

**1.** Add a `global.json` to your solution root:

```json
{
"msbuild-sdks": {
"BulletsForHumanity.Scribe.Sdk": "0.3.0"
}
}
```

**2.** Create your analyzer/generator `.csproj`:

```xml
<Project Sdk="BulletsForHumanity.Scribe.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="BulletsForHumanity.Scribe" PrivateAssets="all" />
</ItemGroup>
</Project>
```

**Requirements:** netstandard2.0 (required by the Roslyn compiler host). See [Project Setup](docs/project-setup.md) for a complete `.csproj` template.
The SDK handles everything: `netstandard2.0` targeting, `LangVersion`, analyzer packaging, `Stubs.cs` polyfills, and dependency bundling. See [Project Setup](docs/project-setup.md) for details.

### Creating a Generator Project
### Manual Setup (Without SDK)

A minimal generator project that uses Scribe:
```shell
dotnet add package BulletsForHumanity.Scribe
```

A minimal generator project without the SDK:

```xml
<Project Sdk="Microsoft.NET.Sdk">
Expand All @@ -41,6 +64,8 @@ A minimal generator project that uses Scribe:
</Project>
```

See [Project Setup](docs/project-setup.md) for a complete `.csproj` template.

Consumer projects reference the generator via NuGet (or a local package via [LocalDev](#local-development-localdev)):

```xml
Expand Down Expand Up @@ -151,6 +176,7 @@ See [Project Setup & Infrastructure](docs/project-setup.md#local-development-loc

| Component | Purpose |
|-----------|---------|
| [**Scribe SDK**](docs/project-setup.md#scribe-sdk-recommended) | MSBuild SDK — zero-config project setup for analyzer/generator projects |
| [**Quill**](docs/quill-reference.md) | Fluent source builder — indentation, usings, namespaces, XML docs, type resolution |
| **ScribeHeader** | Assembly-level attribute for branding generated files with a decorative page header |
| **Template** | Minimal `{{key}}` marker substitution for structural shells |
Expand Down
87 changes: 87 additions & 0 deletions Scribe.Sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# BulletsForHumanity.Scribe.Sdk

A custom MSBuild SDK that eliminates boilerplate from Roslyn analyzer and source generator projects.

## Quick Start

**1. Add a `global.json`** with the SDK version:

```json
{
"msbuild-sdks": {
"BulletsForHumanity.Scribe.Sdk": "0.3.0"
}
}
```

**2. Use the SDK** in your `.csproj`:

```xml
<Project Sdk="BulletsForHumanity.Scribe.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="BulletsForHumanity.Scribe" PrivateAssets="all" />
</ItemGroup>
</Project>
```

That's it. The SDK handles everything else:

- `TargetFramework=netstandard2.0` (required by Roslyn compiler host)
- `LangVersion=14` for modern C# features
- `EnforceExtendedAnalyzerRules=true` to catch common mistakes
- Analyzer packaging (`analyzers/dotnet/cs/` placement, embedded PDB)
- Private dependency bundling (Scribe DLL alongside your analyzer)
- `Stubs.cs` polyfills for `init`, `record`, `required`, nullable annotations
- LocalDev multi-repo development infrastructure

## What the SDK Sets

| Property | Default | Purpose |
|----------|---------|---------|
| `TargetFramework` | `netstandard2.0` | Required by the Roslyn compiler host |
| `LangVersion` | `14` | Modern C# features with polyfill stubs |
| `EnforceExtendedAnalyzerRules` | `true` | Catches analyzer authoring mistakes |
| `IncludeBuildOutput` | `false` | DLL goes into `analyzers/`, not `lib/` |
| `PackageType` | `Analyzer` | Marks package as an analyzer |
| `IncludeSymbols` | `false` | PDB is embedded in the DLL |
| `DebugType` | `embedded` | IDE debugging support |
| `Nullable` | `enable` | Null-safety |
| `GenerateDocumentationFile` | `false` | Not needed for bundled DLLs |
| `CopyLocalLockFileAssemblies` | `true` | Enables private dependency bundling |

All defaults are set in the early phase (`Sdk.props`) and can be overridden in your `.csproj`. Packaging targets (analyzer DLL placement, dependency bundling) are enforced by the SDK and cannot be overridden.

## Stubs.cs Polyfills
Comment on lines +51 to +55

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

This README says "All properties can be overridden in your .csproj", but the SDK sets/enforces some behavior in Sdk.targets (late phase) such as dependency bundling (CopyLocalLockFileAssemblies) and packaging targets, which cannot be overridden the same way. Please adjust the wording to distinguish overridable defaults (Sdk.props) from enforced behavior (Sdk.targets), and/or document the specific opt-out switches.

Copilot uses AI. Check for mistakes.

The SDK auto-includes `Stubs.cs` with polyfill types for modern C# features on netstandard2.0:

- `init` setters / `record` types (C# 9)
- `[ModuleInitializer]`, `[SkipLocalsInit]` (C# 9)
- `[CallerArgumentExpression]` (C# 10)
- Interpolated string handlers (C# 10)
- `required` members (C# 11)
- `scoped ref` parameters (C# 11)
- Nullable flow analysis attributes

All polyfills are guarded by `#if !NET5_0_OR_GREATER` and deactivate on modern targets.

**Opt out:** Set `<ScribeSdkIncludeStubs>false</ScribeSdkIncludeStubs>` if you provide your own.

## Packaging

`dotnet pack` produces a correct analyzer NuGet package:

- Your analyzer DLL is placed in `analyzers/dotnet/cs/`
- Private dependencies (like Scribe) are bundled alongside
- Roslyn SDK DLLs are excluded (provided by the compiler host)

## LocalDev

The SDK includes [LocalDev](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/project-setup.md#local-development-localdev) infrastructure for multi-repo development. See the main Scribe documentation for setup instructions.

## Links

- [Scribe Documentation](https://github.com/BulletsForHumanity/Scribe)
- [Project Setup Guide](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/project-setup.md)
- [Writing Generators](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/writing-generators.md)
46 changes: 46 additions & 0 deletions Scribe.Sdk/Scribe.Sdk.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<!--
BulletsForHumanity.Scribe.Sdk — MSBuild SDK for Roslyn analyzer/generator projects.

This is a pack-only project. It produces no build output — the NuGet package
contains only MSBuild props/targets, polyfill stubs, and LocalDev infrastructure.
-->
<PropertyGroup>
<PackageId>BulletsForHumanity.Scribe.Sdk</PackageId>
<TargetFramework>netstandard2.0</TargetFramework>
<!-- No build output — this is a pure SDK package. -->
<IncludeBuildOutput>false</IncludeBuildOutput>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<!-- NU5128: no DLL in lib/ — expected for SDK packages.
NU5129: build/ files don't need PackageId-matching names — this is an MSBuild SDK, not a regular package.
NETSDK1212: IsTrimmable not supported on netstandard2.0 — irrelevant for pack-only project. -->
<NoWarn>$(NoWarn);NU5128;NU5129;NETSDK1212</NoWarn>
<!-- Ensure .template.config and other dotfiles are included. -->
<NoDefaultExcludes>true</NoDefaultExcludes>
<Description>MSBuild SDK for Roslyn analyzer and source generator projects. Provides default project configuration, analyzer packaging targets, netstandard2.0 polyfill stubs, and LocalDev multi-repo development infrastructure. Use with: &lt;Project Sdk="BulletsForHumanity.Scribe.Sdk"&gt;</Description>
<PackageTags>roslyn;analyzer;generator;source-generator;msbuild-sdk;scribe</PackageTags>
</PropertyGroup>

<!-- Pack the SDK props/targets into the Sdk/ directory. -->
<ItemGroup>
<None Include="Sdk\**" Pack="true" PackagePath="Sdk" />
</ItemGroup>

<!-- Pack the polyfill stubs into the content/ directory. -->
<ItemGroup>
<None Include="content\**" Pack="true" PackagePath="content" />
</ItemGroup>

<!-- Pack the LocalDev build infrastructure from the main Scribe project. -->
<ItemGroup>
<None Include="..\Scribe\build\Scribe.LocalDev.props" Pack="true" PackagePath="build" />
<None Include="..\Scribe\build\Scribe.LocalDev.targets" Pack="true" PackagePath="build" />
</ItemGroup>

<!-- Pack LICENSE and README. -->
<ItemGroup>
<None Include="..\LICENSE" Pack="true" PackagePath="" />
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
</Project>
61 changes: 61 additions & 0 deletions Scribe.Sdk/Sdk/Sdk.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<Project>
<!--
BulletsForHumanity.Scribe.Sdk — Early Phase (Sdk.props)

Sets default properties for Roslyn analyzer/source generator projects.
Chains to the standard Microsoft.NET.Sdk. All properties set here can be
overridden by the consuming project file.

Usage:
<Project Sdk="BulletsForHumanity.Scribe.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
</ItemGroup>
</Project>

Requires a global.json entry for SDK resolution:
{
"msbuild-sdks": {
"BulletsForHumanity.Scribe.Sdk": "<version>"
}
}
-->

<!-- Chain to the standard .NET SDK (early phase). -->
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

<!-- ── Analyzer/Generator Project Defaults ───────────────────────────── -->
<!-- These can be overridden in the consuming .csproj. -->
<PropertyGroup>
<!-- Roslyn compiler host requires netstandard2.0. -->
<TargetFramework>netstandard2.0</TargetFramework>
<!-- Modern C# features, enabled by Stubs.cs polyfills. -->
<LangVersion>14</LangVersion>
<!-- Catches common analyzer authoring mistakes at compile time (e.g. RS1035). -->
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<!-- Analyzer DLL goes into analyzers/dotnet/cs/, not lib/. -->
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- Marks the NuGet package as an analyzer package. -->
<PackageType>Analyzer</PackageType>
<!-- Embed PDB into the DLL for IDE debugging. No separate .snupkg needed. -->
<IncludeSymbols>false</IncludeSymbols>
<DebugType>embedded</DebugType>
<!-- Enable nullable reference types for modern null-safety. -->
<Nullable>enable</Nullable>
<!-- Disable doc XML file — analyzer DLLs are bundled, not referenced directly. -->
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<!-- NU5128: expected warning when lib/ is empty (due to IncludeBuildOutput=false). -->
<NoWarn>$(NoWarn);NU5128</NoWarn>
<!-- Copy NuGet dependency DLLs to output so they can be bundled into the analyzer package. -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<!-- ── Stubs Opt-Out ─────────────────────────────────────────────────── -->
<!-- Consumers can set ScribeSdkIncludeStubs=false to skip Stubs.cs injection. -->
<PropertyGroup Condition="'$(ScribeSdkIncludeStubs)' == ''">
<ScribeSdkIncludeStubs>true</ScribeSdkIncludeStubs>
</PropertyGroup>

<!-- ── LocalDev Infrastructure (Early Phase) ─────────────────────────── -->
<Import Project="$(MSBuildThisFileDirectory)..\build\Scribe.LocalDev.props" />
</Project>
58 changes: 58 additions & 0 deletions Scribe.Sdk/Sdk/Sdk.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<Project>
<!--
BulletsForHumanity.Scribe.Sdk — Late Phase (Sdk.targets)

Defines packaging targets and auto-includes Stubs.cs polyfills.
These run after the project file has been evaluated, so they cannot
be overridden — they enforce correct analyzer packaging behaviour.
-->

<!-- Chain to the standard .NET SDK (late phase). -->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

<!-- ── Auto-Include Stubs.cs Polyfills ───────────────────────────────── -->
<!--
Injects Stubs.cs as a compile item. The polyfills are guarded by
#if !NET5_0_OR_GREATER and deactivate on modern targets.
Set ScribeSdkIncludeStubs=false in your .csproj to opt out.
-->
<ItemGroup Condition="'$(ScribeSdkIncludeStubs)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)..\content\Stubs.cs"
Link="Generated\Stubs.cs"
Visible="false" />
</ItemGroup>

<!-- ── Analyzer DLL Packaging ────────────────────────────────────────── -->
<!--
Places the analyzer DLL into analyzers/dotnet/cs/ in the NuGet package.
This is required for Roslyn to discover the analyzer at build time.
-->
<Target Name="_ScribeSdkAddAnalyzerDlls" BeforeTargets="_GetPackageFiles">
<ItemGroup>
<None Include="$(TargetPath)"
Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Target>

<!-- ── Dependency Bundling ───────────────────────────────────────────── -->
<!--
Bundles private NuGet dependencies alongside the analyzer DLL.
Roslyn SDK DLLs (Microsoft.CodeAnalysis.*) are excluded — they are
provided by the compiler host.
-->

<Target Name="_ScribeSdkAddAnalyzerDependencies" BeforeTargets="_GetPackageFiles"
DependsOnTargets="ResolvePackageAssets">
<ItemGroup>
<_ScribeSdkDeps Include="@(ReferenceCopyLocalPaths)"
Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' != ''
and '$([System.String]::Copy(&quot;%(ReferenceCopyLocalPaths.NuGetPackageId)&quot;).StartsWith(&quot;Microsoft.CodeAnalysis&quot;))' != 'True'
and '%(Extension)' == '.dll'" />
<None Include="@(_ScribeSdkDeps)"
Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Target>

<!-- ── LocalDev Infrastructure (Late Phase) ──────────────────────────── -->
<Import Project="$(MSBuildThisFileDirectory)..\build\Scribe.LocalDev.targets" />
</Project>
Loading
Loading