Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c873516
Add the Notice message level, and align both reporters' mappings
rdeago Aug 13, 2026
3045b0d
Report facts as Notice, keeping narration at Info
rdeago Aug 13, 2026
a763056
Default to minimal verbosity for every command
rdeago Aug 13, 2026
ae1a35b
Align the CI workflows with the new default verbosity
rdeago Aug 13, 2026
415896b
Cover the reporter extension shortcuts with tests
rdeago Aug 13, 2026
863f7e6
Record pushing and publishing as release outcomes
rdeago Aug 14, 2026
c159204
Report a skipped test run at notice level
rdeago Aug 14, 2026
0836f1b
Record that a hook ran
rdeago Aug 14, 2026
df14e81
Demote the hook args dump to trace level
rdeago Aug 14, 2026
33da375
Correct the changelog entry on `bv version show`
rdeago Aug 14, 2026
c282277
Name the offending parameter "level" on an unknown message level
rdeago Aug 14, 2026
394e6a6
Wrap the over-long lines this branch modified
rdeago Aug 14, 2026
57322ee
Bring the rest of ReleaseCommand within the line limits
rdeago Aug 14, 2026
24c402c
Record the publication only once it cannot be undone
rdeago Aug 14, 2026
7b05f70
Record what a rolled-back release undoes
rdeago Aug 14, 2026
63dbee9
Name a running hook without repeating its path
rdeago Aug 14, 2026
163f84f
Read GITHUB_OUTPUT before the release exists
rdeago Aug 14, 2026
ce86eee
Bring ServerRelease and GitHubServerAdapter within the line limits
rdeago Aug 14, 2026
9e77a49
Record the GITHUB_OUTPUT fix in the changelog
rdeago Aug 14, 2026
ec6f634
Record a rollback's undone commits once, not one by one
rdeago Aug 14, 2026
e9477b3
Name the release a rollback deletes
rdeago Aug 14, 2026
5664fc2
Say what the GITHUB_OUTPUT fix actually bought
rdeago Aug 14, 2026
77e9318
Drop the public API count's unreachable singular
rdeago Aug 14, 2026
888654e
Cover the release command's counted notices
rdeago Aug 14, 2026
c91601c
Cover ServerRelease's guards and counted notices
rdeago Aug 14, 2026
ae10864
Keep the hook's two files out of a closure
rdeago Aug 14, 2026
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
8 changes: 5 additions & 3 deletions .github/workflows/build-test-pack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ jobs:
- name: Build, test, and pack with Buildvana
shell: bash
run: |
if [ -z "$BV_VERBOSITY" ]; then BV_VERBOSITY=Normal; fi
if [ "$RUNNER_DEBUG" = "1" ]; then BV_VERBOSITY=Diagnostic; fi
dotnet bv pack --verbosity "$BV_VERBOSITY"
if [ "$RUNNER_DEBUG" = "1" ]; then
dotnet bv pack --verbosity diagnostic
else
dotnet bv pack
fi
- name: Upload coverage to Codecov
if: ${{ hashFiles('TestResults/*.cobertura.xml') != '' }}
uses: codecov/codecov-action@v7
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ on:
description: 'Buildvana verbosity'
required: true
type: choice
default: 'normal'
default: 'minimal'
options:
- quiet
- minimal
- normal
- detailed
- diagnostic
Expand Down Expand Up @@ -81,7 +83,6 @@ jobs:
id: build
shell: bash
run: |
if [ -z "$BV_VERBOSITY" ]; then BV_VERBOSITY=normal; fi
if [ "$RUNNER_DEBUG" = "1" ]; then BV_VERBOSITY=diagnostic; fi
dotnet bv release --verbosity "$BV_VERBOSITY" --bump "$BV_BUMP"
- name: Upload coverage to Codecov
Expand Down
7 changes: 5 additions & 2 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/EnvironmentVariables.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Set to `true` by GitHub Actions on every step. `bv` reads it to recognize that i

### `GITHUB_OUTPUT`

Set by GitHub Actions to the path of the file that collects a step's outputs. `bv release` appends to that file to publish the released version as the `version` step output, so that later steps of the same job can refer to it; the release fails if the variable is unset. `bv` never sets this variable itself.
Set by GitHub Actions to the path of the file that collects a step's outputs. `bv release` appends to that file to publish the released version as the `version` step output, so that later steps of the same job can refer to it; the release fails if the variable is unset, and fails up front, before creating anything, rather than at the moment the output is written. `bv` never sets this variable itself.

### `GITLAB_CI`

Expand Down
33 changes: 27 additions & 6 deletions src/Buildvana.Core.Abstractions/ConsoleOutput/MessageLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,41 @@ namespace Buildvana.Core.ConsoleOutput;
/// message and, together with the reporter's <see cref="Verbosity"/>, decides whether it is shown.
/// </summary>
/// <remarks>
/// Members are ordered from highest to lowest severity, mapping one-to-one onto the <see cref="Verbosity"/>
/// thresholds: <see cref="Error"/>↔<see cref="Verbosity.Quiet"/>, <see cref="Warning"/>↔<see cref="Verbosity.Minimal"/>,
/// <see cref="Info"/>↔<see cref="Verbosity.Normal"/>, <see cref="Detail"/>↔<see cref="Verbosity.Detailed"/>,
/// <see cref="Trace"/>↔<see cref="Verbosity.Diagnostic"/>.
/// <para>Members are ordered from highest to lowest severity. There are more levels than there are
/// <see cref="Verbosity"/> thresholds, so the two do not map one-to-one: the verbosity from which each level
/// becomes visible is stated by <see cref="MessageLevelExtensions.MinimumVerbosity"/>, which is the single
/// authority on the matter and the one every <see cref="IReporter"/> implementation agrees on.</para>
/// </remarks>
public enum MessageLevel
{
/// <summary>An error: something went wrong. Shown at every verbosity.</summary>
Error,

/// <summary>A warning: something looks off but is not fatal.</summary>
/// <summary>
/// A warning: something looks off but is not fatal. Shown at <see cref="Verbosity.Minimal"/> and above.
/// </summary>
Warning,

/// <summary>An informational milestone. Shown at <see cref="Verbosity.Normal"/> and above.</summary>
/// <summary>
/// A record of a fact: something changed, something was decided, something was deliberately skipped.
/// Shown at <see cref="Verbosity.Minimal"/> and above.
/// </summary>
/// <remarks>
/// <para>Use this level for what the reader would want to know afterwards — the version spec changed, N files
/// were rewritten, a step was skipped and why — and <see cref="Info"/> for narration of what the tool is doing
/// right now. The two are not a loudness ranking: this is a quieter <see cref="Warning"/>, not a louder
/// <see cref="Info"/>, and it should feel like it costs something. A message at this level survives the default
/// verbosity, so promoting narration to it makes the default as noisy as <see cref="Verbosity.Normal"/> and
/// leaves the ladder with no rung meaning what this one means.</para>
/// </remarks>
Notice,

/// <summary>
/// Narration of what the tool is doing right now. Shown at <see cref="Verbosity.Normal"/> and above.
/// </summary>
/// <remarks>
/// <para>See <see cref="Notice"/> for the criterion that separates the two levels.</para>
/// </remarks>
Info,

/// <summary>A detail useful when following along closely. Shown at <see cref="Verbosity.Detailed"/> and above.</summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (C) Tenacom and Contributors. Licensed under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;

namespace Buildvana.Core.ConsoleOutput;

/// <summary>
/// Provides extension methods for <see cref="MessageLevel"/> values.
/// </summary>
#pragma warning disable CA1034 // Nested types should not be visible — false positive on C# 14 extension blocks; fixed in .NET 11, backport to .NET 10 requested in https://github.com/dotnet/sdk/issues/53984
#pragma warning disable CA1708 // Identifiers should differ by more than case — false positive on classes with C# 14 extension blocks; fixed in .NET 11, https://github.com/dotnet/sdk/issues/51716
public static class MessageLevelExtensions
{
extension(MessageLevel @this)
{
/// <summary>
/// Gets the least verbose <see cref="Verbosity"/> at which a message of this level is shown.
/// </summary>
/// <returns>The minimum <see cref="Verbosity"/> that enables this level.</returns>
/// <exception cref="ArgumentOutOfRangeException">This level is not a known <see cref="MessageLevel"/>.</exception>
/// <remarks>
/// <para>This method is the single authority on when a level becomes visible. Every
/// <see cref="IReporter"/> implementation answers the question through it — directly, or by mapping the
/// answer onto the visibility rules of whatever it renders through — so that a given level becomes
/// visible at the same point no matter which reporter is in play.</para>
/// <para>The mapping cannot be a comparison of the two enums' underlying values: there are more levels
/// than there are thresholds, so no ordering of the members makes such a comparison give the right
/// answer for all of them.</para>
/// </remarks>
public Verbosity MinimumVerbosity()
{
return @this switch
{
MessageLevel.Error => Verbosity.Quiet,
MessageLevel.Warning or MessageLevel.Notice => Verbosity.Minimal,
MessageLevel.Info => Verbosity.Normal,
MessageLevel.Detail => Verbosity.Detailed,
MessageLevel.Trace => Verbosity.Diagnostic,
_ => ThrowUnknownLevel(@this),
};

// The exception names the offending value "level", as every caller of this method does. The name
// cannot come from the receiver: nameof(@this) yields "this", which names the parameter the
// compiler emits rather than anything a caller can see.
static Verbosity ThrowUnknownLevel(MessageLevel level)
=> throw new ArgumentOutOfRangeException(nameof(level), level, "Unknown message level.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public static class ReporterExtensions
/// <param name="message">The message text.</param>
public void Warning(string message) => @this.Report(MessageLevel.Warning, message);

/// <summary>Reports a <see cref="MessageLevel.Notice"/> message.</summary>
/// <param name="message">The message text.</param>
public void Notice(string message) => @this.Report(MessageLevel.Notice, message);

/// <summary>Reports an <see cref="MessageLevel.Info"/> message.</summary>
/// <param name="message">The message text.</param>
public void Info(string message) => @this.Report(MessageLevel.Info, message);
Expand All @@ -53,6 +57,12 @@ public void Error(CompositeFormat format, params ReadOnlySpan<object?> args)
public void Warning(CompositeFormat format, params ReadOnlySpan<object?> args)
=> @this.Report(MessageLevel.Warning, format, args);

/// <summary>Formats and reports a <see cref="MessageLevel.Notice"/> message.</summary>
/// <param name="format">The composite format string.</param>
/// <param name="args">The arguments to format.</param>
public void Notice(CompositeFormat format, params ReadOnlySpan<object?> args)
=> @this.Report(MessageLevel.Notice, format, args);

/// <summary>Formats and reports an <see cref="MessageLevel.Info"/> message.</summary>
/// <param name="format">The composite format string.</param>
/// <param name="args">The arguments to format.</param>
Expand Down Expand Up @@ -95,7 +105,8 @@ public void Report(MessageLevel level, CompositeFormat format, params ReadOnlySp
/// </summary>
/// <param name="level">The level to test.</param>
/// <returns><see langword="true"/> if the level is enabled; otherwise, <see langword="false"/>.</returns>
public bool IsEnabled(MessageLevel level) => (int)level <= (int)@this.Verbosity;
/// <exception cref="ArgumentOutOfRangeException"><paramref name="level"/> is not a known <see cref="MessageLevel"/>.</exception>
public bool IsEnabled(MessageLevel level) => @this.IsVerbosityAtLeast(level.MinimumVerbosity());

/// <summary>
/// Determines whether the reporter's <see cref="IReporter.Verbosity"/> is at least the given
Expand Down
13 changes: 7 additions & 6 deletions src/Buildvana.Core.Abstractions/ConsoleOutput/Verbosity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ namespace Buildvana.Core.ConsoleOutput;

/// <summary>
/// Controls how much of a reporter's output reaches the user. Each level enables all the
/// <see cref="MessageLevel"/>s enabled by the levels below it (see <see cref="MessageLevel"/> for the mapping).
/// <see cref="MessageLevel"/>s enabled by the levels below it.
/// </summary>
/// <remarks>
/// The members mirror <c>bv</c>'s <c>--verbosity</c> command-line vocabulary and are ordered from least to most
/// verbose, so a message at a given <see cref="MessageLevel"/> is shown when
/// <c>(int)level &lt;= (int)verbosity</c>.
/// <para>The members mirror <c>bv</c>'s <c>--verbosity</c> command-line vocabulary and are ordered from least to
/// most verbose. Which levels each one enables is stated by
/// <see cref="MessageLevelExtensions.MinimumVerbosity"/>: a message is shown when its level's minimum verbosity
/// is at most the one in effect.</para>
/// </remarks>
public enum Verbosity
{
/// <summary>Only errors are shown.</summary>
Quiet,

/// <summary>Errors and warnings are shown.</summary>
/// <summary>Errors, warnings, and notices are shown. This is <c>bv</c>'s default, as it is the .NET CLI's.</summary>
Minimal,

/// <summary>Errors, warnings, and informational messages are shown. This is the default.</summary>
/// <summary>Everything <see cref="Minimal"/> shows, plus informational messages.</summary>
Normal,

/// <summary>Everything <see cref="Normal"/> shows, plus detail messages.</summary>
Expand Down
1 change: 1 addition & 0 deletions src/Buildvana.Core.ConsoleOutput/TextWriterReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public void ChildError(string line, Verbosity? minimumVerbosity)
{
MessageLevel.Error => (ConsoleColor.Red, "error"),
MessageLevel.Warning => (ConsoleColor.Yellow, "warning"),
MessageLevel.Notice => (null, "notice"),
MessageLevel.Info => (null, "info"),
MessageLevel.Detail => (null, "detail"),
MessageLevel.Trace => (null, "trace"),
Expand Down
2 changes: 1 addition & 1 deletion src/Buildvana.Core.Versioning/VersioningService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public VersioningService(
FileVersion = FormattableString.Invariant($"{SimpleVersion}.0");
InformationalVersion = ComputeInformationalVersion(SemVer, Spec.Prerelease, IsPublicRelease, CommitId);
var publicity = IsPublicRelease ? "public release" : "not a public release";
reporter.Info(FormattableString.Invariant($"Version {SemVer} (height {Height}, {publicity})"));
reporter.Notice(FormattableString.Invariant($"Version {SemVer} (height {Height}, {publicity})"));
}

/// <summary>
Expand Down
40 changes: 30 additions & 10 deletions src/Buildvana.Sdk.Tasks/TaskLoggingHelperReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,36 @@ namespace Buildvana.Sdk;
/// human-facing output through the build's own loggers.
/// </summary>
/// <remarks>
/// <para>Message levels map to MSBuild severities as follows: <see cref="MessageLevel.Error"/> and
/// <see cref="MessageLevel.Warning"/> become build errors and warnings; <see cref="MessageLevel.Info"/>,
/// <see cref="MessageLevel.Detail"/>, and <see cref="MessageLevel.Trace"/> become messages of
/// <see cref="MessageImportance.High"/>, <see cref="MessageImportance.Normal"/>, and
/// <see cref="MessageImportance.Low"/> importance respectively.</para>
/// <para>Message levels map to MSBuild severities so that each level becomes visible at the verbosity named by
/// <see cref="MessageLevelExtensions.MinimumVerbosity"/>, the same one at which <c>bv</c>'s console shows it:
/// <see cref="MessageLevel.Error"/> and <see cref="MessageLevel.Warning"/> become build errors and warnings;
/// <see cref="MessageLevel.Notice"/> becomes a message of <see cref="MessageImportance.High"/> importance, which
/// MSBuild shows from minimal verbosity up; <see cref="MessageLevel.Info"/> becomes
/// <see cref="MessageImportance.Normal"/>, shown from normal verbosity up; and both
/// <see cref="MessageLevel.Detail"/> and <see cref="MessageLevel.Trace"/> become
/// <see cref="MessageImportance.Low"/>, shown from detailed verbosity up.</para>
/// <para>The last two share a rung because MSBuild's ladder has three and ends there. A task cannot do better:
/// <see cref="EngineServices"/> exposes no verbosity to read, and its
/// <see cref="EngineServices.LogsMessagesOfImportance"/> is a union query across every registered logger, each
/// with a verbosity of its own — so detailed and diagnostic are indistinguishable from in here, and
/// <see cref="MessageLevel.Trace"/> surfaces one rung earlier than it would on the console.</para>
/// <para>Messages are always forwarded, regardless of <see cref="Verbosity"/>: visibility is governed by
/// MSBuild's own verbosity and importance filtering, exactly like any other message logged by a task.
/// <see cref="Verbosity"/> is derived from <see cref="IBuildEngine10.EngineServices"/> when available
/// (falling back to fully permissive otherwise), so that callers checking it — such as the formatting
/// helpers in <see cref="ReporterExtensions"/> — skip work whose output MSBuild would discard anyway.</para>
/// <para>That derivation over-claims at both ends of the ladder, deliberately, because its only consumers are
/// short-circuit checks and the cost of guessing low is a dropped message. When low-importance messages are
/// logged it reports <see cref="Verbosity.Diagnostic"/> rather than <see cref="Verbosity.Detailed"/>: the honest
/// answer would make <c>Report(Trace, format, args)</c> short-circuit and drop every formatted
/// <see cref="MessageLevel.Trace"/> message even under <c>-v:diag</c>. When nothing at all is logged it floors at
/// <see cref="Verbosity.Minimal"/> rather than <see cref="Verbosity.Quiet"/>: the honest answer would make
/// <c>Warning(format, args)</c> short-circuit and drop warnings that MSBuild's own quiet verbosity still
/// prints.</para>
/// <para>Activity start and outcome lines are logged at <see cref="MessageImportance.Normal"/> importance,
/// so they are hidden at MSBuild's default (minimal) verbosity and visible from normal verbosity up.</para>
/// so they are hidden at MSBuild's default (minimal) verbosity and visible from normal verbosity up. That is the
/// importance <see cref="MessageLevel.Info"/> maps to, and the console side gates the same lines on
/// <see cref="MessageLevel.Info"/> too, so the two agree on when an activity is narrated.</para>
/// <para>Child-process output and error lines are both forwarded as low-importance messages: MSBuild has no
/// neutral standard-error channel, and logging a build error or warning would misrepresent — and, given how
/// task success is determined, potentially fail the build over — stderr lines that many tools use for
Expand All @@ -48,9 +66,10 @@ public TaskLoggingHelperReporter(TaskLoggingHelper log, IBuildEngine engine)
_engineServices = (engine as IBuildEngine10)?.EngineServices;
}

// The two remaining rungs of MSBuild's ladder, minimal and quiet, both answer Minimal here: see the
// over-claim paragraph in this class's remarks for why quiet does not answer Quiet.
public Verbosity Verbosity => LogsMessagesOfImportance(MessageImportance.Low) ? Verbosity.Diagnostic
: LogsMessagesOfImportance(MessageImportance.Normal) ? Verbosity.Detailed
: LogsMessagesOfImportance(MessageImportance.High) ? Verbosity.Normal
: LogsMessagesOfImportance(MessageImportance.Normal) ? Verbosity.Normal
: Verbosity.Minimal;

public void Report(MessageLevel level, string message)
Expand All @@ -66,12 +85,13 @@ public void Report(MessageLevel level, string message)
case MessageLevel.Warning:
_log.LogWarning("{0}", message);
break;
case MessageLevel.Info:
case MessageLevel.Notice:
_log.LogMessage(MessageImportance.High, "{0}", message);
break;
case MessageLevel.Detail:
case MessageLevel.Info:
_log.LogMessage(MessageImportance.Normal, "{0}", message);
break;
case MessageLevel.Detail:
case MessageLevel.Trace:
_log.LogMessage(MessageImportance.Low, "{0}", message);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,25 @@

using System;
using System.Collections.Generic;
using Buildvana.Core.ConsoleOutput;

namespace Buildvana.Tool.Infrastructure.Execution;

/// <summary>
/// A discovered <c>bv</c> command: the paths it is registered under, the class that implements it, whether
/// it forwards all of its arguments verbatim, its settings type (if any), and its default verbosity. Produced by
/// it forwards all of its arguments verbatim, and its settings type (if any). Produced by
/// <see cref="CommandRegistry"/> from <see cref="ImplementsCommandAttribute"/>.
/// </summary>
/// <param name="AliasPaths">The paths the command is invoked under, each as a list of segments. The first path is canonical.</param>
/// <param name="CommandType">The class implementing the command.</param>
/// <param name="ConsumesAllArguments">Whether the command forwards all of its arguments verbatim.</param>
/// <param name="SettingsType">The command's <c>*Settings</c> type, or <see langword="null"/> if it has none.</param>
/// <param name="DefaultVerbosity">The verbosity in effect when <c>--verbosity</c> is not given.</param>
/// <param name="UsesSdk">Whether the command uses the repository's pinned Buildvana SDK and must therefore
/// pass the SDK version check before running.</param>
internal sealed record CommandRegistration(
IReadOnlyList<IReadOnlyList<string>> AliasPaths,
Type CommandType,
bool ConsumesAllArguments,
Type? SettingsType,
Verbosity DefaultVerbosity = Verbosity.Normal,
bool UsesSdk = false)
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ private static IReadOnlyList<CommandRegistration> Discover()
type,
attribute.ConsumesAllArguments,
attribute.SettingsType,
attribute.DefaultVerbosity,
attribute.UsesSdk));
}
}
Expand Down
Loading