Repository navigation
Tell the agent to map the build type to flags, not to a compilation mode - #48
Open
anakanemison wants to merge 17 commits into
Open
anakanemison wants to merge 17 commits into
anakanemison wants to merge 17 commits into
Conversation
anakanemison
force-pushed
the
mschaller/u13-build-type-to-flags
branch
from
October 26, 2026 13:30
8f48388 to
c0b0afb
Compare
bazel_args is meant for the flags that make aquery mirror the real build (--config=release, -c opt). Anything that consumes it splices it into a bazel command line, but nothing checked its shape, and the SKILL.md text sits next to a literal `bazel aquery ...` line that is easy to paste in. Measured in any2bazel-lab: on a QGIS migration the agent recorded the whole aquery command as bazel_args. Its own diff never read the field, so the migration looked converged, while a consumer that spliced the field into `bazel aquery` failed on a second query expression and produced no parity result. SKILL.md now says the field holds flags, with one right and one wrong example, and runs `bazel canonicalize-flags --for_command=aquery` on it just before aquery. Bazel refuses anything that is not an aquery option, including a target pattern and a stray word after a boolean flag (`--keep_going true`), which a hand-written check would have to special-case. The one-liner hands the list to Bazel as argv (os.execvp), so a value with a space in it is not split. It does not check that a --config exists; aquery fails on one that does not. Checked against Bazel 9.3.1: it refused a whole aquery command and `--keep_going true`, and accepted `--copt=-DMSG="a b"`, `-c opt`, `--config=release` and a config with no bazel_args. The lab's later QGIS run, made with an earlier version of this change (a Python check), recorded only valid flags; this version has not been through a migration run. Lab-Proposal: P22 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
exclude_targets holds CMake names. The diff applies target_map before deciding participation, so a Bazel target mapped to an excluded CMake name is skipped. excluded_summary, which reports what was skipped and why, checked the raw Bazel label instead, so that target appeared nowhere in `excluded.bazel`: skipped, but not on the record. A new helper, _bazel_renames, returns each Bazel label with the name the diff compares it under (today, the target_map entries whose label exists), and is the one place that decision is made. diff_models renames the Bazel views with it and, as before, also applies every explicit target_map entry to dependency references (a map entry can align an external dependency that has no target node). excluded_summary checks exclusion on the renamed name while still listing the target under its Bazel label. The per-side summaries later in this series use the same helper, so they count exactly the targets the diff compares. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_union_tus pooled library TUs into one source-keyed map with dict.setdefault while iterating a set of target names. When one source is compiled into several libraries with different flags (a full and a lite build of the same file, a hardened variant), only one variant per side was compared, and which one depended on set iteration order, that is, on PYTHONHASHSEED. The verdict was not reproducible. Measured in any2bazel-lab: the same MySQL Server models gave 4,715 to 4,817 errors over 10 hash seeds, a different error set each time, and the final models of a Blender migration converged under 2 of 8 seeds. Projects without multi-variant sources were unaffected (50 recorded verdicts identical under 20 seeds). Pooling now keeps every distinct variant per source, visiting targets in sorted order. Each CMake variant is compared with its best-matching Bazel variant: fewest errors, then warnings, then the smallest symmetric difference of the two TUs' define, flag and include sets, then a stable text order. Every CMake variant must be matched by some Bazel variant and nothing is suppressed; an extra Bazel-only variant is tolerated, like an extra Bazel flag. The distance term keeps a tie from choosing a variant that only adds flags (-w, -Wno-error=...) over an exact one. A source with one variant per side takes exactly the old path, and on every single-variant verdict the lab has recorded, the result is unchanged. Executables and the opt-in test union use the same pairing, and the Java source union is visited in sorted order too. One consequence for source_map users: a token that collapses several CMake TUs (an AUTOMOC bundle, say) now yields one comparison, and so possibly one discrepancy, per distinct CMake TU, so the worklist can grow. The source_map comment in config.py says so. In the lab's later QGIS run with this change, the differ gave the same verdict under three seeds. Lab-Proposal: P14 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flag comparison is an asymmetric subset: a CMake flag missing on the Bazel side is an error, and a flag only Bazel has is tolerated. That is the right policy, since Bazel's toolchain injects flags no CMake build has, but nothing in the output said so. "0 errors" reads as "both builds compile the same way" when it means "no CMake flag is missing on the Bazel side", and a flag the Bazel side added appeared nowhere unless that TU also had a real discrepancy. Measured in any2bazel-lab: one migration's Bazel side carried 15 distinct compiler warning flags against CMake's one, including ten -Wno-error= demotions the agent added to get past -Werror, and the diff reported 0 errors and 0 warnings with an empty ignore block. Those demotions were justified; an unjustified -w would have looked the same. diff_models takes an optional DiffNotes collector. For every compared TU pair (the chosen variant pair) it counts the compile flags only Bazel has, after the ignore lists and minus the toolchain defaults already hidden from bazel_only; for every aligned executable, it does the same for link flags. summarize() reports them as "bazel_only_flags" (flag -> compared TU pairs, or executables) with a note that they are not compared; triage.py prints a one-line summary with the top entries; SKILL.md says so where diff.json is described. Severity and `converged` are unchanged. Not done: raising warning-control flags the Bazel side adds (-w, -Wno-*, -Wno-error=*) to WARN. That changes what the tool counts, so it is left for the maintainers to decide. In the lab's later graphicsgems run with this change, the agent's report named its Bazel-only flags; the earlier run's report had not. Lab-Proposal: P17 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
canonicalize.py drops -O* and -g* from both sides before comparing. That is the right rule for a per-TU diff, since the level is a build-wide setting, but nothing else looked at it, so the tool reported parity for builds compiled at a different optimization level than the reference, and SKILL.md told the agent the flags were "never your concern". Measured in any2bazel-lab across its first 35 judged migrations, with and without the skill: seven are of pono, whose reference compiles all 94 TUs at -O3, and six of those seven compile at -O2, each reported as 0 errors. lensfun's reference sets -g on all 40 TUs, and every migration lost it. On MySQL Server one migration flattened the reference's 201 -O3 TUs to -O2. summarize() now adds "levels": per side and per language (C, CXX, OBJC, OBJCXX, by source extension), histograms of the optimization level, the debug level and the debug format each compared compile action carries. The last flag of each kind wins, and "(none)" means absent. Level and format are kept apart because they vary independently: `-g3 -gdwarf-4` and `-g1 -gdwarf-4` share a format, and only -g3 emits macro information. A -gdwarf-N with no level flag counts as -g, since it enables debug info. "differs" is set when, for a language both sides compile, any of the three differs. It reads the raw argv the models already hold, over exactly the targets the diff compares (participating roles, minus exclude_targets on the name the diff compares under, minus Bazel header-processing actions), so no new extraction is needed. It never affects errors or `converged`. triage.py prints the levels, and SKILL.md says where to look instead of calling the flags no concern. In the lab's later runs with this series, pono kept -O3 (8 of 9 earlier pono migrations were at -O2), lensfun and openttd kept -g (no earlier lensfun migration had), and xbmc kept its reference's -O3. Lab-Proposal: P16 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
canonicalize_flags collected every -D into the define set and passed -U through as an ordinary flag, so an undefine never removed anything. A Bazel build that keeps assertions on with `build --copt=-UNDEBUG` after -c opt's -DNDEBUG was reported as defining NDEBUG (a WARN on every TU), and one that turned assertions back on against a reference compiled with -DNDEBUG converged with 0 errors. Later commits in this series report NDEBUG state and recommend exactly that -UNDEBUG, so it has to be read correctly. - canonicalize_flags applies `-U NAME` / `-UNAME` as the compiler does: it removes every NAME define collected so far, and a later -D defines it again. A -D likewise replaces every earlier definition of its name, so each side ends with the last word per macro: `-DF(x)=1 -DF(x)=2` and `-DF(x)=1 -UF -DF(x)=2` both leave F(x)=2 and compare equal. `-UNAME` is also kept as a comparable flag iff the -U is the last word on NAME (a later -D clears it), so whether it survives never depends on what either side's toolchain passed before it. Bazel's -c opt preamble (-D_FORTIFY_SOURCE=1 -DNDEBUG) followed by a project's `-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2` now compares equal to the reference (before this change it produced a discrepancy, in either direction). A last-word -U still compares a -U of a compiler-predefined macro (-U_FORTIFY_SOURCE alone). A define is matched by its macro name, the identifier before any `(` or `=`, so `-UF` cancels a function-like `-DF(x)=x`, as `gcc -E` shows. - A define with no value means value 1 to the compiler, so `-DX` is spelled `X=1` (and `-DF(x)` is `F(x)=1`); `-DX -DX=1` and `-DX=1 -DX` compare equal. Define values in diff.json now show that spelling. - extract_cmake.py synthesizes each compile argv in the order CMake's generators emit it ($DEFINES $INCLUDES $FLAGS, as in CMake 3.28's Makefile and Ninja output) instead of flags first, so a -UNDEBUG in CMAKE_CXX_FLAGS lands after, and cancels, an NDEBUG compile definition, as it does in the real command. It does not cancel Release's -DNDEBUG, which comes later in the same flags fragment; only a compile definition is cancelled. Include order changes too: structured include directories now precede any -I from the flags fragments. That has no verdict impact, since includes are compared for presence, not order. Checked against the preprocessor: a differential test generates fixed-seed random -D/-U sequences over object-like and function-like names (joined and split forms, with and without values, repeats) and requires two sequences to canonicalize to the same define set exactly when `cc -E` expands a probe source identically; it is skipped when no C compiler is installed. A larger run of the same check (3,000 sequences, 4.5 million pairs, three seeds) found no disagreement in either direction. One class is documented instead of fixed: a function-like macro that differs only by parameter names (F(x)=x vs F(y)=y) preprocesses the same but reports a define difference. Trade-off: a reference compiled with `-DNDEBUG -UNDEBUG` against a Bazel side with neither now shows a cmake-only `-UNDEBUG` flag error. It is benign (both sides compile without NDEBUG); copy the flag into copts, or add it to ignore.flags (ignore.defines does not reach it: it is a flag). Tests cover valueless defines, last-word -U in both orders, redefinition with and without an intervening -U, function-like macros in both orders, the predefined-macro case, the fortify override against Bazel's opt preamble in both directions, both NDEBUG directions through the diff, and the CMake argv order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Defines are compared both ways, but a define only the Bazel side has is a WARN on each TU, and no summary line showed it. The one that matters most is NDEBUG: Bazel's `-c opt` adds -DNDEBUG, so a migration that pins --compilation_mode=opt against a reference that keeps assertions turns them off everywhere and still converges. Measured in any2bazel-lab: four judged migrations compile with NDEBUG against a reference that passes none, and nothing in the differ's verdict or summary said so. Three migrations of CMake's own sources got it from `-c opt`, on every compile action; a GDAL migration passed -DNDEBUG itself, on 1,534 of 1,534 actions, at no optimization level. - bazel_only_flags gains "defines": each define only the Bazel side has, after ignore.defines, with the compared TU pairs it appears on. Its note says these are a WARN, not an error; triage prints the count and the top entries. - A new "ndebug" block sits next to "levels". Per side, it counts the compared compile actions that leave NDEBUG defined. With the diff's notes, "bazel_added" and "bazel_missing" count the compared TU pairs where only Bazel, or only CMake, defines it, and "differs" is true when either count is nonzero; a Bazel-only TU that is never compared cannot flip it. Both read the canonicalized defines, which apply -U in order (the previous commit), so a `build --copt=-UNDEBUG` after -c opt counts as assertions on. The counts ignore ignore.defines on purpose, so an accepted suppression cannot hide assertion state. triage.py prints one NDEBUG line after the levels. - SKILL.md mentions ndebug under "Diff against reference build". The guidance on opt's -DNDEBUG is in the build-type commit. Severity and `converged` are unchanged: a Bazel-only NDEBUG is still the existing WARN. Whether it should be an error, since it changes what the built program does, is a decision for the maintainers. In the lab's later CMake runs with this series, none added NDEBUG against the reference's none (three runs; two earlier runs had added it). Lab-Proposal: P17 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A configure_file() output never reaches a command line, so a value that differs only inside one (a config.h define, a version string) is invisible to the diff. The skill said so in a note beside the CMake extraction commands, but diff.json, the artifact people act on, said nothing, and `converged: true` read as covering them. Measured in any2bazel-lab: lensfun's generated config.h differed from the reference in 3 of 7 migrations with the skill, each with a converged diff. configured_files does populate (the lab saw it on another project); the empty lists on lensfun, which spells the command CONFIGURE_FILE, come from the uppercase bug fixed earlier in this series. diff_models now appends one WARN discrepancy, kind configured_files_not_compared, target "<configure>", when the CMake model records configured files: cmake_only lists them, and the detail gives the count and how many land on a compile include path. It never blocks `converged`. One aggregated entry rather than one per file keeps a large project's worklist readable (MySQL Server has about 200). All project-owned configured files are listed, not only those flagged as compile inputs, because that flag depends on the output sitting under a known include directory and can miss a header reached another way. A new end-to-end test covers the path from a trace through extract_cmake and the model to the diff. In the lab's later runs with this series, agents byte-compared the listed files on lensfun, CMake and QGIS; the QGIS agent wrote eleven Bazel tests that do it, and CMake's three configure headers that had differed from the reference came out identical in all three CMake runs. Lab-Proposal: P2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKILL.md said that configure_file outputs are "recorded but not yet
diffed" only in a note beside the CMake extraction commands. The paragraph
that defines done ("Done when both stages converge") carried no such
caveat, so a reader who lands there reads converged: true as covering the
whole build.
Measured in any2bazel-lab: a lensfun migration converged with its generated
config.h differing from the reference (a GLIB_VERSION_MIN_REQUIRED value
that appears in no command line), and the same divergence recurred in later
runs, each alongside a converged diff.
Adds a short "what converged does not cover" list directly under the
definition of done, starting with generated file contents and the
instruction to byte-compare them. Later commits add the other boundaries
to the same list.
Lab-Proposal: P1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKILL.md described link consistency as covering "every name-aligned executable/shared-lib". diff.py does not do that: shared libraries are in the library TU union, only executables reach the link-flag comparison, and the external-dependency check is a project-wide union, so a dependency one .so is missing passes if any other target links it. A clean diff says nothing about how a shared library links. Measured in any2bazel-lab: a pono migration converged while libpono.so lacked its MPFR link, and the Bazel-built binary crashed at load. The agent found it only by running the binary. Corrects the two places in SKILL.md and the four comments in scripts/diff.py (the module docstring, LINK_FLAGS_DIFF, _diff_link_flags and _diff_tests) that claimed shared-library link parity, and adds the boundary to the "what converged does not cover" list with the remedy: run a binary that loads each shared library. Comparing shared libraries' link lines is a larger design change (they would need identity alignment like executables) and is left to the maintainers. No later run exercised this change on the project that motivated it. On lensfun, an agent following it loaded the built shared library, found it missing a GLib symbol, and fixed the link. Lab-Proposal: P8 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CMake passes CMAKE_<LANG>_FLAGS to the link line as well as the compile line, so warning flags, -pedantic and -D/-U defines (including the -DNDEBUG of Release builds) appear in the File API's link flags. The driver ignores them when linking, and Bazel's link lines do not carry them, so each one was a link_flags_diff error that every migration had to suppress by hand in ignore.link_flags. Measured in any2bazel-lab: every converged migration of three small projects suppressed the same kinds of flag, each agent rediscovering and justifying them in its own words (-Wall -Wextra -Wformat-security -pedantic on one, -Wno-format-truncation on another, -DNDEBUG on a third). Agents also suppressed front-end flags by hand: -fexceptions on lensfun, -w and -fno-common on CMake's sources, -fvisibility=hidden on GDAL and QGIS. A hand suppression is also where a reviewer can wave a real difference through. _diff_link_flags now sends these missing CMake link flags to a new WARN kind, link_compile_only_flags, which does not block `converged`: - warnings and -Wp, preprocessor pass-throughs (-W..., but not -Wl, or -Wa,, the latter because an LTO link runs the assembler), -w and -pedantic*; - -D and -U; - front-end -f flags that steer only the compiler, listed one by one: -fexceptions, -fno-exceptions, -fcommon, -fno-common, -fvisibility-inlines-hidden and -fvisibility=. Everything else stays a link_flags_diff error. Most -f flags matter at link time, so the list is explicit rather than a prefix, and -fopenmp, -pthread, -static*, -fsanitize* and -flto* are not in it. When an -flto* flag is on either side of a link, warning controls and the front-end flags stay errors, because LTO runs code generation, and diagnostics such as -Werror=lto-type-mismatch, at link time. ignore.link_flags still silences any of them entirely. SKILL.md lists the new kind, and the front-end flags, with the other no-action WARNs. The upstream test that used -fvisibility=hidden as its example of a link error now uses -Wl,--no-undefined. A built-in list is a design choice the maintainers may prefer to own; this one is reported rather than dropped, so nothing disappears silently. Not done: raising an unreferenced <target>_EXPORTS define only when a source reads it, which would need the differ to read source files. In the lab's runs with this change, the first version of the list (without the front-end flags) still left agents suppressing -fexceptions on lensfun and -w and -fno-common on CMake. With the list above, neither project's next run suppressed a link flag by hand. Lab-Proposal: P11 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CMake File API reports a target's link line in fragments by role. Flags that a dependency carries on its link interface (Python's -Xlinker -export-dynamic, OpenMP's -fopenmp, Threads' -pthread) arrive in the "libraries" fragments, not "flags". extract_cmake.py read "libraries" only for dependency names, so these flags reached neither the dep closure nor the link-flag comparison, and a Bazel binary missing one converged. Observed in any2bazel-lab: on a Blender migration, the lab verified that -Xlinker -export-dynamic sits only in the blender target's libraries fragment. By the migrating agent's own report, the diff converged without it and Python extension modules then failed to resolve CPython symbols at run time, caught only by its functional test; the lab did not reproduce the failure, because that tree was not kept. A "libraries" fragment that is not a library and starts with "-" is now added to the target's link argv, so it is compared like any link flag. "-Xlinker X" pairs are folded into "-Wl,X" first, so the fragment is classified by what it passes to the linker. Excluded, because they position or locate the libraries around them or name the CMake build tree: -L, -framework, rpaths in every spelling (-Wl,-rpath, -Wl,--rpath, -Wl,-R, and -Xlinker -rpath -Xlinker <dir>), the whole-archive, push/pop-state, group, -Bstatic/-Bdynamic and as-needed toggles, and -force_load. canonicalize_link_flags also folds "-Xlinker X" into "-Wl,X" on both sides, before its noise filters, so the pair compares as one flag, and then joins an rpath given as two linker arguments (`-Wl,-rpath` then `-Wl,DIR`) into `-Wl,-rpath,DIR`, which is what the linker receives. So these three spellings of one rpath compare equal: `-Xlinker -rpath -Xlinker DIR` (what CMake's LINKER:-rpath,DIR becomes under Clang), `-Wl,-rpath -Wl,DIR` and `-Wl,-rpath,DIR`. `-Wl,-rpath=DIR`, `-Wl,--rpath,DIR` and `-Wl,-R,DIR` are not unified with them, as before. Bazel spells its own rpaths the first way, as $ORIGIN/, $EXEC_ORIGIN/ or @loader_path/ paths into its _solib_<cpu> directories; on the Bazel side exactly those (an anchored match) are toolchain link noise, otherwise they would appear as Bazel-only link flags on every binary. A project's own rpath (-Wl,-rpath,$ORIGIN/../lib, -Wl,-rpath=/opt/lib, -Wl,-rpath-link,...) is kept and compared. One limitation: any non-library "libraries" fragment becomes a link flag the Bazel side must have, including one that carries a CMake build-tree path Bazel cannot reproduce (-Wl,--version-script,<build dir>/sym.map; this holds when the map file's basename does not start with "lib", since a fragment ending in lib<name>.<ext> is still read as the external dependency <name>, as before). A version script decides which symbols are exported, so such a flag is not benign: its exact CMake spelling may go in ignore.link_flags only after confirming the Bazel link passes an equivalent script with the same content, and SKILL.md's link_flags_diff row now says so. A multi-token fragment is classified as a whole, as before, so `-Wl,-Bstatic -lfoo -Wl,-Bdynamic` in one fragment still loses its -lfoo; splitting fragments per token is left out because rpath pairs would then need their own pairing logic. A behavior change: a migration that converged before may now show a link_flags_diff for such a flag, most often -pthread (Threads::Threads with THREADS_PREFER_PTHREAD_FLAG) or -fopenmp. That is a real difference in the link command; add the flag to linkopts or record it in ignore.link_flags. SKILL.md says these flags are compared. No migration run exercised this change; it was checked on the stored Blender models. Lab-Proposal: P25 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The differ checks external link dependencies (-lz, -lhdf5, ...) as a project-wide union over PRODUCTION targets. A target that compiles nothing of its own is an AGGREGATE and is left out, and in a Bazel migration nearly every cc_shared_library is one: its sources live in the cc_library targets it wraps, and the external libraries it links appear only on its own link line. So a library linked only by a shared library read as missing although it was there. Found by an agent migrating QGIS in any2bazel-lab: its own diff ended with seven missing_dep errors (crypto, curl, dl, hdf5, netcdf, pthread, sz), which it traced to provider_mdal, a cc_shared_library classified as an aggregate. The lab confirmed the seven are on that target's Bazel link line. Link-only targets now take part in the external-dependency union on both sides, unless exclude_targets drops them. Their translation units are still compared through the libraries they wrap. Run over the stored models of the 42 migrations the lab has judged, each with its agent's own config, this removes exactly those seven errors and changes nothing else. (One earlier QGIS model is unreadable to both versions.) The next QGIS migration, with this change, converged. Lab-Proposal: P26 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKILL.md says to always call aquery with --features=-linker_param_file. The toolchain that rules_cc 0.2.22 autodetects on Linux implies that feature unconditionally, so analysis fails. In any2bazel-lab's migration runs, 8 of 10 agents hit the error and retried without the flag, one to three calls each, and none of the aquery outputs then held a param file reference (four runs checked). The text now names the error, says to drop the flag, and gives the check that keeps the instruction's purpose: no @....params entry in any action's arguments. Written after the runs, so no run has used it yet. Lab-Proposal: P29 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bazel_args records the extra args aquery runs with to mirror the real build. Nothing said where settings the migration itself introduces should live, so parity could be established for a named --config that a plain `bazel build //...` never applies: the claim then holds for a command no user runs. Measured in any2bazel-lab: one pono migration put the reference's Release settings behind --config=release, recorded it in bazel_args, and converged. A plain `bazel build //...` produced an assertion-enabled binary that crashes on an input where the reference prints an error and exits. The four other runs on the same project put the settings in .bazelrc's build lines and were equivalent. Adds one bullet to the "invoke aquery the way the project is built" list: settings the migration adds go in `.bazelrc` build lines, and parity must hold for the project's documented build command, which is a plain `bazel build //...` unless the project documents flags; only those documented flags go in bazel_args. The bazel_args field description now says the same, and that the example config (a project config plus the top-level -fno-exceptions BoringSSL expects) is that kind of entry. In the lab's later pono and lensfun runs with this change, bazel_args was empty and the settings were in .bazelrc build lines. Lab-Proposal: P10 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing in the procedure said which CMake configuration the reference should use, or that choosing one is a decision. An agent can turn options off before extracting the reference and then reach parity with that narrower build; both sides of the comparison move together, so no error count shows it. Measured in any2bazel-lab: a MySQL Server migration configured the reference with Router, NDB, the unit tests and the distribution's package compiler flags all turned off, recorded only in a comment in any2bazel.json. Against the project's default configuration the result showed 4,928 differences; against its own, 13. The reason given for the package flags was defensible; the problem was that the choice was silent. Agents redefined the reference with and without the skill, on small projects as well as large. "Extract the reference model" now says to configure with the project's documented default options, to surface any change as a scope decision, and to record the exact command in any2bazel.json as reference.configure_command (added to the example and the field list as record-only; config.load ignores it, like any unknown key) and never change it after the first diff. The optional test step reconfigures with tests on; that command is recorded separately, as reference.test_configure_command, so it does not count as changing the reference. In the lab's later runs with this change, every migrating agent recorded its configure command. Lab-Proposal: P13 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill never mentioned CMAKE_BUILD_TYPE or --compilation_mode, and its canonicalization text called -O*/-g* "never your concern" (reworded in the levels commit). Agents therefore map the concept, Release to `-c opt`, and lose the number: CMake's Release is -O3 -DNDEBUG, Bazel's opt is -O2; CMake's Debug is -g, Bazel's default fastbuild has neither. Measured in any2bazel-lab: all nine migrations of pono, with and without the skill, wrote `build --compilation_mode=opt` as the counterpart of Release, several saying so in a comment on the line above, and eight of the nine compile at -O2 against a reference at -O3; the ninth added `build --copt=-O3` and matched. lensfun's CMakeLists.txt defaults the build type to Debug when none is given; no lensfun migration set a compilation mode, so every one dropped -g from all 40 TUs. Adds a short "Build type" section to docs/REFERENCE-cmake.md, under a new "Writing the BUILD files" heading that "Create initial BUILD.bazel files" points to: the two mappings that differ, the reminder that a project can set its own default build type, where to read the reference's actual level (diff.json's levels, or the cache), and the two .bazelrc lines that pin it. It also says that opt adds -DNDEBUG and so turns off assertions the reference may keep (diff.json's ndebug shows it), and that `build --copt=-UNDEBUG` undoes only the toolchain's -DNDEBUG, not one a rule passes in its copts, which come later on the command line. In the lab's later runs with this change, pono kept -O3, lensfun kept -g, and CMake's sources, whose reference sets no build type, gained neither -O2 nor NDEBUG in three runs (two earlier runs had added both). Lab-Proposal: P18 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anakanemison
force-pushed
the
mschaller/u13-build-type-to-flags
branch
from
November 2, 2026 17:50
c0b0afb to
651c209
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Agents map Release to
-c optand lose the numbers: CMake's Release is-O3 -DNDEBUGand Bazel's opt is-O2, and CMake's Debug-ghas no counterpart in fastbuild. A new "Build type" section in docs/REFERENCE-cmake.md gives the mappings that differ, where to read the reference's actual level, the.bazelrclines that pin it, and that opt's-DNDEBUGturns off assertions the reference may keep.In any2bazel-lab, eight of nine pono migrations compiled at
-O2against an-O3reference, and every lensfun migration dropped-g. In later runs with this change, pono kept-O3, lensfun kept-g, and CMake's sources gained neither-O2nor NDEBUG in three runs.Stacked on #47 (tracking issue: #35).
pytest tests/: 186 passed (132 onmain). Lab proposal: P18.🤖 Generated with Claude Code