Skip to content

Commit

Permalink
Drastically simply packing and namespace management
Browse files Browse the repository at this point in the history
The ability to customize the namespace of the struct id types seemed a bit of a corner case and it was introducing non-trivial complexity in scenarios involving transitive project references (analyzers are transitive, as well as buildTransitive targets, but analyzer options, contentFiles -with or without compile action- are not). This was leading to hack over hack for no serious gain.

So even if we keep the CodeTemplate behavior intact should we figure out how to properly handle changing the namespace, this change removes the copying and content updating on the static files, which are now simply included via nuget's contentFiles feature plus buildAction=Compile. This provides the behavior we're looking for:

- Interfaces and types required for struct ids are only added to the project referencing the package
- using and codegen for struct ids is supported in projects referencing the "core" one: analyzers are already transitive, we just make the templates transitive too via targets (since we can't via contentFiles).
  • Loading branch information
kzu committed Dec 22, 2024
1 parent 513689a commit 4c7159d
Show file tree
Hide file tree
Showing 10 changed files with 25 additions and 79 deletions.
3 changes: 0 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ The package is a [development dependency](https://github.com/NuGet/Home/wiki/Dev
meaning it will not add any run-time dependencies to your project (or package if you
publish one that uses struct ids).

The default target namespace for the included types will match the `RootNamespace` of the
project, but can be customized by setting the `StructIdNamespace` property.

You can simply declare a new ID type by implementing `IStructId<TValue>`:

```csharp
Expand Down
3 changes: 3 additions & 0 deletions src/StructId.Analyzer/CodeTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public static SyntaxNode Apply(this SyntaxNode node, INamedTypeSymbol structId)

var tid = iface.TypeArguments.FirstOrDefault()?.ToFullName() ?? "string";
var corens = iface.ContainingNamespace.ToFullName();
if (string.IsNullOrEmpty(corens))
corens = nameof(StructId);

var targetNamespace = structId.ContainingNamespace != null && !structId.ContainingNamespace.IsGlobalNamespace ?
structId.ContainingNamespace.ToDisplayString() : null;

Expand Down
4 changes: 2 additions & 2 deletions src/StructId.Analyzer/KnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public record KnownTypes(Compilation Compilation)
/// StructId.IStructId
/// </summary>
public INamedTypeSymbol? IStructId { get; } = Compilation
.GetAllTypes(true)
.GetAllTypes(includeReferenced: true)
.FirstOrDefault(x => x.MetadataName == "IStructId" && x.IsGeneratedByStructId());

/// <summary>
/// StructId.IStructId{T}
/// </summary>
public INamedTypeSymbol? IStructIdT { get; } = Compilation
.GetAllTypes(true)
.GetAllTypes(includeReferenced: true)
.FirstOrDefault(x => x.MetadataName == "IStructId`1" && x.IsGeneratedByStructId());
}
3 changes: 2 additions & 1 deletion src/StructId.FunctionalTests/NoNs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using StructId.Functional;
using StructId;
using StructId.Functional;

// Showcases that types don't need to have a namespace
public partial record struct NoNsId : IStructId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\StructId.Package\StructId.props" />

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand Down
2 changes: 1 addition & 1 deletion src/StructId.FunctionalTests/UlidEntityFramework.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#nullable enable

using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using StructId.Functional;
using StructId;

[TStructId]
file partial record struct TSelf(Ulid Value) : INewable<TSelf, Ulid>
Expand Down
5 changes: 3 additions & 2 deletions src/StructId.Package/StructId.Package.msbuildproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<DevelopmentDependency>true</DevelopmentDependency>
<Description>Stronly typed ids using readonly record structs and modern C# features.</Description>
<PackageTags>dotnet record struct typed id</PackageTags>
<PackFolder>build</PackFolder>
<PackFolder>buildTransitive</PackFolder>
<PackNone>true</PackNone>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NuGetizer" Version="1.2.3" />
Expand All @@ -15,7 +16,7 @@
<ProjectReference Include="..\StructId.CodeFix\StructId.CodeFix.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\StructId\*.cs" Visible="false" />
<Content Include="..\StructId\*.cs" BuildAction="Compile" CodeLanguage="cs" Pack="true" />
<None Include="..\StructId\Templates\*.cs" PackFolder="$(PackFolder)\Templates" Visible="false" />
<None Update="@(None)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Expand Down
3 changes: 0 additions & 3 deletions src/StructId.Package/StructId.props

This file was deleted.

78 changes: 14 additions & 64 deletions src/StructId.Package/StructId.targets
Original file line number Diff line number Diff line change
@@ -1,50 +1,21 @@
<Project>

<PropertyGroup>
<StructIdNamespace Condition="$(StructIdNamespace) == ''">$(RootNamespace)</StructIdNamespace>
<StructIdNamespace Condition="$(StructIdNamespace) == ''">StructId</StructIdNamespace>
<StructIdPathHash Condition="$(StructIdNamespace) != ''">$([MSBuild]::StableStringHash($(StructIdNamespace)))\</StructIdPathHash>
</PropertyGroup>

<ItemGroup>
<StructId Include="$(MSBuildThisFileDirectory)*.cs" Link="StructId\$(StructIdPathHash)%(Filename)%(Extension)" />
<StructId Include="$(MSBuildThisFileDirectory)Templates\*.cs" Link="StructId\$(StructIdPathHash)Templates\%(Filename)%(Extension)" />
<!-- This is safe to do even if we're in a transitive targets file, since content files providing
these items won't exist in a transitive project. See https://github.com/NuGet/Home/issues/7420 -->
<Compile Update="@(Compile)">
<Visible Condition="'%(NuGetItemType)' == 'Compile' and '%(NuGetPackageId)' == 'StructId'">false</Visible>
<Link Condition="'%(NuGetItemType)' == 'Compile' and '%(NuGetPackageId)' == 'StructId'">StructId\%(Filename)%(Extension)</Link>
</Compile>

<!-- Templates should always be added transitively since they are all file-local and their
syntax tree is required in the compilation for them to apply properly -->
<StructId Include="$(MSBuildThisFileDirectory)Templates\*.cs"
Link="StructId\Templates\%(Filename)%(Extension)"
Visible="false"/>
</ItemGroup>

<Target Name="CollectStructIds">
<ItemGroup>
<StructId>
<TargetPath>$(IntermediateOutputPath)%(Link)</TargetPath>
</StructId>
</ItemGroup>
</Target>

<Target Name="IncludeStructIdAsIs" Condition="$(StructIdNamespace) == 'StructId'" DependsOnTargets="CollectStructIds" Inputs="@(StructId)" Outputs="%(StructId.TargetPath)">
<!-- No copying needed in this case, we'll just include the original files. -->
<ItemGroup>
<StructId>
<TargetPath>%(FullPath)</TargetPath>
</StructId>
</ItemGroup>
</Target>

<Target Name="AddStructIdContent" Condition="$(StructIdNamespace) != 'StructId'" DependsOnTargets="CollectStructIds" Inputs="@(StructId)" Outputs="|%(StructId.Identity)|">
<PropertyGroup>
<StructIdContent>$([System.IO.File]::ReadAllText(%(StructId.FullPath)))</StructIdContent>
<StructIdContent>$(StructIdContent.Replace('using StructId', 'using $(StructIdNamespace)').Replace('namespace StructId', 'namespace $(StructIdNamespace)'))</StructIdContent>
</PropertyGroup>
<ItemGroup>
<StructId>
<Content>$([MSBuild]::Unescape($(StructIdContent)))</Content>
</StructId>
</ItemGroup>
</Target>

<Target Name="CopyStructIdNamespaced" Condition="$(StructIdNamespace) != 'StructId'" DependsOnTargets="AddStructIdContent" Inputs="@(StructId)" Outputs="%(StructId.TargetPath)">
<WriteRaw Content="%(StructId.Content)" SourcePath="%(StructId.FullPath)" TargetPath="%(StructId.TargetPath)" />
</Target>

<Target Name="AddStructId" DependsOnTargets="IncludeStructIdAsIs;CopyStructIdNamespaced;ResolveLockFileReferences" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun">
<Target Name="AddStructId" DependsOnTargets="ResolveLockFileReferences" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun">
<!-- Feature detection -->
<PropertyGroup>
<UseDapper>false</UseDapper>
Expand All @@ -62,29 +33,8 @@
</ItemGroup>
<!-- Add final template items to project -->
<ItemGroup>
<Compile Include="%(StructId.TargetPath)" />
<Compile Include="%(StructId.FullPath)" />
</ItemGroup>
</Target>

<UsingTask TaskName="WriteRaw" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Content ParameterType="System.String" Required="true" />
<SourcePath ParameterType="System.String" Required="true" />
<TargetPath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text"/>
<Using Namespace="Microsoft.Build.Framework"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
Directory.CreateDirectory(Path.GetDirectoryName(TargetPath));
File.WriteAllText(TargetPath, Content, Encoding.UTF8);
File.SetLastWriteTimeUtc(TargetPath, File.GetLastWriteTimeUtc(SourcePath));
]]>
</Code>
</Task>
</UsingTask>

</Project>
2 changes: 0 additions & 2 deletions src/StructId.Tests/StructId.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\StructId.Package\StructId.props" />

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down

0 comments on commit 4c7159d

Please sign in to comment.