Skip to content

Build the C++ bindings against the library beside them - #808

Merged
Rafael-SOWNet merged 2 commits into
masterfrom
fix/cpp-exporting-tracks-source
Aug 8, 2026
Merged

Build the C++ bindings against the library beside them#808
Rafael-SOWNet merged 2 commits into
masterfrom
fix/cpp-exporting-tracks-source

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator

AngouriMath.CPP.Exporting referenced AngouriMath as a NuGet package pinned to 1.4.0-preview.2 — a preview older than the 1.4.0 release itself.

<PackageReference Include="AngouriMath" Version="1.4.0-preview.2" />

So the C++ bindings, and the CPPBuild / CPPTest workflows that exercise them, were built and tested against a published package rather than the source in the same repository. Nothing changed here since that preview has reached them — and shipping 2.0 would have left the C++ layer bound to a 1.4 preview.

A ProjectReference builds clean, so nothing was holding it back.

Why this is a separate PR

Found while fixing the stale version fields in #807. I kept it out of that one deliberately: #807 only changes inert metadata, while this changes what code is compiled and tested. If the C++ layer does not survive the behaviour changes since 1.4.0-preview.2, that should show up here on its own, not take a metadata PR down with it.

So please read CPPTest on this PR as the actual verdict. I can build the exporting project locally but cannot exercise the full native round-trip here, so CI is the measurement. If it goes red, that is a real finding about the C++ layer rather than a reason to abandon the change — the bindings would then be shipping against an API they no longer match, which is worth knowing before 2.0.

Scope

This is the last live reference of its kind. AngouriMath.Terminal.Lib had the same pin on AngouriMath.Interactive, but it is already commented out there and a ProjectReference is what it actually uses.

🤖 Generated with Claude Code

@Rafael-SOWNet
Rafael-SOWNet marked this pull request as draft August 8, 2026 11:31
@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator Author

CI answered, and the answer is no — this does not work as written. Recording why, because the cause is more interesting than the change.

error NETSDK1204: Ahead-of-time compilation is not supported on the current platform
'osx-arm64'.  [Sources/AngouriMath/AngouriMath.csproj::TargetFramework=net7.0]
error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.
[Sources/Analyzers/Analyzers/Analyzers.csproj]

AngouriMath.CPP.Exporting publishes with NativeAOT. PublishAot propagates through a ProjectReference but not through a PackageReference — with the package, the dependency is a prebuilt DLL and no AOT constraint reaches it. So the moment the C++ layer builds against the source next to it, the source has to be AOT-publishable, and today it is not:

  • AngouriMath targets net7.0, and AOT on osx-arm64 needs net8.0 or later
  • Analyzers targets netstandard2.0, which cannot be AOT'd at all

Marking this draft rather than closing it — the change itself is right, it is just blocked on something upstream of it.

What this actually surfaces

The pin was hiding a constraint rather than being a mistake on its own. Two things follow, and both are worth settling before 2.0:

  1. net7.0 went out of support in May 2024. Shipping a 2.0 in 2026 that targets an unsupported framework is a maintenance liability regardless of the C++ layer, and retargeting is a breaking change — so it belongs in a major version or waits for 3.0. Retargeting to a supported framework also removes NETSDK1204 here.
  2. Whatever the target ends up being, Analyzers needs to be excluded from the AOT closure rather than retargeted, since it is a build-time analyzer.

I have not measured what retargeting would break, so I am not proposing a framework here — that is a maintainer call, and the trade-off is dropping consumers on older runtimes. netstandard2.0 presumably stays either way, which is what keeps the long tail working.

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator Author

Solved, and measured. Pushed; still a draft because it depends on #811 landing first — this branch now contains that commit, so the diff here shows both until it does.

Three things were in the way, and only the first was visible in the original CI failure.

1. The library could not be AOT compiled at all. net7.0 does not support AOT on osx-arm64 — that was NETSDK1204. Fixed by #811, which replaces net7.0 with net8.0 and net10.0.

2. PublishAot was passed as a global property. This is the interesting one:

dotnet publish -p:NativeLib=Shared -p:SelfContained=true -p:PublishAot=true -r ... -c release

A -p: on the command line reaches every project in the graph, so the AOT-support check ran against netstandard2.0 too — both AngouriMath's own netstandard2.0 target and the Analyzers project, which is netstandard2.0 because Roslyn requires it. Neither can be AOT compiled and neither needs to be; that was NETSDK1207, twice.

Declaring <PublishAot>true</PublishAot> in the project that actually publishes keeps it off the rest. So it moves into AngouriMath.CPP.Exporting.csproj and comes out of both workflow commands.

Worth noting the analyzer reference was already correct (ReferenceOutputAssembly="false", OutputItemType="Analyzer", PrivateAssets="all") — a global property bypasses all of that, which is why the usual advice did not apply.

3. The reference pulled in every target framework. Pinned to net10.0, as UnitTests already does.

Result

From a clean bin and obj:

dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r linux-x64 -c release
-> AngouriMath.CPP.Exporting.so   4393256 bytes   no errors, no IL warnings

So the C++ layer AOT-compiles against the current source. I still cannot exercise the native round-trip locally — CPPTest on this PR remains the verdict once #811 lands and this can come out of draft.

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator Author

CI got all the way to the C++ tests, and 22 of 24 passed. So the bindings do work against current source; the AOT blockers are gone.

 1/24 RunTests.ParsingTest1 .......... Passed
 ...
 8/24 RunTests.DiffTest2 ............. Passed
 9/24 RunTests.IntTest1 .............. ***Failed
10/24 RunTests.IntTest2 ............. ***Failed
11/24 RunTests.LimTest1 ............. Passed
 ...

Steps 5–10 — the native AOT build and the library copy — all succeeded on macOS. Only the two integral tests failed, and they failed for a reason that is not about the C++ layer at all.

Why those two

They compared Integrate's printed form against string literals:

auto expected = AngouriMath::Entity("x2 / 2");        // IntTest1
auto expected = AngouriMath::Entity("x2 / 2 + 2x");   // IntTest2

Neither literal says what it looks like — x2 parses as a variable named x2, not as a square. And neither survives Integrate gaining its constant of integration:

integrate(x)  ->  x ^ 2 / 2 + C

So this is a stale expectation from a 1.4-era package, pinned to a form rather than to a value — exactly what being stuck on 1.4.0-preview.2 was hiding.

Fixed by asserting the mathematics

Both now differentiate the antiderivative and check the difference from the integrand simplifies to zero, which is what the rest of the suite does and what AGENTS.md asks for.

That the form was the problem rather than the mathematics shows up in the second case: d/dx of the antiderivative of x + 2 prints as 2 + x, so even a corrected string literal would have been comparing arrangements rather than values.

Measured:

d/dx(x ^ 2 / 2 + C) x
d/dx(x ^ 2 / 2 + 2 * x + C) 2 + x
(x) - (x) simplified 0
(x + 2) - (x + 2) simplified 0

cmake is not available in this environment, so I cannot run CTest locally — CI remains the verdict. Still a draft until #811 lands, since this branch contains it.

Rafael-SOWNet added a commit that referenced this pull request Aug 8, 2026
.NET 7 left support in May 2024. The target list named it and no supported
modern framework, so a consumer on net8.0 or net10.0 resolved the
netstandard2.0 asset and silently lost the generic-math surface with it.

    was   net7.0;netstandard2.0
    is    netstandard2.0;net8.0;net10.0

netstandard2.0 stays, so .NET Framework and the older runtimes are
unaffected. Both replacements are LTS.

The generic-math folder needs INumber<T> and so must still be excluded below
net7.0. Its condition was a literal `!= 'net7.0'`, which would have dropped
it from every new framework silently; it is now a compatibility test, so
adding a target keeps it. Verified on the built assemblies rather than
assumed: IClosedArithmetics is present in net8.0 and net10.0 and absent from
netstandard2.0, with Sumf present in all three as the control.

Dropping a framework breaks anything that pins one. UnitTests pinned
net7.0 on its project reference and is repointed to net10.0; so did all nine
measurement harnesses outside this repository, which is what the
BREAKING-CHANGES entry warns about, since the failure is an unhelpful
NETSDK1005 about a missing assets target.

This also removes NETSDK1204 -- "ahead-of-time compilation is not supported
on the current platform 'osx-arm64'" -- which was what stopped
AngouriMath.CPP.Exporting from being built against the source next to it
rather than against a 1.4.0-preview.2 package. That is not fixed here, since
two further changes are needed for it, but it is no longer blocked. See #808.

5551 unit tests and 130 F# tests pass; all four packable projects build;
casbench 113/117 with 0 wrong, propcheck 0 failures, rootcheck 596/596,
simpsweep 10463/10463.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Rafael-SOWNet and others added 2 commits August 8, 2026 12:28
AngouriMath.CPP.Exporting referenced AngouriMath as a NuGet package pinned to
1.4.0-preview.2 -- a preview older than the 1.4.0 release itself. So the C++
bindings, and the CPPBuild and CPPTest workflows that exercise them, were
built and tested against a published package rather than against the source
in the same repository. Nothing changed here since that preview reached them,
and shipping 2.0 would have left the C++ layer bound to a 1.4 preview.

Three things were in the way, and only the first was obvious.

**The library could not be AOT compiled.** It targeted net7.0, and ahead-of-
time compilation is not supported for net7.0 on osx-arm64. Fixed by the
retarget in the commit below this one, which is why that has to land first.

**PublishAot was passed as a global property.** `-p:PublishAot=true` on the
command line reaches every project in the graph, so the AOT support check ran
against netstandard2.0 -- both AngouriMath's own netstandard2.0 target and
the Analyzers project, which is netstandard2.0 because Roslyn requires it.
Neither can be AOT compiled, and neither needs to be. Declaring PublishAot in
the project that actually publishes keeps it off the rest, so it moves into
AngouriMath.CPP.Exporting.csproj and out of the two workflow commands.

**The reference pulled in every target framework.** Pinned to net10.0, as
UnitTests already does.

Measured, from a clean bin and obj:

    dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r linux-x64 -c release
    -> AngouriMath.CPP.Exporting.so, 4393256 bytes, no errors and no IL warnings

AngouriMath.Terminal.Lib had the same kind of pin on AngouriMath.Interactive,
but it is already commented out there and a project reference is what it uses.
This was the last live one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
IntTest1 and IntTest2 compared Integrate's printed form against the literals
"x2 / 2" and "x2 / 2 + 2x". Neither says what it looks like: "x2" parses as
a variable of that name, not as a square. And neither survives Integrate
gaining its constant of integration, which is why these two were the only
failures of the twenty-four once the bindings were built against the current
source instead of a 1.4.0-preview.2 package.

The printed form was never the property under test. Both now differentiate
the antiderivative and check the difference from the integrand simplifies to
zero, which is what the rest of the suite does and what AGENTS.md asks for.

That the form was the problem rather than the mathematics is visible in the
second case: d/dx of the antiderivative of x + 2 prints as 2 + x, so even a
corrected string would have been comparing arrangements rather than values.

Measured: d/dx(x ^ 2 / 2 + C) is x, d/dx(x ^ 2 / 2 + 2 * x + C) is 2 + x, and
both differences simplify to 0. cmake is not available here, so CTest on CI
is the check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Rafael-SOWNet
Rafael-SOWNet marked this pull request as ready for review August 8, 2026 12:28
@Rafael-SOWNet
Rafael-SOWNet force-pushed the fix/cpp-exporting-tracks-source branch from 7fc53ed to 427a433 Compare August 8, 2026 12:28
@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator Author

#811 has landed, so this is out of draft and now shows only its own two commits. CPPTest here is the check that matters — it reached the C++ tests last time with 22 of 24 passing, and the two that failed are the stale string expectations rewritten in the second commit.

@Rafael-SOWNet
Rafael-SOWNet merged commit dc349c3 into master Aug 8, 2026
24 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the fix/cpp-exporting-tracks-source branch August 8, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant