Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: mcpp-community/mcpp
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: mcpp-community/mcpp
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: feat/windows-shared-library
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 9 commits
  • 34 files changed
  • 1 contributor

Commits on Aug 17, 2026

  1. fix(modgraph,manifest,pack): module_extensions is the knob, and every…

    …thing follows it
    
    `.ixx` is not built in and should not be: the extension set is configuration,
    not a list mcpp grows one entry at a time. What that owes in return is that ONE
    declaration is enough — and three places were not holding up their end.
    
    **The scanner accepted what it could not classify.** An `.ixx` matched by
    `sources` but declared nowhere produced a compile edge whose object nothing
    links. Measured:
    
        build obj/mathkit.ixx.o | gcm.cache/mathkit.gcm : cxx_object …
          bmi_out = gcm.cache/mathkit.gcm      ← the BMI was produced
        build bin/app : cxx_link obj/main.o    ← the object is not here
        ld: undefined reference to `mk::answer@mathkit()'
    
    Two answers to "is this a module interface" and only one of them read: the
    scanner sees `export module` and records `provides`, which is why the edge got a
    `bmi_out`; the classifier says `Other`, and the link set is built from the
    classifier. Refused now, where the classification happens, naming the file, the
    extension and the key.
    
    **The lib-root convention hard-coded `.cppm`**, so packing an `.ixx` library
    started its closure at a file that does not exist:
    
        $ mcpp pack mathkit
             Interface (headers only)      ← the module interface, gone
              Withheld (nothing)
          Packed …-x86_64-linux-gnu        ← the C-SURFACE tag
    
    Silently wrong in both directions, and the second one is worse than the first:
    an empty published set is precisely how the packer recognises a C surface, so
    losing the interface also downgraded the compatibility tag and the gate stopped
    checking compiler and stdlib. The convention now offers one candidate per
    declared extension and the packer takes the one that exists.
    
    **The generated manifest listed `.ixx` sources without saying what an `.ixx`
    is**, so every consumer was refused by the classifier. The package now declares
    it — computed from the published FILES, so it cannot disagree with `sources`,
    and absent entirely for a `.cppm` package, whose manifest is unchanged.
    
    ⚠️ The probing resolver lives in `mcpp.manifest.toml`, not in
    `mcpp.manifest.types` where its sibling is, and the reason is measured: probing
    needs the extension table, and adding that import to `types` — a module nearly
    everything depends on — made GCC 16.1 ICE while compiling `src/main.cpp`, an
    unrelated file, with a cleared gcm.cache. Same module-poisoning shape this
    project has hit before. The edge is not added; the function moves to where the
    edge already is.
    speak-agent committed Aug 17, 2026
    Configuration menu
    Copy the full SHA
    0b0d095 View commit details
    Browse the repository at this point in the history
  2. feat(build): generate a .def so MSVC can export a DLL

    MSVC exports nothing from a DLL without `__declspec(dllexport)` or a `.def`, so
    mcpp refused `kind = "shared"` on that ABI. The reason was right and the
    conclusion was not: CMake has shipped `WINDOWS_EXPORT_ALL_SYMBOLS` since 3.4 and
    its `bindexplib` reads COFF directly — which matters here, because `dumpbin`
    lives in a Visual Studio developer environment and mcpp's default Windows
    toolchain is clang, so a plain `mcpp build` is not inside one.
    
    `mcpp.build.coff_exports` is that reader, as a pure function over bytes. Its
    filter follows bindexplib's semantics — external storage class, defined section,
    DATA for anything that is not code, and the skip list (destructor thunks, dotted
    managed names, ARM64EC bridges) each entry of which has its own reason. Past
    65535 exports it refuses rather than truncates: PE addresses exports by 16-bit
    ordinal, and a truncated table links cleanly and then fails at whichever consumer
    needed the symbol that fell off the end.
    
    Pure over bytes so it can be tested everywhere: 16 unit tests build synthetic
    objects byte by byte, which is the only way to vary a storage class on demand,
    plus a committed real mingw-cross object — a reader fed only its own test's
    output agrees with itself and with nothing else, and macOS and Windows CI cannot
    produce COFF at all.
    
    In the graph it is `mcpp coff-def`, an edge whose inputs are the same objects
    the link consumes, so the exported surface cannot drift from what was compiled.
    A subcommand rather than a shell fragment for the reason `bmi-equal` is one: a
    generated POSIX-shell command is skipped entirely on Windows, the only platform
    this edge exists for. No edge is emitted for MinGW — its linker auto-exports, and
    a second source of truth for a DLL's surface is how the two come to disagree.
    
    Two limits survive that no tool can remove, and they are CMake's documented ones
    for the same mechanism: a consumer still needs `__declspec(dllimport)` to read
    exported DATA, and a class whose vtable is referenced must be marked whole. 258
    exercises a function across the boundary and says why.
    speak-agent committed Aug 17, 2026
    Configuration menu
    Copy the full SHA
    3f17e9c View commit details
    Browse the repository at this point in the history
  3. feat(build,manifest,pack): annotation wins over auto-export, and cl.e…

    …xe can consume a package
    
    Two things the auto-`.def` needed to be usable rather than merely present.
    
    **An author who annotates keeps their surface.** `__declspec(dllexport)` makes
    the compiler write `/EXPORT:` directives into the object's `.drectve` section.
    Generating a list on top of that exports the same names twice (LNK4197) and,
    worse, exports everything else as well — replacing a chosen public surface with
    all of it. So mcpp reads `.drectve` and stands down. Detected rather than
    configured: a manifest key for "I annotated my exports" would be a second place
    to say what the objects already say, and the two could disagree. Tested against a
    real annotated object, because the point is recognising what a COMPILER emits.
    
    **A package can now be linked by native cl.exe.** The generated manifest already
    carried each leg's link line as `ldflags`, which is GNU spelling — cl rejects
    `-L`. It now also carries the dialect-neutral pair `[target.<pred>.runtime]`
    `link_library_dirs` / `libraries`, which mcpp renders as `/LIBPATH:` + `<n>.lib`
    or `-L` + `-l<n>` from the target. Not new vocabulary: the same two keys
    `[runtime]` has had at the top level, made per-target.
    
    BOTH spellings are emitted, and that is the load-bearing part. An older mcpp
    reads only the ldflags and silently ignores the runtime block (measured), so
    dropping the ldflags would leave every older client with no link line at all. A
    newer mcpp seeing the neutral form therefore IGNORES that leg's ldflags rather
    than adding to them — adding would put `-L` back on a cl command line, which is
    the whole thing being avoided. Scoped to distribution packages: a hand-written
    manifest that states both may well mean both.
    speak-agent committed Aug 17, 2026
    Configuration menu
    Copy the full SHA
    5142dc5 View commit details
    Browse the repository at this point in the history
  4. docs: the Windows round, and where it diverged from the design

    Records the three deviations and their evidence: .ixx is NOT built in (the
    extension set is configuration); pack had to follow module_extensions too, which
    the design did not cover; and the export-macro layer became detection of
    `.drectve` rather than a new key, because a manifest key for 'I annotated my
    exports' is a second place to say what the objects already say.
    
    Also the two defects only measurement found — a .ixx library packed to a
    silently C-surface-tagged package with no interface at all, and a new module
    edge into mcpp.manifest.types making GCC 16.1 ICE on an unrelated file — and an
    honest list of what is still unverified.
    speak-agent committed Aug 17, 2026
    Configuration menu
    Copy the full SHA
    a0d9874 View commit details
    Browse the repository at this point in the history
  5. fix(pack,prepare): the neutral link form only where it says the same …

    …thing
    
    e2e 257 caught a regression the previous commit introduced, and the reason is
    worth keeping: a leg's link line is not always just a library reference.
    
    A PE/MinGW shared leg links with `-L… -Wl,-Bdynamic -lmathkit`, and
    `-Wl,-Bdynamic` only works IMMEDIATELY BEFORE the `-l` it enables — mcpp gives PE
    executables `-static`, which otherwise leaves ld in static-only mode where it
    refuses an import library and says `have you installed the static version of the
    mathkit library?`, naming neither the DLL nor `-static`.
    
    The first attempt cleared the leg's ldflags wholesale and lost the flag. The
    second kept it but rendered the library reference through the neutral channel,
    which puts it in a different slot on the command line — so the flag and its
    argument were separated and the same failure came back. Both are the same
    mistake: treating a hand-tuned link line as if it were a two-field record.
    
    So the neutral form is emitted only for legs where it is EQUIVALENT, and the
    PE/MinGW shared leg keeps the spelling that works. It costs nothing — a PE/GNU
    leg is not an MSVC-ABI leg, and cl.exe, the reason the neutral form exists, never
    reads it. On the consuming side only the library references are replaced, never
    the whole list.
    speak-agent committed Aug 17, 2026
    Configuration menu
    Copy the full SHA
    f9ca398 View commit details
    Browse the repository at this point in the history
  6. review(coff): name /bigobj, and drop a constant the DATA rule stopped…

    … using
    
    Two things a read-through caught.
    
    `/bigobj` objects are a different container — machine 0 and `0xFFFF` where the
    section count would be — and the reader fell through to "unsupported COFF machine
    0x0000". True and useless: it blames the reader for a flag the project passed.
    Named now, with the two ways out (drop /bigobj, or annotate the surface and need
    no generated .def at all).
    
    `kScnMemWrite` was left over from a first version whose DATA rule keyed on
    writability. It does not: a `const` in `.rdata` is a variable and consumers read
    it as one, so the rule keys on "not executable" instead. The constant was
    documenting a decision that is no longer taken.
    speak-agent committed Aug 17, 2026
    Configuration menu
    Copy the full SHA
    bbf785e View commit details
    Browse the repository at this point in the history
  7. fix(pack): print the compiler's output when the packer's build fails

    Windows CI reported
    
        error: build failed
        FAIL: shared pack failed off ELF
    
    and nothing else, because `mcpp pack` printed `BuildError::message` and dropped
    `BuildError::diagnosticOutput` — the field that carries what the compiler
    actually said. `mcpp build` has printed it all along; the two pack pipelines
    were the only callers that did not.
    
    That is a defect on its own terms, not just an inconvenience for this
    investigation: a packaging failure is exactly when a maintainer has least context,
    and three words is not a report. Both pipelines now print it.
    speak-agent committed Aug 17, 2026
    Configuration menu
    Copy the full SHA
    c8ac60a View commit details
    Browse the repository at this point in the history
  8. fix(flags): -fPIC is a property of the target format, not of the dialect

    Windows CI, once the packer stopped swallowing the compiler's output:
    
        error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc'
    
    every MSVC-ABI shared build died in clang-scan-deps before compiling anything.
    The comment beside the condition already said the right thing — "PE code is
    position independent by design" — and the condition tested the DIALECT:
    `!isMsvcDialect`. Windows' default toolchain is clang, which speaks the GNU
    dialect while targeting the MSVC ABI, so the flag went out anyway.
    
    Same shape as the shared-library guard this PR replaced: asking which COMPILER
    when the question is which TARGET. It keys on the target format now.
    
    Unreachable until this PR, because `kind = "shared"` was refused on that ABI —
    the refusal was hiding an untested path, which is what refusals do. 257 pins the
    absence on every Linux CI pass through mingw-cross rather than only on Windows:
    the flag is equally meaningless for a PE target whichever compiler emits it, and
    GCC merely ignores it where clang refuses.
    speak-agent committed Aug 17, 2026
    Configuration menu
    Copy the full SHA
    9bdb284 View commit details
    Browse the repository at this point in the history

Commits on Aug 18, 2026

  1. fix(build): the import-library and .def spellings follow the target ABI

    Windows CI again, one layer further in:
    
        lld-link: warning: ignoring unknown argument '--out-implib'
        lld-link: error: could not open 'bin/mathkit-shared.lib': no such file
    
    Clang targeting the MSVC ABI speaks the GNU DIALECT while driving lld-link, so a
    dialect-keyed spelling handed the MSVC linker a MinGW flag. The `.def` had the
    same problem from the other side: `/DEF:` was added only to the MSVC-dialect
    rule, so a clang-driven MSVC-ABI link generated the file and never passed it.
    
    Three flags in this PR made the identical mistake — `-fPIC`, the import library,
    and `/DEF:` — and it is always the same one: asking which COMPILER when the
    question is which TARGET. So the spelling leaves the dialect table entirely and
    becomes `pe_link_flag`, which reads the target triple and wraps in `-Wl,` unless
    the linker is invoked directly. The dialect table now says, where the entry used
    to be, why it cannot answer this.
    
    Verified in both directions locally: a MinGW target still gets
    `-Wl,--out-implib,` and no def edge; ELF and Mach-O are untouched.
    speak-agent committed Aug 18, 2026
    Configuration menu
    Copy the full SHA
    9e0ddf7 View commit details
    Browse the repository at this point in the history
Loading