Skip to content

build: generate the coverage report without exuberant-ctags - #407

Merged
filmil merged 1 commit into
mainfrom
fix-81-hermetic-coverage
Aug 13, 2026
Merged

filmil merged 1 commit into
mainfrom
fix-81-hermetic-coverage

Conversation

@filmil

@filmil filmil commented Aug 13, 2026

Copy link
Copy Markdown
Member

Fixes #81

Root cause

make cov runs build/showprogress.sh, which extracted the ICU function lists with:

all=$(ctags -x --c-kinds=fp $header_fullname | ...)

--c-kinds is specific to exuberant-ctags. Other ctags implementations reject it, and where no ctags is installed the report cannot be generated at all.

While confirming this, I found the script fails even earlier on a current system. Line 31 called icu-config, which ICU removed in favor of pkg-config and which distributions no longer ship:

$ which ctags icu-config
(neither found)
$ bash build/showprogress.sh
build/showprogress.sh: line 31: icu-config: command not found

So the script aborted on its first real command, before reaching the ctags call.

The change

build/icu_c_functions.awk replaces the ctags call. awk is in POSIX, so nothing extra has to be installed.

It keys on how ICU declares its C API rather than on parsing C. Every public function is introduced by a line holding U_EXPORT2, with the name either starting the next line:

U_CAPI int32_t U_EXPORT2
umsg_format(const UMessageFormat *fmt, ...);

or trailing on the same one:

U_CAPI int32_t U_EXPORT2 uloc_countAvailable(void);

Keying on U_EXPORT2 rather than on U_CAPI also covers the older U_STABLE, U_DRAFT and U_DEPRECATED spellings, which take the same shape. Function pointer typedefs use U_CALLCONV instead and are skipped. ICU's handful of C++ convenience wrappers, declared inline <Type> with the name on the next line, are still reported, because ctags reported them.

The other changes:

  • Fall back to pkg-config when icu-config is absent. icu-config is still tried first, since a hand-built ICU installs it and it reports that installation, which is what build/Dockerfile.maint sets up. Without this the script cannot run at all on a current system, so the ctags fix could not be verified.
  • Drop exuberant-ctags from build/Dockerfile.buildenv. gawk is already installed there.
  • Truncate the report detail file. It was opened with >>, so a run following one that died partway through appended to the leftovers. Easy to hit, since the script currently always dies partway through.
  • Error out when a header yields no functions, instead of recording 0 / 0. The extraction keys on how ICU spells its declarations, so a future change to that should be loud rather than silent.
  • Drop the grep -v U_DEFINE filter. It existed because ctags reported U_DEFINE_LOCAL_OPEN_POINTER macro invocations as functions. The awk extraction does not pick them up (checked on ICU 74 and ICU 77). Keeping it would have been actively harmful: under set -o pipefail, grep -v exits 1 on empty input, which killed the script silently and made the new "no functions found" check unreachable.

Verification

Ran on Ubuntu 24.04, ICU 74.2, with neither ctags nor icu-config installed.

The awk extraction agrees with ctags exactly. Running the old script and the new script against the same headers produces byte-identical output over all 40 files in coverage/:

$ diff -r old/coverage new/coverage && echo "IDENTICAL"
IDENTICAL

Per header, against both ctags implementations (exuberant-ctags 5.9, universal-ctags 5.9, which agree with each other on all 18):

18 headers, 471 symbols, mismatches: 0

Also checked against 10 ICU 77 headers, to confirm the extraction is not overfitted to the ICU version installed here. It matches ctags on 362 symbols, with three differences, all of which are ctags misparses that the awk extraction correctly skips:

Predicate   ucol.h:1547  explicit Predicate(const UCollator* ucol) : collator(ucol) {}
match       ucol.h:1594  bool match(std::u16string_view lhs, std::u16string_view rhs) const {
operator () ucol.h:1554  bool operator()(const T& lhs, const U& rhs) const {

These are C++ class members in a U_SHOW_CPLUSPLUS_API block, not ICU C API. This is why coverage/report.md changes in this PR: the checked-in report was generated against a newer ICU and lists match, operator and Predicate as unimplemented ICU functions. ucol.h goes from 8 / 54 to 8 / 51. That is the only content change to the checked-in report; no real function appears or disappears.

Checked with mawk 1.3.4, the default awk on Debian and Ubuntu. gawk and busybox awk were not installed here, so I did not run them; the script uses no extension beyond POSIX awk (match, substr, sub, RSTART, RLENGTH).

Error paths:

$ PKG_CONFIG_LIBDIR=/nonexistent PATH=/usr/bin:/bin bash build/showprogress.sh
error: cannot find the ICU headers: icu-config is not installed and pkg-config
does not know about icu-uc.  Install libicu-dev or the equivalent for your system.
exit=1

$ # include path pointing somewhere with no ICU headers
error: cannot read /nonexistent/unicode/ubrk.h

$ # header present but declaring nothing
error: no functions found in .../unicode/ubrk.h; build/icu_c_functions.awk needs updating

Workspace check:

$ cargo check --workspace
    Checking rust_icu v5.7.0 (/home/user/wt-81/rust_icu)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 30.08s

No crate sources changed, so there is no affected crate to run cargo test against. Nothing under .github/workflows/ is touched.


Generated by Claude Code

@google-cla

google-cla Bot commented Aug 13, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

`make cov` extracted the ICU function lists with `ctags -x --c-kinds=fp`.
The `--c-kinds` flag is specific to exuberant-ctags, so the report could
not be generated with the ctags that most systems ship, and could not be
generated at all where no ctags is installed.

Replace the ctags call with `build/icu_c_functions.awk`. awk is in POSIX,
so nothing extra has to be installed. The extraction keys on how ICU
declares its C API rather than on parsing C: every public function is
introduced by a line holding U_EXPORT2, with the name either trailing on
that line or starting the next one. Keying on U_EXPORT2 rather than on
U_CAPI also covers the older U_STABLE, U_DRAFT and U_DEPRECATED
spellings.

The script also called `icu-config`, which ICU removed in favor of
pkg-config and which distributions no longer ship, so the script failed
on its first line on a current system. Fall back to pkg-config when
icu-config is absent, and report a usable error when neither is found.

Two smaller fixes the above surfaced:

* The detail section of the report was opened with `>>`, so a run
  following one that died partway through appended to the leftovers.
  Truncate it.
* Report an error when a header yields no functions, rather than
  recording 0 / 0 coverage, so a future change to how ICU spells its
  declarations is visible instead of silent.

The awk extraction agrees with ctags exactly. Running the old and the new
script against ICU 74 produces byte-identical output over all 40 files in
coverage/:

    $ diff -r old/coverage new/coverage && echo IDENTICAL
    IDENTICAL

The checked-in report changes because it was generated against a newer
ICU, whose ucol.h carries a C++-only block that ctags miscounted as three
C functions named `match`, `operator` and `Predicate`. Those were listed
as unimplemented ICU API. The awk extraction skips them, so ucol.h goes
from 8 / 54 to 8 / 51.

Verified with `cargo check --workspace` (clean) and by running
`build/showprogress.sh` with neither ctags nor icu-config installed.
@filmil
filmil force-pushed the fix-81-hermetic-coverage branch from 97ee3fa to 5ae976c Compare August 13, 2026 09:23
@filmil
filmil merged commit 703f576 into main Aug 13, 2026
22 checks passed
@filmil

filmil commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

The google-cla remarks is incorrect.

@filmil
filmil deleted the fix-81-hermetic-coverage branch August 16, 2026 04:17
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.

make cov requires ctags-exuberant

1 participant