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

Simple unreleased version and BuildDate config #35

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
# Changelog

## Unreleased
## [Unreleased]

### Added

* Warnings when the `<ChangelogFile>` property is not set, or when the specified file cannot be found
* Automatic pre-release version bumping when an `Unreleased` section is found, which increments the patch version of the latest release and adds the "-alpha" suffix
* Added a property `<GenerateVersionForUnreleasedChanges>` which can be set to `false` to prevent automatic version bumping
* Added an output property `UnreleasedReleaseNotes` containing the contents of the `Unreleased` section
* Added a property `<GenerateAssemblyBuildDateAttribute>` which controls whether the `BuildDate` `AssemblyAttribute` is written to the assembly, default `true`

### Changed

* No longer automatically use a `CHANGELOG.md` file, the path to the changelog must not be specified with the `<ChangelogFile>` property
* Swapped to using the `KeepAChangelogParser` package for parsing

### Fixed

* Fix bundled dependencies into the package for net6.0
* Support for use in Visual Studio

### Removed

* Removed the Ionide.KeepAChangelog parser package

## [0.2.0] - 2023.12.05

Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.25" />
<PackageVersion Include="DotNet.ReproducibleBuilds.Isolated" Version="1.2.25" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="8.0.1" />
</ItemGroup>
</Project>
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Ionide.KeepAChangelog
# Ionide.KeepAChangelog.Tasks

This project implements a Changelog parser according to the spec at KeepAChangelog. It also provides MSBuild tasks and targets to automate the setting of **Versions** and **Package Release Notes** for your NuGet packages, so that the Changelogs are your source of truth.
This project provides MSBuild tasks and targets to automate the setting of **Versions** and **Package Release Notes** for your NuGet packages from changelogs meeting the spec at [KeepAChangelog](https://keepachangelog.com), so that the Changelogs are your source of truth.

When configured, this package will set the `Version`, `PackageVersion`, and `PackageReleaseNotes` of your packable project with the matching data from the latest Changelog release, as well as adding AssemblyMetadata for the `BuildDate` in the `YYYY-mm-dd` format.
When configured, this package will set the `Version`, `PackageVersion`, and `PackageReleaseNotes` of your packable project with the matching data from the latest Changelog release, or calculated from the unreleased section, as well as adding AssemblyMetadata for the `BuildDate` in the `YYYY-mm-dd` format.

## Installation

Expand Down Expand Up @@ -69,20 +69,28 @@ If your changelog has multiple versions, the latest one will be used.

## Customization

There's really only one property that matters for these targets, and that's `ChangelogFile`. This needs to point to the Changelog file you want to read, but it defaults to `CHANGELOG.md` in the root of a given project in case you want to adhere to defaults.
There's really only one property that matters for these targets, and that's `ChangelogFile`. This needs to point to the Changelog file you want to read, and a warning will be emitted if you try to package your project without having set this.


| Property | Type | Default Value | Description |
| - | - |---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ChangelogFile | string | - | Points to the changelog file to parse. Note that the value is relative to the _project_ root by default, so a repository-wide changelog would require this property be set to a different value, for example in a Directory.Build.props file |
| GenerateVersionForUnreleasedChanges | boolean | true | If set, the assembly/package version and release notes will be set from Unreleased changes, if any are present. |
| GenerateAssemblyBuildDateAttribute | boolean | true | If set, an assembly metadata attribute named "BuildDate" will be generated with the date (YYYY-MM-DD) of the parsed release. |

## API

When the task runs, it writes several output items and properties:

|Name|Type|Description|
|----|----|-----------|
| UnreleasedChangelog | UnreleasedChangelogData option | If present, there was an 'Unreleased' section in the Changelog. This structure will contain the sections present. |
| UnreleasedChangelog | ReleaseChangelogData option | If present, there was an 'Unreleased' section in the Changelog. This structure will contain the sections present, as well as an auto-incremented version number for this release. |
| UnreleasedReleaseNotes | string option | If present, contains the concatenated list of all Changelog sections for the Unreleased section of the Changelog. This is a convenience property so that you don't have to String.Join all the lines in the `ReleaseChangelogData` structure yourself! |
| CurrentReleaseChangelog | ReleaseChangelogData option | If present, there was at least one released logged in the Changelog. This structure will contain the details of each one. |
| AllReleasedChangelogs | ReleaseChangelogData list | Contains the ordered list of all released in the ChangelogFile, descending. |
| AllReleasedChangelogs | ReleaseChangelogData list | Contains the ordered list of all releases in the ChangelogFile, descending. |
| LatestReleaseNotes | string option | If present, contains the concatenated list of all Changelog sections for the latest release. This is a convenience property so that you don't have to String.Join all the lines in the `ReleaseChangelogData` yourself! |

### ChangelogData
### ReleaseChangelogData

This TaskItem has metadata for each of the known sections of a Changelog:

Expand All @@ -95,14 +103,7 @@ This TaskItem has metadata for each of the known sections of a Changelog:

In each case, the value of the metadata is the newline-concatenated list of all of the Changelog Entries for that section.

### UnreleasedChangelogData

This structure is a `ChangelogData` with an `Identity` of `"Unreleased"`.

### ReleaseChangelogData

This structure is the same as `ChangelogData`, but it contains two more items of metadata:

In addition,
* the `Identity` of the `TaskItem` is the Semantic Version of the release
* the `Date` of the `TaskItem` is the `YYYY-MM-DD`-formatted date of the release

Expand Down
63 changes: 43 additions & 20 deletions src/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ open System
open System.Globalization
open System.Runtime.CompilerServices
open System.Text
open FsToolkit.ErrorHandling.Operator.Result
open Microsoft.Build.Utilities
open Microsoft.Build.Framework
open System.IO
Expand Down Expand Up @@ -86,6 +87,9 @@ type ParseChangeLogs() =
[<Output>]
member val UnreleasedChangelog: ITaskItem = null with get, set

[<Output>]
member val UnreleasedReleaseNotes: string = null with get, set

[<Output>]
member val CurrentReleaseChangelog: ITaskItem = null with get, set

Expand All @@ -104,7 +108,8 @@ type ParseChangeLogs() =
let! changelog = this.ParseChangelog file

do! this.ReadUnreleasedSection changelog
do! this.ProcessReleases changelog
let latestRelease = this.ProcessReleases changelog
do! this.UpdateUnreleasedVersion latestRelease

return true
}
Expand All @@ -128,36 +133,54 @@ type ParseChangeLogs() =
Error()

member this.ReadUnreleasedSection(changelog: Changelog) =
match changelog.SectionUnreleased with
| null -> Ok()
| unreleased ->
this.UnreleasedChangelog <- unreleased.ToTaskItem()
let unreleasedSection = changelog.SectionUnreleased

match unreleasedSection.MarkdownTitle with
| "" -> Ok()
| _ ->
this.UnreleasedChangelog <- unreleasedSection.ToTaskItem()
this.UnreleasedReleaseNotes <- unreleasedSection.SubSectionCollection.ToMarkdown()
Ok()

member this.ProcessReleases(changelog: Changelog) =
let releases =
changelog.SectionCollection.Unwrapped()
|> Seq.sortByDescending _.version
|> Seq.toArray
|> Seq.toList

let latestRelease = releases |> (fun x -> x[0])
match releases with
| [] -> None
| latestRelease :: _ ->
let mapped =
releases
|> List.map (fun x ->
let taskItem = TaskItem(x.version.ToString())
taskItem.SetMetadata("Date", x.date.ToString("yyyy-MM-dd"))

let mapped =
releases
|> Array.map (fun x ->
let taskItem = TaskItem(x.version.ToString())
taskItem.SetMetadata("Date", x.date.ToString("yyyy-MM-dd"))
for (key, value) in x.collection.ToTaskItemMetadata() do
taskItem.SetMetadata(key, value)

for (key, value) in x.collection.ToTaskItemMetadata() do
taskItem.SetMetadata(key, value)
taskItem :> ITaskItem
)
|> Array.ofList

taskItem :> ITaskItem
)
this.CurrentReleaseChangelog <- mapped[0]
this.AllReleasedChangelogs <- mapped
this.LatestReleaseNotes <- latestRelease.collection.ToMarkdown()
Some(latestRelease.version)

this.CurrentReleaseChangelog <- mapped[0]
this.AllReleasedChangelogs <- mapped
this.LatestReleaseNotes <- latestRelease.collection.ToMarkdown()
Ok()
member this.UpdateUnreleasedVersion(latestVersion: SemVersion option) =
let latestVersion = latestVersion |> Option.defaultValue (SemVersion(0, 0, 0))

match this.UnreleasedChangelog with
| null -> Ok()
| _ ->
let newUnreleased =
latestVersion.WithPrereleaseParsedFrom "alpha"
|> _.WithPatch(latestVersion.Patch + 1)

this.UnreleasedChangelog.ItemSpec <- newUnreleased.ToString()
Ok()

/// <summary>
/// Helper method to log an error with the given log data.
Expand Down
20 changes: 16 additions & 4 deletions src/build/Core.targets
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,35 @@
<Target Name="GetChangelogVersion" Condition="'$(ChangelogFile)' != '' and Exists('$(ChangelogFile)')" DependsOnTargets="ValidateChangelog" Inputs="$(ChangelogFile)" Outputs="UnreleasedChangelog;CurrentReleaseChangelog;AllReleasedChangelogslLatestReleaseNotes">
<Ionide.KeepAChangelog.Tasks.ParseChangeLogs ChangelogFile="$(ChangelogFile)">
<Output TaskParameter="UnreleasedChangelog" ItemName="UnreleasedChangelog"/>
<Output TaskParameter="UnreleasedReleaseNotes" ItemName="UnreleasedReleaseNotes"/>
<Output TaskParameter="CurrentReleaseChangelog" ItemName="CurrentReleaseChangelog"/>
<Output TaskParameter="AllReleasedChangelogs" ItemName="AllReleasedChangelogs"/>
<Output TaskParameter="LatestReleaseNotes" ItemName="LatestReleaseNotes"/>
</Ionide.KeepAChangelog.Tasks.ParseChangeLogs>
</Target>

<Target Name="SetVersionFromChangelog" DependsOnTargets="GetChangelogVersion">
<Target Name="SetVersionFromChangelog" DependsOnTargets="GetChangelogVersion"
BeforeTargets="GetAssemblyVersion;GenerateAssemblyInfo" >
<PropertyGroup Condition="'@(CurrentReleaseChangelog)' != ''">
<!-- Set the version to the version of the latest release -->
<Version>%(CurrentReleaseChangelog.Identity)</Version>
<PackageVersion>%(CurrentReleaseChangelog.Identity)</PackageVersion>
<PackageReleaseNotes>@(LatestReleaseNotes)</PackageReleaseNotes>
<_ReleaseDate>%(CurrentReleaseChangelog.Date)</_ReleaseDate>
</PropertyGroup>

<PropertyGroup Condition="'@(UnreleasedChangelog)' != '' and '$(GenerateVersionForUnreleasedChanges)' == 'true'">
<!-- Set the version to the derived version of the unreleased changelog -->
<Version>%(UnreleasedChangelog.Identity)</Version>
<PackageVersion>%(UnreleasedChangelog.Identity)</PackageVersion>
<PackageReleaseNotes>@(UnreleasedReleaseNotes)</PackageReleaseNotes>
<_ReleaseDate>%(UnreleasedChangelog.Date)</_ReleaseDate>
</PropertyGroup>

<ItemGroup Condition="'@(CurrentReleaseChangelog)' != '' and '$(GenerateAssemblyInfo)' == 'true'">
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition="'$(GenerateRepositoryUrlAttribute)' == 'true' and ('$(RepositoryUrl)' != '' or '$(PublishRepositoryUrl)' == 'true')">
<ItemGroup Condition="'$(_ReleaseDate)' != '' and '$(GenerateAssemblyInfo)' == 'true'">
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition="'$(GenerateAssemblyBuildDateAttribute)' == 'true'">
<_Parameter1>BuildDate</_Parameter1>
<_Parameter2>%(CurrentReleaseChangelog.Date)</_Parameter2>
<_Parameter2>$(_ReleaseDate)</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
</Target>
Expand Down
8 changes: 8 additions & 0 deletions src/build/Ionide.KeepAChangelog.Tasks.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- If set, the assembly/package version and release notes will be set from Unreleased changes, if any are present -->
<GenerateVersionForUnreleasedChanges Condition="'$(GenerateVersionForUnreleasedChanges)' == ''">true</GenerateVersionForUnreleasedChanges>
<!-- If true, assembly metadata for the build date of the release will be written -->
<GenerateAssemblyBuildDateAttribute Condition="'$(GenerateAssemblyBuildDateAttribute)' == ''">true</GenerateAssemblyBuildDateAttribute>
</PropertyGroup>
</Project>
3 changes: 3 additions & 0 deletions src/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="../build/Ionide.KeepAChangelog.Tasks.props"/>
</Project>
Loading
Loading