Skip to content

NativeAOT: LinkNative target fails with MSB4086 when NativeLib=Static and LinkerFlavor=lld #126978

@sbomer

Description

@sbomer

This content was created with assistance from AI.

Description

When NativeLib=Static is set (to have ILC produce a .a archive instead of a shared library), the LinkNative target in Microsoft.NETCore.Native.targets fails with:

Microsoft.NETCore.Native.targets(351,96): error MSB4086: A numeric comparison was attempted on "$(_LinkerVersion)" that evaluates to "" instead of a number, in condition '$(LinkerFlavor)' == 'lld' and '$(_LinkerVersion)' > '12'"

Root Cause

In Microsoft.NETCore.Native.Unix.targets, the _LinkerVersion detection is correctly skipped when NativeLib=Static:

<!-- Line ~332: version detection is skipped for static builds -->
<Exec Command="&quot;$(CppLinker)&quot; -fuse-ld=lld -Wl,--version"
      Condition="'$(LinkerFlavor)' == 'lld' and '$(NativeLib)' != 'Static'" ...>

However, in Microsoft.NETCore.Native.targets, the CustomLinkerArg item group inside LinkNative evaluates _LinkerVersion without the same NativeLib guard:

<!-- Line ~351: no NativeLib guard on this condition -->
<CustomLinkerArg Include="-Wl,-T,&quot;$(NativeIntermediateOutputPath)sections.ld&quot;"
                 Condition="'$(LinkerFlavor)' == 'lld' and '$(_LinkerVersion)' > '12'" />

The same issue exists on line ~368:

<WriteLinesToFile ... Condition="... '$(LinkerFlavor)' == 'lld' and '$(_LinkerVersion)' > '12'" />

Since _LinkerVersion is empty (detection was skipped), the numeric comparison '' > '12' causes MSBuild error MSB4086.

When This Triggers

This triggers when:

  1. NativeLib=Static (so version detection is skipped), AND
  2. LinkerFlavor=lld (set automatically for android, linux-bionic, and freebsd targets)

This combination occurs when Android or other platforms set NativeLib=Static to take ownership of the final native link step (producing their own shared library from the ILC .o output), following the same pattern used by xamarin-macios.

Suggested Fix

Add '$(NativeLib)' != 'Static' to the conditions on lines ~351 and ~368, consistent with the guard on the version detection itself:

<CustomLinkerArg Include="-Wl,-T,&quot;$(NativeIntermediateOutputPath)sections.ld&quot;"
                 Condition="'$(LinkerFlavor)' == 'lld' and '$(NativeLib)' != 'Static' and '$(_LinkerVersion)' > '12'" />

Alternatively, the version detection could default _LinkerVersion to 0 when skipped, so the comparison always succeeds safely.

Workaround

Clear LinkerFlavor before LinkNative runs:

<PropertyGroup>
  <LinkerFlavor />
</PropertyGroup>

This must be done inside a target (not a static PropertyGroup) because the ILC Unix targets set LinkerFlavor in a static PropertyGroup that evaluates after the consumer's import.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions