Skip to content
Open
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
12 changes: 11 additions & 1 deletion src/Fallout.Build/Execution/BuildContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ namespace Fallout.Common.Execution;
/// <remarks>
/// FT-2 / <see href="https://github.com/Fallout-build/Fallout/issues/307">#307</see>. Intentionally
/// <c>internal</c> — not a public contract until the SDK lands (milestone #7). Subsequent steps move
/// the per-run services (parameters, logging scope, tool-path config) onto this context.
/// the remaining per-run services (logging scope, tool-path config) onto this context; the parameter
/// service already rides it (FT-4 / <see href="https://github.com/Fallout-build/Fallout/issues/309">#309</see>).
/// </remarks>
internal sealed class BuildContext : IDisposable
{
Expand All @@ -34,6 +35,15 @@ internal sealed class BuildContext : IDisposable
private readonly ConsoleCancelEventHandler onCancelKeyPress;
private readonly EventHandler onToolOptionsCreated;

/// <summary>
/// The parameter service for this run. FT-4 / <see href="https://github.com/Fallout-build/Fallout/issues/309">#309</see>:
/// this instance is what the static <see cref="ParameterService.Instance"/> facade resolves to, so a
/// build reads parameters through the same instance form the specs already use, and the service's
/// mutable fields die with the run instead of leaking into the next one.
/// </summary>
public ParameterService Parameters { get; } =
new(() => EnvironmentInfo.ArgumentParser, () => EnvironmentInfo.Variables);

private BuildContext()
{
onCancelKeyPress = (_, _) => cancellationHandlers.ForEach(x => x());
Expand Down
12 changes: 9 additions & 3 deletions src/Fallout.Build/Execution/ParameterService.Statics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Fallout.Common.Execution;
using Fallout.Common.Utilities;

namespace Fallout.Common;

internal partial class ParameterService
{
internal static ParameterService Instance = new(
() => EnvironmentInfo.ArgumentParser,
() => EnvironmentInfo.Variables);
// FT-4 (#309): the active service is the per-run one on BuildContext, so a build reads parameters
// through the same instance form the specs use and nothing leaks into the next run. The fallback
// covers access outside a run — process-wide, but there is no cross-run state to leak there — and
// can retire once that path is confirmed dead.
private static readonly Lazy<ParameterService> ambientInstance = new(
() => new ParameterService(() => EnvironmentInfo.ArgumentParser, () => EnvironmentInfo.Variables));

internal static ParameterService Instance => BuildContext.Current?.Parameters ?? ambientInstance.Value;

public static T GetParameter<T>(string name, char? separator = null)
{
Expand Down
46 changes: 46 additions & 0 deletions tests/Fallout.Build.Specs/BuildContextSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,52 @@ public void Dispose_clears_current()
BuildContext.Current.Should().BeNull();
}

[Fact]
public void Parameter_service_facade_resolves_the_current_contexts_instance()
{
using var context = BuildContext.Activate();

// FT-4 (#309): the static facade is a pointer at the running context, not its own singleton.
ParameterService.Instance.Should().BeSameAs(context.Parameters);
}

[Fact]
public void Parameter_service_facade_falls_back_to_an_ambient_instance_outside_a_run()
{
BuildContext.Current.Should().BeNull();

// Nothing to route to outside a run, so the facade hands back a stable process-wide instance
// rather than throwing — this is the path the fallback exists for.
ParameterService.Instance.Should().NotBeNull();
ParameterService.Instance.Should().BeSameAs(ParameterService.Instance);
}

[Fact]
public void Each_run_gets_its_own_parameter_service()
{
ParameterService first;
ParameterService second;

using (var context = BuildContext.Activate())
first = context.Parameters;
using (var context = BuildContext.Activate())
second = context.Parameters;

second.Should().NotBeSameAs(first);
}

[Fact]
public void Parameter_service_state_does_not_leak_into_the_next_run()
{
using (BuildContext.Activate())
ParameterService.Instance.ArgumentsFromCommitMessageService = new ArgumentParser(["-arg", "value"]);

// The mutable per-run fields (commit-message args, args-from-files) died with the previous
// context — the whole point of moving the service off a process-global singleton.
using (BuildContext.Activate())
ParameterService.Instance.ArgumentsFromCommitMessageService.Should().BeNull();
}

[Fact]
public void Disposing_a_superseded_context_leaves_the_newer_one_current()
{
Expand Down
Loading