Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ Bazel aquery jsonproto ──extract_bazel.py───────┘

Done when both stages converge. The comparison is **asymmetric**: every
correctness-relevant reference flag must be present on the Bazel side; extra
Bazel flags are tolerated. No artifact/symbol diff or test execution yet.
Bazel flags are tolerated (listed in `diff.json` as `bazel_only_flags`). No
artifact/symbol diff or test execution yet.

### How targets are matched

Expand All @@ -76,11 +77,13 @@ Bazel aquery jsonproto ──extract_bazel.py───────┘
### Canonicalization

`canonicalize.py` strips noise before comparing flags. Universal mechanics
(driver/wrapper paths, `-c`/`-o`, sysroot, reproducibility defines, `-O*`/`-g*`)
are dropped in code — never your concern. Judgment calls (warning-set or cosmetic
differences) go in `any2bazel.json`'s `ignore`. Correctness flags (`-std=*`,
`-fno-exceptions`, `-fno-rtti`, …) must never be ignored — they surface as hard
errors by design.
(driver/wrapper paths, `-c`/`-o`, sysroot, reproducibility defines) are dropped
in code — never your concern. `-O*`/`-g*` are dropped from the per-TU
comparison too, but the level still matters: `levels` in `diff.json` reports
each side's, and `differs: true` there is real. Judgment calls (warning-set or
cosmetic differences) go in `any2bazel.json`'s `ignore`. Correctness flags
(`-std=*`, `-fno-exceptions`, `-fno-rtti`, …) must never be ignored — they
surface as hard errors by design.

## The migration config: `any2bazel.json`

Expand Down Expand Up @@ -286,7 +289,13 @@ python3 scripts/diff.py model.<frontend>.json model.bazel.json \
```
`diff.json` has `converged` (⇔ zero `error` discrepancies), a `discrepancies`
worklist (each with `kind`, `severity`, `target`, `tu`, `cmake_only`,
`bazel_only`), and an `excluded` map of non-participating targets by role.
`bazel_only`), an `excluded` map of non-participating targets by role,
`levels` (optimization/debug level per side), `ndebug` (assertion state per
side, and on how many compared TU pairs Bazel adds or lacks `NDEBUG`), and
`bazel_only_flags`: flags and defines only the Bazel side has. Those flags are
never a discrepancy and those defines only a WARN (the comparison is
one-directional), so a `-w` or `-Wno-error=` you add converges silently;
review the list before reporting.
`cmake_only` is a historical name: for every frontend it means "present in the
reference, absent in Bazel".
Synthetic target names `<libraries>`, `<external>` (and `<tests>` when
Expand Down
53 changes: 48 additions & 5 deletions scripts/canonicalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,44 @@ def _matches_any(flag: str, prefixes: Iterable[str]) -> bool:
return any(flag == p or flag.startswith(p) for p in prefixes)


def _macro_name(define: str) -> str:
"""The macro a define names: the identifier before any `(` (a
function-like macro, `F(x)=x`) or `=` (a value)."""
ends = [i for i in (define.find("("), define.find("=")) if i >= 0]
return define[:min(ends)] if ends else define


def _undefine(defines: List[str], undefined: List[str], name: str) -> None:
"""Apply `-U name` as the compiler does: it cancels every `name` define
that came before it. `name` is also recorded in `undefined`, the names
whose LAST word is a -U: those are emitted as `-Uname` flags and still
compared, because the -U may target a compiler-predefined macro (e.g.
-U_FORTIFY_SOURCE). A later -D on `name` clears it (see _redefine), so
whether a -U survives never depends on what came before it."""
defines[:] = [d for d in defines if _macro_name(d) != name]
if name not in undefined:
undefined.append(name)


def _redefine(defines: List[str], undefined: List[str], d: str) -> None:
"""Apply `-D d` as the compiler does: it replaces every earlier definition
of the same macro name (so each side ends with the last word per name,
whether or not a -U came between), and a -U on that name is no longer the
last word."""
key = _macro_name(d)
defines[:] = [x for x in defines if _macro_name(x) != key]
if key in undefined:
undefined.remove(key)


def _add_define(defines: List[str], d: str) -> None:
"""Append a define unless it's a hardcoded reproducibility injection
(__DATE__/__TIME__/__TIMESTAMP__), matched by key before '='."""
(__DATE__/__TIME__/__TIMESTAMP__), matched by key before '='. A define
with no value means value 1 to the compiler (`-DX` is `-DX=1`, `-DF(x)`
is `-DF(x)=1`), so it is spelled that way and the two compare equal."""
key = d.split("=", 1)[0]
if key not in _DROP_DEFINE_KEYS:
defines.append(d)
defines.append(d if "=" in d else d + "=1")


def canonicalize_flags(
Expand All @@ -199,14 +231,16 @@ def canonicalize_flags(
) -> Tuple[Tuple[str, ...], Tuple[str, ...], Tuple[str, ...]]:
"""Return (defines, includes, other_flags) in canonical form.

- defines: sorted, deduped, '-D' stripped to 'KEY=VAL'
- defines: sorted, deduped, '-D' stripped to 'KEY=VAL'; a later -U removes
them (see _undefine)
- includes: repo-relative, ORDER PRESERVED, dedup-adjacent only
- other_flags: correctness-relevant only (ignorable + bazel-default dropped),
sorted for stable comparison
"""
defines: List[str] = []
includes: List[str] = []
other: List[str] = []
undefined: List[str] = [] # names whose last word is -U (see _undefine)

i = 0
n = len(raw)
Expand All @@ -227,9 +261,16 @@ def canonicalize_flags(

# -Dfoo / -D foo
if tok == "-D" and i + 1 < n:
_add_define(defines, raw[i + 1]); i += 2; continue
_redefine(defines, undefined, raw[i + 1]); _add_define(defines, raw[i + 1])
i += 2; continue
if tok.startswith("-D"):
_add_define(defines, tok[2:]); i += 1; continue
_redefine(defines, undefined, tok[2:]); _add_define(defines, tok[2:])
i += 1; continue
# -Ufoo / -U foo: cancels the defines collected so far (last wins)
if tok == "-U" and i + 1 < n:
_undefine(defines, undefined, raw[i + 1]); i += 2; continue
if tok.startswith("-U"):
_undefine(defines, undefined, tok[2:]); i += 1; continue

# force-include flavors: -include FILE / -imacros FILE. The argument is
# a FILE path (not a search dir), which each build system may spell
Expand Down Expand Up @@ -260,6 +301,8 @@ def canonicalize_flags(

other.append(tok); i += 1

# a -U that is the last word on its name stays a comparable flag
other.extend("-U" + name for name in undefined)
# defines: order-insensitive
canon_defines = tuple(sorted(set(defines)))
# includes: order preserved, drop only consecutive dups
Expand Down
Loading
Loading