From 4562f5a0028f8629d63597109f79ed52f418e03b Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Mon, 19 Oct 2026 15:46:45 +0200 Subject: [PATCH] build: let FAKE write the assembly information as MSBuild properties The release step of the build script wrote the assembly attributes into AssemblyInfo.fs, and Directory.Build.props set GenerateAssemblyInfo to false so that the SDK did not generate them a second time. Every other assembly attribute then needed a source file, or a property and a target. The GenerateAssemblyInfo target now writes src/AssemblyInfo.Build.props with Product, AssemblyVersion, FileVersion and InformationalVersion, and the AssemblyMetadata items ReleaseDate, ReleaseChannel and GitHash. src/Directory.Build.props imports it, GenerateAssemblyInfo is no longer false, and the SDK generates the attributes. A project sets what is specific to it (its title, for example) in its own project file. The SDK appends no source revision to the informational version, because the git hash is in the metadata. AssemblyInfo.fs keeps the module AssemblyVersionInformation with the literals, but no attributes. The release commit stages both files. The package Fake.DotNet.AssemblyInfoFile is not needed any more. The values in the checked-in files are those of the last release, as before. The assembly has the attributes of before and the four that the SDK generates by default: AssemblyCompany, AssemblyConfiguration, AssemblyDescription and the AssemblyMetadata RepositoryUrl. Co-Authored-By: Claude Sonnet 5.5 --- Directory.Build.props | 1 - Directory.Packages.props | 1 - README.md | 2 +- build/build.fs | 106 +++++++++++++----- build/build.fsproj | 1 - src/AssemblyInfo.Build.props | 15 +++ src/Directory.Build.props | 2 + .../AssemblyInfo.fs | 11 -- 8 files changed, 95 insertions(+), 44 deletions(-) create mode 100644 src/AssemblyInfo.Build.props diff --git a/Directory.Build.props b/Directory.Build.props index db8bbd23..6e84452a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -29,7 +29,6 @@ See https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-director enable true true - false diff --git a/Directory.Packages.props b/Directory.Packages.props index 3660945f..def08789 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,7 +19,6 @@ - diff --git a/README.md b/README.md index a10138fc..950057e1 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ or - `GenerateCoverageReport` - Code coverage is run during `DotnetTest` and this generates a report via [ReportGenerator](https://github.com/danielpalme/ReportGenerator). - `ShowCoverageReport` - Shows the report generated in `GenerateCoverageReport`. - `WatchTests` - Runs [dotnet watch](https://docs.microsoft.com/en-us/aspnet/core/tutorials/dotnet-watch?view=aspnetcore-3.0) with the test projects. Useful for rapid feedback loops. -- `GenerateAssemblyInfo` - Generates [AssemblyInfo](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.applicationservices.assemblyinfo?view=netframework-4.8) for libraries. +- `GenerateAssemblyInfo` - Writes the version, the release date, the release channel and the git hash of the latest changelog entry into `src/AssemblyInfo.Build.props`, which `src/Directory.Build.props` imports, so that the SDK generates the assembly attributes of the libraries from it. - `DotnetPack` - Runs [dotnet pack](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-pack). This includes running [Source Link](https://github.com/dotnet/sourcelink). - `SourceLinkTest` - Runs a Source Link test tool to verify Source Links were properly generated. - `PublishToNuGet` - Publishes the NuGet packages generated in `DotnetPack` to NuGet via [dotnet nuget push](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push). Runs only from `Github Actions`. diff --git a/build/build.fs b/build/build.fs index 004a55b3..d9536a28 100644 --- a/build/build.fs +++ b/build/build.fs @@ -1,4 +1,5 @@ open System +open System.Xml.Linq open Fake.Core open Fake.DotNet open Fake.Tools @@ -28,6 +29,9 @@ let rootDirectory = __SOURCE_DIRECTORY__ ".." let productName = "FSharp.Collections.Immutable" +/// The assembly information of the release. `src/Directory.Build.props` imports it, so that the SDK generates the attributes. +let assemblyInfoPropsPath = rootDirectory "src" "AssemblyInfo.Build.props" + let sln = rootDirectory "FSharp.Collections.Immutable.slnf" let srcCodeGlob = @@ -432,47 +436,90 @@ let watchTests _ = cancelEvent.Cancel <- true +/// Writes the assembly information of the latest changelog entry in two places. `src/AssemblyInfo.Build.props` holds +/// the properties and the `AssemblyMetadata` items from which the SDK generates the assembly attributes, and +/// `src/Directory.Build.props` imports it, so a project sets only what is specific to it in its own project file. +/// `AssemblyInfo.fs` of each F# project in `src` holds the literals of `AssemblyVersionInformation`, which the code can +/// use at compile time. It has no attributes, because the SDK generates them. let generateAssemblyInfo _ = - let (|Fsproj|Csproj|Vbproj|) (projFileName : string) = - match projFileName with - | f when f.EndsWith ("fsproj") -> Fsproj - | f when f.EndsWith ("csproj") -> Csproj - | f when f.EndsWith ("vbproj") -> Vbproj - | _ -> failwithf "Project file %s not supported. Unknown project type." projFileName - let releaseChannel = match latestEntry.SemVer.PreRelease with | Some pr -> pr.Name | _ -> "release" - let getAssemblyInfoAttributes projectName = [ - AssemblyInfo.Title (projectName) - AssemblyInfo.Product productName - AssemblyInfo.Version latestEntry.AssemblyVersion - // The date is optional in a changelog entry - match latestEntry.Date with - | Some releaseDate -> AssemblyInfo.Metadata ("ReleaseDate", releaseDate.ToString ("o")) + // The date is optional in a changelog entry + let releaseDate = latestEntry.Date |> Option.map _.ToString("o") + let gitHash = Git.Information.getCurrentSHA1 (null) + + let metadata = [ + match releaseDate with + | Some date -> "ReleaseDate", date | None -> () - AssemblyInfo.FileVersion latestEntry.AssemblyVersion - AssemblyInfo.InformationalVersion latestEntry.AssemblyVersion - AssemblyInfo.Metadata ("ReleaseChannel", releaseChannel) - AssemblyInfo.Metadata ("GitHash", Git.Information.getCurrentSHA1 (null)) + "ReleaseChannel", releaseChannel + "GitHash", gitHash ] - let getProjectDetails (projectPath : string) = - let projectName = IO.Path.GetFileNameWithoutExtension (projectPath) + let properties = + XElement ( + "PropertyGroup", + XElement ("Product", productName), + XElement ("AssemblyVersion", latestEntry.AssemblyVersion), + XElement ("FileVersion", latestEntry.AssemblyVersion), + XElement ("InformationalVersion", latestEntry.AssemblyVersion), + // The git hash is in the metadata, so the SDK must not append the source revision to the informational version + XElement ("IncludeSourceRevisionInInformationalVersion", "false") + ) - (projectPath, projectName, IO.Path.GetDirectoryName (projectPath), (getAssemblyInfoAttributes projectName)) + let items = + XElement ( + "ItemGroup", + metadata + |> List.map (fun (key, value) -> + XElement ("AssemblyMetadata", XAttribute ("Include", key), XAttribute ("Value", value)) + ) + ) - !!srcGlob - |> Seq.map getProjectDetails - |> Seq.iter (fun (projFileName, _, folderName, attributes) -> - match projFileName with - | Fsproj -> AssemblyInfoFile.createFSharp (folderName "AssemblyInfo.fs") attributes - | Csproj -> AssemblyInfoFile.createCSharp ((folderName "Properties") "AssemblyInfo.cs") attributes - | Vbproj -> AssemblyInfoFile.createVisualBasic ((folderName "My Project") "AssemblyInfo.vb") attributes - ) + let props = + XDocument (XComment " Auto-Generated by FAKE; do not edit ", XElement ("Project", properties, items)) + + // XDocument breaks lines as the operating system does, and the other generated file has "\n" everywhere + let propsText = props.ToString().ReplaceLineEndings "\n" + + File.writeString false assemblyInfoPropsPath $"%s{propsText}\n" + + let versionInformation projectName = + let literals = [ + "AssemblyTitle", projectName + "AssemblyProduct", productName + "AssemblyVersion", latestEntry.AssemblyVersion + match releaseDate with + | Some date -> "AssemblyMetadata_ReleaseDate", date + | None -> () + "AssemblyFileVersion", latestEntry.AssemblyVersion + "AssemblyInformationalVersion", latestEntry.AssemblyVersion + "AssemblyMetadata_ReleaseChannel", releaseChannel + "AssemblyMetadata_GitHash", gitHash + ] + + [ + "// Auto-Generated by FAKE; do not edit" + "namespace System" + "" + "module internal AssemblyVersionInformation =" + yield! + literals + |> List.map (fun (name, value) -> $" let [] %s{name} = \"%s{value}\"") + ] + |> String.concat "\n" + + let writeVersionInformation (projectPath : string) = + // The project path is not null, so neither is the name + let projectName = IO.Path.GetFileNameWithoutExtension projectPath |> nonNull + File.writeString false (Path.getDirectory projectPath "AssemblyInfo.fs") $"%s{versionInformation projectName}\n" + + !!(rootDirectory "src/**/*.fsproj") + |> Seq.iter writeVersionInformation let dotnetPack ctx = // Get release notes with properly-linked version number @@ -534,6 +581,7 @@ let gitRelease _ = !!(rootDirectory "src/**/AssemblyInfo.fs") ++ (rootDirectory "tests/**/AssemblyInfo.fs") + ++ assemblyInfoPropsPath |> Seq.iter (Git.Staging.stageFile "" >> ignore) let msg = $"release: bump version to `%s{latestEntry.NuGetVersion}`\n\n%s{releaseNotesGitCommitFormat}" diff --git a/build/build.fsproj b/build/build.fsproj index 95ccdb3c..26c77382 100644 --- a/build/build.fsproj +++ b/build/build.fsproj @@ -25,7 +25,6 @@ - diff --git a/src/AssemblyInfo.Build.props b/src/AssemblyInfo.Build.props new file mode 100644 index 00000000..924e6532 --- /dev/null +++ b/src/AssemblyInfo.Build.props @@ -0,0 +1,15 @@ + + + + FSharp.Collections.Immutable + 1.0.0 + 1.0.0 + 1.0.0 + false + + + + + + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5ea09a4f..59621c56 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,5 +1,7 @@ + + true true diff --git a/src/FSharp.Collections.Immutable/AssemblyInfo.fs b/src/FSharp.Collections.Immutable/AssemblyInfo.fs index db10dac4..48935162 100644 --- a/src/FSharp.Collections.Immutable/AssemblyInfo.fs +++ b/src/FSharp.Collections.Immutable/AssemblyInfo.fs @@ -1,16 +1,5 @@ // Auto-Generated by FAKE; do not edit namespace System -open System.Reflection - -[] -[] -[] -[] -[] -[] -[] -[] -do () module internal AssemblyVersionInformation = let [] AssemblyTitle = "FSharp.Collections.Immutable"