Repository navigation
Description
Usage Information
Fallout / .net 10 / Windows 11
Description
Summary
Any [Parameter] whose type derives from Fallout.Common.Tooling.Enumeration cannot be read from the parameters file. Deserialization throws, the exception is caught and logged as a warning, and the parameter silently keeps its default value.
AbsolutePath had the same root cause and was fixed in #598 by adding a dedicated JsonConverter<AbsolutePath> — but only for that one type, and only on the write path. The Enumeration family on the read path in ArgumentsFromParametersFileAttribute has no such converter.
Root cause
MSBuildTargetPlatform relies on a TypeConverter:
Fallout.Common.Tools.MSBuild.MSBuildTargetPlatform : Fallout.Common.Tooling.Enumeration
[TypeConverter(typeof(Enumeration.TypeConverter<MSBuildTargetPlatform>))]
The command-line path goes through that converter and works. The parameters-file path does not: since #174 migrated ArgumentsFromParametersFileAttribute from JObject.Parse to JsonNode.Parse / GetValue<T>, the value goes straight to System.Text.Json, which ignores [TypeConverter] — exactly the behaviour diagnosed in #588. ObjectDefaultConverter<MSBuildTargetPlatform> then treats the type as a plain POCO and expects a JSON object, so a string throws.
Newtonsoft fell back to TypeDescriptor/TypeConverter for string-to-object conversion, which is why this worked before the migration.
Why this is hard to diagnose
Three properties of the current behaviour compound each other:
The generated schema prescribes the shape the deserializer rejects. Fallout writes .fallout/build.schema.json itself and declares Platform as a string enum. Following Fallout's own documented contract is what triggers the bug — there is no correct way to write this entry.
The natural workaround is silent and worse. Rewriting the entry as an object removes the warning but yields an Enumeration with Value == null, because the Value setter is protected and System.Text.Json skips it. That instance throws on use. Details in the workaround section.
A parameters file that cannot be parsed is only a warning. ValueInjectionAttributeBase.TryGetValue swallows the exception and leaves the property initializer in place, so the build proceeds with an unconfigured value. The failure then appears somewhere else entirely — or not at all, as a gated target that quietly stops running.
Scope
Every Enumeration-derived parameter read from the parameters file. Platform, Configuration and Verbosity are all affected.
Suggested fix
Register a JsonConverter for Enumeration-derived types in the JsonSerializerOptions used by ArgumentsFromParametersFileAttribute, reading the JSON string and delegating to the existing Enumeration.TypeConverter<T>. This mirrors #598, but generically for the Enumeration family and on the read path.
Independently, consider failing the build when a value in the parameters file cannot be converted, rather than falling back to the default.
Related
- Add an additional type converter for
System.Text.Json#598 — same defect forAbsolutePath; fixed for that type only, write path only AddProperty/SetPropertyserialization may produce unexpected results #588 — documents that System.Text.Json ignores[TypeConverter]- feat!: close the #83 Newtonsoft migration tree — STJ-6 + STJ-2 tail (#119, #115) #174 — the migration that moved
ArgumentsFromParametersFileAttributeto System.Text.Json - Environment Variables prepended with
FALLOUT_don't work. #457 — another parameter that silently fails to inject
Environment
Fallout Execution Engine 10.4.0.15, Windows, .NETCoreApp v10.0.
Reproduction Steps
Minimal repro — three files, Fallout.Common 10.4.0 as the only dependency. No MSBuild and no solution involved; the target only logs the parameter value, so this isolates parameter injection.
.fallout/parameters.json
{
"Platform": "x86"
}build/_build.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace></RootNamespace>
<FalloutRootDirectory>..</FalloutRootDirectory>
<FalloutScriptDirectory>..</FalloutScriptDirectory>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fallout.Common" Version="10.4.0" />
</ItemGroup>
</Project>build/Build.cs
using Fallout.Common;
using Fallout.Common.Tools.MSBuild;
using Serilog;
class Build : FalloutBuild
{
public static int Main() => Execute<Build>(x => x.ShowPlatform);
[Parameter("Platform for the build")]
readonly MSBuildTargetPlatform Platform = MSBuildTargetPlatform.MSIL;
Target ShowPlatform => _ => _
.Executes(() => Log.Information("Platform = {Platform}", Platform));
}Run
dotnet run --project build/_build.csproj
Expected: Platform = x86
Actual: Platform = MSIL, preceded by a warning:
[WRN] Could not inject value for Build.Platform
[INF] Platform = MSIL
The exception is only in .fallout/temp/build.<timestamp>.log, not on the console:
W | | Could not inject value for Build.Platform
System.Text.Json.JsonException: The JSON value could not be converted to Fallout.Common.Tools.MSBuild.MSBuildTargetPlatform. Path: $ | LineNumber: 0 | BytePositionInLine: 5.
at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Deserialize(Utf8JsonReader& reader, ReadStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObject(Utf8JsonReader& reader, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadFromSpanAsObject(ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
at System.Text.Json.JsonSerializer.ReadFromNodeAsObject(JsonNode node, JsonTypeInfo jsonTypeInfo)
at Fallout.Common.Execution.ArgumentsFromParametersFileAttribute.<>c__DisplayClass0_0.<OnBuildCreated>b__5(String parameter, Type destinationType)
in src/Fallout.Build/Execution/Extensions/ArgumentsFromParametersFileAttribute.cs:line 80
at Fallout.Common.ParameterService.<GetParameter>g__TryFromProfileArguments|18_3(<>c__DisplayClass18_0&)
in src/Fallout.Build/Execution/ParameterService.cs:line 150
at Fallout.Common.ParameterService.GetParameter(String parameterName, Type destinationType, Nullable`1 separator)
in src/Fallout.Build/Execution/ParameterService.cs:line 155
at Fallout.Common.ParameterService.GetFromMemberInfo(MemberInfo member, Type destinationType, Func`4 provider)
in src/Fallout.Build/Execution/ParameterService.cs:line 129
at Fallout.Common.ParameterService.GetParameter[T](MemberInfo member, Type destinationType)
in src/Fallout.Build/Execution/ParameterService.Statics.cs:line 32
at Fallout.Common.ParameterAttribute.GetValue(MemberInfo member, Object instance)
in src/Fallout.Build/ParameterAttribute.cs:line 50
at Fallout.Common.ValueInjection.ValueInjectionAttributeBase.TryGetValue(MemberInfo member, Object instance)
in src/Fallout.Build/Execution/Extensibility/ValueInjectionAttributeBase.cs:line 18
Two more runs, same repro
The command line works — that path still goes through Enumeration.TypeConverter<T>:
dotnet run --project build/_build.csproj -- --platform x86
[INF] Platform = x86
The obvious workaround makes it worse. Writing the value as an object satisfies ObjectDefaultConverter, so the warning disappears — but Enumeration.Value has a protected setter (Family, HideBySig, SpecialName), so System.Text.Json skips it and returns an instance with Value == null:
{
"Platform": { "Value": "x86" }
}[INF] Platform = Capturing the property value threw an exception: ArgumentException
No warning, no error — just a broken object handed to the build.
Summary of the three runs
.fallout/parameters.json |
command line | resulting Platform |
warning |
|---|---|---|---|
"Platform": "x86" |
— | MSIL (the default) |
yes |
"Platform": "x86" |
--platform x86 |
x86 |
no |
"Platform": { "Value": "x86" } |
— | broken instance, Value == null |
no |
Environment: Fallout Execution Engine 10.4.0.15, Windows, .NETCoreApp v10.0.
Expected Behavior
Platform is MSBuildTargetPlatform.x86, matching the parameters file.
"Platform": "x86" is the shape Fallout's own generated .fallout/build.schema.json prescribes:
"Platform": { "type": "string", "enum": ["arm", "MSIL", "Win32", "x64", "x86"] }A parameters file that validates against that schema should be readable by Fallout. The same string is accepted on the command line (--platform x86), so both paths should agree.
Secondary expectation: if a value in the parameters file cannot be converted, the build should fail with that error rather than warn and silently substitute a default. A malformed build configuration is not something a build should work around on its own.
Actual Behavior
Platform keeps its default value MSIL. The value "x86" from the parameters file is discarded.
Console output:
[WRN] Could not inject value for Build.Platform
[INF] Platform = MSIL
The underlying exception appears only in .fallout/temp/build.<timestamp>.log, never on the console — System.Text.Json.JsonException: The JSON value could not be converted to Fallout.Common.Tools.MSBuild.MSBuildTargetPlatform (full stack trace in the reproduction steps above).
The build then continues with a value nobody configured. In our real build this surfaced in two ways, both far from the cause:
- MSBuild was invoked with
/p:Platform="Any CPU"and failed minutes later on a solution that only definesx86:Nothing in that message points back to the warning at startup.error MSB4126: The specified solution configuration "Debug|Any CPU" is invalid. - A target gated on
OnlyWhenDynamic(() => Platform == MSBuildTargetPlatform.x86)had quietly stopped running weeks earlier. No error, no warning about it — a build step simply no longer happened.
This affects every Enumeration-derived parameter read from the parameters file, not just Platform; Configuration and Verbosity behave the same way.
Regression?
No response
Known Workarounds
Pass the parameter on the command line. That path still goes through Enumeration.TypeConverter<T> and converts correctly:
fallout <Target> --platform x86
For a fixed value, hardcoding the default in the build class also works, at the cost of losing the parameter.
Do not rewrite the entry as an object — it looks like it works and does not:
{ "Platform": { "Value": "x86" } }ObjectDefaultConverter is satisfied, so the warning disappears, but Enumeration.Value has a protected setter (Family, HideBySig, SpecialName). System.Text.Json cannot write it, skips the property, and returns an instance with Value == null. That instance is unusable — even rendering it into a log message throws:
[INF] Platform = Capturing the property value threw an exception: ArgumentException
This form is strictly worse than the string form: no warning, no error, broken value.
Could you help with a pull-request?
No
Activity
daniatic commentedon Oct 9, 2026
I reproduced this with Configuration in Fallout 10.4.0. The current develop profile loader still calls JsonNode.Deserialize without the Enumeration converter already used by tool options.
The same loader also rejects string values for ordinary enum parameters, including framework Verbosity. The fix in #703 (profile JSON conversion and GUID schema generation) reuses EnumerationJsonConverterFactory and JsonStringEnumConverter for profile values. Explicit consumer JsonConverter attributes keep precedence. It covers nullable values and arrays, so each build does not need its own converters.
All 250 build-engine tests passed after the test audit on Windows, including new file-loading checks for Configuration, Verbosity, ordinary enums, arrays, named profile overrides, and existing consumer converters. The conversion checks fail with the original loader logic.
The draft PR also fixes #701 (GUID schemas matching runtime strings). Both changes restore agreement between profile JSON, the runtime, and generated schemas.
Should explicitly invalid profile values fail the build instead of retaining defaults? The patch keeps the existing warning/fallback policy. That behavior change needs a compatibility decision and a test proving targets do not execute after invalid configuration.