ICU audit for freebsd-launchd-mach superseded

Decision-quality survey of ICU usage across every Apple-source repo relevant to the launchd-adjacent system-services stack we're building on FreeBSD. The question was: do we need to install ICU on the freebsd-launchd-mach ISO? The audit recommended no (Option A — drop ICU, patch the handful of CF files that use it). Execution proved the recommendation wrong; see the callout below and the libicu port plan for the actual outcome.

This audit's recommendation was wrong. Decision flipped 2026-05-15.

While executing Option A (drop ICU + patch CF), three additional CF source files turned up with hard ICU includes the audit missed: CFString.c (4 ICU calls in Unicode grapheme/emoji boundary detection), CFTimeZone.c (25 ICU calls for localized time zone names), and CFBundle_Locale.c (4 ICU calls including the Apple-only ualoc_localizationsToUse — a symbol that exists only in Apple's ICU fork). The audit's "16-file drop list" missed these because the grep used to compile the list didn't catch every <_foundation_unicode/...> include shape. The three additional files are cross-referenced too heavily to drop, and one symbol (ualoc_*) doesn't exist in any non-Apple ICU.

With the audit's "~50–80 LOC patch" budget broken and a confirmed Apple-only symbol dependency, the project flipped to Option C: vendor Apple's ICU fork. The vendoring target became apple/swift-foundation-icu (not apple-oss-distributions/ICU) because the former ships headers natively under the _foundation_unicode/ namespace that swift-corelibs CF expects.

The libCoreFoundation library + libicu library now build and install green at freebsd-launchd-mach commit 416a5c9; COREFOUNDATION-OK boot smoke marker fires. The full empirical port plan + post-mortem of this audit's miss live at freebsd-libicu-port-plan.html.

Companion to the launchctl CoreFoundation spike, which picked swift-corelibs-foundation as our CF source. That spike's §14.4 left the ICU question open ("install ICU as a build dep, keep all CF source files"); this audit was meant to revisit that decision with evidence and reverse it — but the evidence turned out to be incomplete.

The body below is preserved as the original audit, both as a record of how the wrong decision was reached and because the per-component inventory of CF-ICU usage (§5–§9) remains accurate and useful for future porting work. The "what we lose" caveats (§12) and "could we vendor Apple's ICU instead?" (§13) are the most relevant sections post-flip; everything before that was reasoning toward an answer that didn't survive contact with reality.

Contents

  1. 1. What is ICU?
  2. 2. Which CoreFoundation APIs need ICU?
  3. 3. Scope of this audit
  4. 4. Method — what we grepped for
  5. 5. Currently-vendored sources
  6. 6. Apple daemons (to port)
  7. 7. Apple userland-command repos
  8. 8. Apple supporting frameworks & tools
  9. 9. Summary matrix — all 22 components
  10. 10. Decision: don't install ICU
  11. 11. Build-time vs runtime — what kind of dependency is ICU?
  12. 12. Caveats & what we lose
  13. 13. Could we vendor Apple's ICU instead?
  14. 14. Audit provenance

1. What is ICU?

ICU = International Components for Unicode, an IBM-originated C/C++ library (Apache 2.0 + ICU License) that provides industrial-grade Unicode + localization support. It's the de-facto standard backend for everything <language, region, calendar, encoding>-aware on Linux, macOS, Windows, Android, FreeBSD, and the Java/.NET runtimes. On FreeBSD it's installable via the icu port; the libraries land at /usr/local/lib/libicu{uc,i18n,data}.so (~30 MB combined).

ICU exists because text processing is much harder than it looks once you leave ASCII:

None of this is needed by C programs that handle ASCII reverse-DNS keys or UTF-8 file paths. It's the surface that human-facing apps care about — not the system-services lane.

1.1 Does FreeBSD base have ICU? What does it provide instead?

FreeBSD base does not ship ICU. Stock FreeBSD 15.0 has no libicuuc.so, libicui18n.so, or libicudata.so in /usr/lib or anywhere in the pkgbase install — ICU comes from the third-party ports tree as devel/icu (installed to /usr/local/lib if a port that needs it pulls it in). Nothing in FreeBSD's base system links ICU. The userland utilities (ls, sort, grep, etc.) do their locale-aware work without it.

What FreeBSD base does provide is the POSIX locale + NLS facilities in libc:

libc header / APIWhat it doesWhat it can't do that ICU can
<locale.h>setlocale, newlocale, uselocaleSet / query the process or thread locale (LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES)No CLDR data — only locales installed at /usr/share/locale/<name>
<langinfo.h>nl_langinfoRead locale-specific data (decimal point, thousands separator, abbreviated month names, currency symbol, date formats)Limited to the items POSIX specifies; no calendar rules, no transliteration
<string.h>strcoll, strxfrmLocale-aware byte-string comparison (for sort order)No DUCET fallback, no per-locale tailoring beyond the loaded collation table
<wchar.h>wcscoll, wcsxfrm, mblen, mbtowc, wctombWide-character / multibyte conversion + comparisonSame gap — no full Unicode collation algorithm
<wctype.h>iswalpha, iswdigit, iswspace, etc.Wide-character class tests (Unicode property queries)Only the basic POSIX-defined classes; no full Unicode property database
<time.h>strftime, strptime, nl_langinfo(D_T_FMT)Format / parse date and time strings in the current localeGregorian only; no Buddhist / Hebrew / Islamic calendars; no relative-time strings ("3 days ago")
<iconv.h>iconv_open, iconv, iconv_close (citrus iconv, in libc since FreeBSD 9)Convert text between encodings — UTF-8, UTF-16, EUC-JP, Shift_JIS, GB18030, etc.Same encoding-conversion power as ICU's converters, just a different API
mklocale, colldef, localedef toolsBuild locale-data binaries that libc consumesBuild their own NLS data, not CLDR/ICU data — format incompatible

The practical effect: POSIX C programs that want "respect the user's locale" get most of what they need from libc. The Apple userland tools we surveyed in §7 (ls, sort, grep, etc.) call setlocale/strcoll/wcscoll/nl_langinfo — libc NLS — and work fine on FreeBSD without ICU.

What you give up by relying on libc NLS instead of ICU:

For the launchd-adjacent system services: none of those gaps are felt. Service keys are ASCII reverse-DNS; plist contents are UTF-8 byte streams; timestamps are stored as absolute time intervals, not formatted for human display by the daemons themselves; encoding conversion is needed only for the niche cases the audit found and replaceable with libc strftime / strptime / iconv.

2. Which CoreFoundation APIs need ICU?

Our libCoreFoundation (swift-corelibs-foundation) routes specific CF APIs through ICU and falls back to in-tree fast paths for everything else. The map:

CF API familyNeeds ICU?Fallback path if ICU absent
CFLocale*YesNone — locale data lives in CLDR.
CFCalendar*YesNone — ICU drives all calendar arithmetic.
CFDateFormatter*YesNone — replace with libc strftime/strptime.
CFNumberFormatter*YesNone — replace with printf("%lld").
CFListFormatter*YesNone.
CFDateIntervalFormatter*YesNone.
CFRelativeDateTimeFormatter*YesNone.
CFStringTransform*YesNone.
CFRegularExpression*YesUse libc <regex.h>.
CFCharacterSetGetPredefinedPartialHardcoded ASCII for whitespace, alphanumeric. Loses Unicode-aware sets.
CFApplicationPreferences*YesNone (it's a desktop-app concept anyway).
CFStringCompareWithOptions with kCFCompareLocalizedYesDrop the flag, fall back to binary compare.
CFStringCompareWithOptions any other flagsNoASCII / binary fast paths in CFString.c.
CFString Create/GetLength/GetCString/HasPrefix/HasSuffix/Find/Append/ReplaceNoBuilt-in.
CFString case-fold (ASCII)NoBuilt-in.
CFString encoding conversion: UTF-8 / UTF-16 / ASCII / ISO Latin1NoHardcoded fast paths in CFString.c.
CFString encoding conversion: anything else (GB18030, Shift_JIS, etc.)YesLose those encodings.
CFArray / CFDictionary / CFSet / CFBagNoBuilt-in.
CFData / CFNumber / CFBooleanNoBuilt-in.
CFPropertyList (XML + binary read/write)NoUTF-8 only; doesn't need ICU.
CFURL (ASCII / UTF-8 paths)NoBuilt-in.
CFDate (storage of absolute time)NoBuilt-in — formatting is the ICU-needing part.
CFRunLoop / CFSocket / CFStream / CFMachPortNoEvent/IO plumbing.
CFBundle (load executable, find resources)NoBuilt-in.
CFUUIDNoBuilt-in.
CFErrorNoBuilt-in.

The dividing line: anything to do with presenting data to a human in their language belongs to the ICU side. Anything that operates on the structure of data (arrays, dictionaries, plists, byte streams, time intervals, run loops, sockets) is built-in.

3. Scope of this audit

Every Apple-source repo we've already vendored, plus every repo on the launchd-adjacent porting roadmap. 22 components total:

TierComponentsCount
Currently vendored / shippedlibCoreFoundation (swift-corelibs CF), libdispatch, libxpc, launchd-842 (incl. liblaunch + launchctl + support), libmach (our libsystem_kernel), bootstrap (our bootstrap_server), bootstrap_cmds (Apple MIG)7
Apple daemons to portconfigd, bootp (IPConfiguration), mDNSResponder, syslog (asl family), Libnotify (notifyd), DiskArbitration6
Apple userland-command reposfile_cmds, network_cmds, shell_cmds, system_cmds, text_cmds5
Apple supporting frameworks & toolsPowerManagement (pmset), adv_cmds (ps/top/lsof/pkill), CommonCrypto, IOKitUser (userland IOKit framework)4

The GNUstep stack (libs-base, libs-corebase, libobjc2) is intentionally out of scope. GNUstep is the gershwin desktop overlay's lane; this ISO doesn't ship GNUstep. When gershwin lands later, ICU comes with it regardless (libs-base hard-requires ICU at build time) — but that's a separate decision in a separate buildlist.

4. Method — what we grepped for

Four parallel agents, one consistent toolkit, repo-by-repo:

  1. Direct ICU usage: #include <unicode/...>, plus the prefixed-API families u_* (generic Unicode), ucol_* (collation), udat_* (date), unum_* (number), ureg_* (regex), unorm2_* (normalization), ucnv_* (encoding converters), uloc_* (locale), usearch_* (search).
  2. CF-ICU API usage: every CF API from the table in §2CFLocale*, CFCalendar*, CFDateFormatter*, etc.
  3. Locale-sensitive string compare: kCFCompareLocalized as a flag to CFStringCompareWithOptions.
  4. Exotic encoding conversion: kCFStringEncodingGB*, kCFStringEncodingShiftJIS, kCFStringEncodingEUC_*, kCFStringEncodingBig5, kCFStringEncodingISO_2022*, etc. ASCII/UTF-8/UTF-16/Latin1 are free; anything else needs ICU's encoding tables.
  5. Link-line evidence: Makefiles, xcconfigs, pbxproj scanned for -licu*, libicu*, libicucore, libicudata. Direct link is the unambiguous signal of runtime dependency.

5. Currently-vendored sources

What we already ship on the freebsd-launchd-mach ISO. The question is whether anything currently linked into the build pulls ICU.

libCoreFoundation/  contains ICU files

What it is: swift-corelibs-foundation's pure-C CoreFoundation, vendored 2026-05-15 as src/libCoreFoundation/. Provides CFArray, CFDictionary, CFString, CFPropertyList, etc. — the engine every Apple system service expects.

ICU usage: 13 of the 78 .c files include <_foundation_unicode/...> (swift-corelibs's renamed ICU header path): CFLocale.c, CFLocaleIdentifier.c, CFLocaleKeys.c, CFCalendar.c, CFCalendar_Enumerate.c, CFDateFormatter.c, CFNumberFormatter.c, CFListFormatter.c, CFDateIntervalFormatter.c, CFRelativeDateTimeFormatter.c, CFStringTransform.c, CFRegularExpression.c, CFCharacterSet.c, CFICUConverters.c, CFStringEncodingDatabase.c, CFApplicationPreferences.c. About 30k LOC of the total 98k.

Resolution: drop these files from the Makefile's SRCS. The remaining 65k LOC covers everything launchctl and the planned daemon ports actually use — CFString basic ops, CFArray/Dictionary/Set/Bag, CFData/Number/Boolean, CFPropertyList (XML + binary), CFURL, CFRunLoop, CFSocket, CFStream, CFBundle, CFMachPort, CFMessagePort, CFNotificationCenter, CFRuntime, etc.

libdispatch/  no ICU

What it is: swift-corelibs-libdispatch (Grand Central Dispatch). Async/concurrency runtime + the Mach-RECV backend the launchd/bootstrap stack relies on.

ICU usage: zero. ripgrep over src/libdispatch/: no #include <unicode/, no u_* / ucol_* / udat_* calls, no CF-ICU API calls. The few CF references in tests reach for CFRunLoop/CFData/SecTransform — none ICU-backed.

libxpc/  no ICU

What it is: ravynOS-derived libxpc. The XPC connection-and-message layer above Mach, used by every Apple daemon for typed-dictionary IPC.

ICU usage: zero. Plain C; serialization is libnv-over-Mach, not bplist; no CFString / encoding-conversion / locale paths.

launchd/ (launchd-842, incl. liblaunch + launchctl + support)  no ICU

What it is: Apple's last open-source launchd (842.92.1) vendored 2026-05-13. The PID-1 init replacement, plus liblaunch (the launch_data_t IPC library) and support tools (launchctl, launchproxy, wait4path).

ICU usage: zero. The daemon itself uses no CF at all (audited 2026-05-15 — 0 CF calls across launchd.c + core.c + runtime.c + ipc.c + log.c + kill2.c + ktrace.c). liblaunch uses launch_data_t (its own C type system), not CF. The lone CF consumer is launchctl.c, which calls CFStringCompareWithOptions at lines 734 and 739 with options = 0 (binary compare, no locale flag), and all 8 encoding calls use kCFStringEncodingUTF8. No ICU paths exercised.

libmach/  no ICU

What it is: our own libsystem_kernel implementation — userland Mach trap wrappers, MIG runtime stubs, host-special-port APIs.

ICU usage: zero. Pure C against libc + the FreeBSD syscall infrastructure. No #include <unicode/ anywhere.

bootstrap/  no ICU

What it is: our standalone Phase G2 bootstrap_server daemon — allocates the host bootstrap port, runs the check_in/look_up loop.

ICU usage: zero. Pure C, Mach IPC only. Service names are ASCII reverse-DNS labels.

bootstrap_cmds/ (Apple MIG)  no ICU

What it is: Apple's bootstrap_cmds-138 vendor drop — the Mach Interface Generator (mig + migcom).

ICU usage: zero. byacc-and-flex compiler; processes .defs files into C stubs. No locale or Unicode concerns.

6. Apple daemons (to port)

The six Apple-source daemons on the launchd-adjacent porting roadmap.

configd  3 narrow CF-ICU touchpoints, all replaceable

What it is: Apple's configd daemon (apple-oss-distributions/configd) + the SystemConfiguration.framework API. Maintains the cross-process SCDynamicStore (a CFDictionaryRef-shaped key/value store) for network, hostname, and reachability state.

Direct ICU usage: none. No #include <unicode/, no -licu / libicucore link line in any of configd's Makefiles or xcconfigs.

CF-ICU API usage: three sites, all narrow:

Resolution: ~50 LOC of stub work in our configd port, all in cosmetic / debug paths. No core SCDynamicStore functionality depends on ICU.

bootpIPConfiguration  1 optional feature uses CF-ICU

What it is: Apple's DHCP / DHCPv6 / IPv6-RA client daemon (apple-oss-distributions/bootp). Replaces FreeBSD's dhclient / dhcpcd. Maintains per-interface lease state in configd via SystemConfiguration.

Direct ICU usage: none. No link-line evidence.

CF-ICU API usage: one feature, exhaustively:

Resolution: two options. (a) Drop the PvDInfo subsystem entirely — it's a single optional plugin. (b) Replace the 5-line CFDateFormatter parsing block with libc strptime("%Y-%m-%dT%H:%M:%SZ") + timegm. (b) is cheaper if anyone cares about RFC 8801 support.

mDNSResponder  1 gated feature uses ICU directly, gate is off by default

What it is: Apple's multicast-DNS / Bonjour daemon (apple-oss-distributions/mDNSResponder) + the dns-sd CLI + libdns_sd client library. mDNSCore (the protocol engine) is portable C and runs on Linux/BSD/embedded; mDNSPosix/ is the POSIX platform binding.

Direct ICU usage: exactly one feature, gated:

CF-ICU API usage: none.

Resolution: leave USE_LIBIDN undefined, as upstream already does. Lose: nothing user-visible — mDNS labels are RFC 1035 ASCII anyway; international hostname punycode is handled at the resolver layer above mDNS, not inside the daemon.

syslog (asl family: syslogd, aslmanager, syslog CLI, libasl)  no ICU

What it is: Apple's structured-logging family (apple-oss-distributions/syslog). Apple System Logger is the pre-unified-logging structured-log infrastructure that replaces BSD syslogd on macOS; log records are key/value dictionaries.

ICU usage: zero direct, zero CF-ICU, no -licu link line. Log messages are UTF-8 byte streams; query strings are ASCII. Time formatting happens client-side; the daemon stores absolute timestamps.

Libnotify (notifyd + libnotify + notifyutil)  no ICU

What it is: Apple's lightweight named-event pub/sub daemon (apple-oss-distributions/Libnotify) — processes register interest in named events ("com.apple.system.timezone changed", "com.example.foo"), the daemon coordinates delivery.

ICU usage: zero. Event names are ASCII reverse-DNS labels; no string-processing surface; no time formatting; no locale concerns. Pure Mach-port-driven C.

DiskArbitration  no ICU

What it is: Apple's diskarbitrationd + DiskArbitration.framework client API (apple-oss-distributions/DiskArbitration). Notifies userland of disk insertion / ejection / mount events; arbitrates which process gets to fsck/mount what.

ICU usage: zero direct, zero CF-ICU APIs. The daemon's surface is device paths (UTF-8), BSD device names (ASCII), volume names (UTF-8). The optional autodiskmount binary uses kCFStringEncodingMacRoman for an HFS volume-name encoding lookup, but that's a single-byte ASCII-superset encoding handled by CFLite's built-in single-byte converters — no ICU needed.

7. Apple userland-command repos

Five Apple-source command repos cloned locally as siblings of the daemon repos. Together they cover the userland CLI tools the project might want to port for parity with macOS.

file_cmds  no ICU

What it is: Apple's file-manipulation commands — cp, mv, ls, find, stat, chmod, chown, rm, cmp, plus their friends. Most of these are also in FreeBSD base; the question is whether Apple's versions add anything.

ICU usage: zero direct, zero CF-ICU, no -licu link line. ls uses libc strcoll + nl_langinfo for locale-aware sorting; that's NLS (libc-supplied on FreeBSD), not ICU.

network_cmds  no ICU

What it is: Apple's network configuration utilities — ifconfig, route, ping, traceroute, arp, ndp, natd, netstat, etc. Many of these have FreeBSD equivalents; some have Apple-only options.

ICU usage: zero direct, zero CF-ICU, no -licu link line. Network names, IP addresses, interface labels are all ASCII; no locale processing.

shell_cmds  no ICU

What it is: shell-builtin-like utilities — basename, dirname, env, test, getconf, locate, limits, nice, nohup, etc.

ICU usage: zero. Tiny POSIX utilities.

system_cmds  no ICU

What it is: system-administration utilities — chkpasswd, vipw, ac, sa, passwd, zprint, etc. Note: defaults(1), plutil(1), pmset are NOT in this snapshot; they live in separate Apple OSS drops.

ICU usage: zero. The one near-miss is zprint/zprint.c:1164: CFStringCompareWithOptionsAndLocale(..., kCFCompareNumerically, NULL) — locale arg is NULL, flag is pure-numeric collation (CFLite handles without ICU).

text_cmds  no ICU

What it is: text-processing utilities — cat, head, tail, tr, grep, awk, sed, sort, uniq, jq, etc.

ICU usage: zero direct ICU. Two notable false positives caught and adjudicated:

sort, grep, etc. use libc strcoll/wcscoll/setlocale for locale-aware processing — not ICU.

8. Apple supporting frameworks & tools

Four more Apple OSS repos that the launchd-adjacent stack might link or transitively depend on. Audited for completeness so the decision rests on a closed set.

PowerManagement (pmset CLI + pmconfigd daemon)  CF-ICU used for human-readable timestamps; replaceable with strftime

What it is: Apple's power-management subsystem (apple-oss-distributions/PowerManagement) — the pmset CLI, the pmconfigd daemon, and the libraries that publish power state to other daemons.

Direct ICU usage: none.

CF-ICU API usage: 3 binaries touch CF-ICU:

Resolution: all uses are replaceable with libc strftime/strptime/timegm. The code already pins ISO-style "yyyy-MM-dd HH:mm:ss ZZZ" templates in several spots, so the libc swap is straightforward. ~40 LOC of pure mechanical replacement across 6 files.

adv_cmds  no ICU

What it is: Apple's "advanced commands" — ps, top, lsof, pkill, killall, finger, fingerd, locale, localedef, colldef, mklocale, locate.

ICU usage: zero. locale/localedef build libc locale data, not ICU data. setlocale/newlocale calls resolve through libc/NLS. No CF references in any tool.

CommonCrypto  no ICU

What it is: Apple's libcommonCrypto — the C wrapper around CommonCrypto's symmetric ciphers, hashes (SHA family), HMAC, etc. Linked by configd, mDNSResponder, and other daemons for hashing operations.

ICU usage: zero. Pure crypto primitives; no string/locale surface.

IOKitUser  1 vestigial CF-ICU site, trivially replaceable

What it is: the userland IOKit.framework client library — IORegistryEntryFromPath, IORegistryEntryCreateCFProperty, IOServiceMatching, etc. Linked heavily by DiskArbitration, IPConfiguration, PowerManagement.

Direct ICU usage: none.

CF-ICU API usage: one vestigial site:

Resolution: ~5-line replacement. The function name suggests it predates Apple noticing the locale call wasn't actually needed.

9. Summary matrix — all 22 components

ComponentTierDirect ICUCF-ICU APIsLocale compareExotic encodingsLink lineVerdict
libCoreFoundationvendoredin 13 filesn/a (it IS CF)n/an/an/adrop 13 .c files
libdispatchvendoredNoNoNoNoNoICU-free
libxpcvendoredNoNoNoNoNoICU-free
launchd-842 (incl. launchctl)vendoredNoNoNoNoNoICU-free
libmachvendoredNoNoNoNoNoICU-free
bootstrapvendoredNoNoNoNoNoICU-free
bootstrap_cmdsvendoredNoNoNoNoNoICU-free
configddaemonNo3 sitesNoNoNostub-replaceable
bootp (IPConfiguration)daemonNo1 featureNoNoNostrptime-replaceable
mDNSResponderdaemongated offNoNoNoNogate stays off
syslog (asl)daemonNoNoNoNoNoICU-free
Libnotify (notifyd)daemonNoNoNoNoNoICU-free
DiskArbitrationdaemonNoNoNoNoNoICU-free
file_cmdscmdsNoNoNoNoNoICU-free
network_cmdscmdsNoNoNoNoNoICU-free
shell_cmdscmdsNoNoNoNoNoICU-free
system_cmdscmdsNoNoNoNoNoICU-free
text_cmdscmdsNoNoNoNoNoICU-free
PowerManagementextrasNo3 binariesNoNoNostrftime-replaceable
adv_cmdsextrasNoNoNoNoNoICU-free
CommonCryptoextrasNoNoNoNoNoICU-free
IOKitUserextrasNo1 siteNoNoNo5-line replace

Tally: 17 of 22 components are fully ICU-free with no work. 4 (configd, bootp, PowerManagement, IOKitUser) touch CF-ICU APIs in narrow paths totaling ~100 LOC across all four; each path has a direct libc-equivalent replacement. 1 (mDNSResponder) has direct-ICU code gated by an undefined macro. 1 (libCoreFoundation itself) ships ICU-using files we simply drop from SRCS.

10. Decision: don't install ICU superseded

This decision was wrong. See the top-of-page callout and the libicu port plan. The 16-file drop list below missed three additional CF source files with hard ICU includes; the partial drop wouldn't have linked. apple/swift-foundation-icu was vendored at src/swift-foundation-icu/ instead. Verdict block preserved verbatim below for record-keeping.

Do not install ICU on the freebsd-launchd-mach ISO. Drop the 9 ICU-using .c files from libCoreFoundation's SRCS. Plan to stub the ~100 LOC of CF-ICU consumer code in configd / bootp / PowerManagement / IOKitUser with libc strftime / strptime / localtime_r / direct CFStringCreateWithFormat when porting each daemon. Leave USE_LIBIDN undefined in mDNSResponder (matches upstream's default).

Concrete changes in the libCoreFoundation Makefile — drop these files from SRCS:

CFLocale.c                CFLocaleIdentifier.c       CFLocaleKeys.c
CFCalendar.c              CFCalendar_Enumerate.c
CFDateFormatter.c         CFNumberFormatter.c
CFListFormatter.c         CFDateIntervalFormatter.c  CFRelativeDateTimeFormatter.c
CFStringTransform.c       CFRegularExpression.c
CFCharacterSet.c          CFStringEncodingDatabase.c CFICUConverters.c
CFApplicationPreferences.c

Add a Makefile comment naming the audit so a future maintainer who finds "why isn't CFLocale here?" gets the answer in one place.

11. Build-time vs runtime — what kind of dependency is ICU?

"Do we need ICU" splits into two questions, and the answer is different at each stage. Spelling it out so the consequences of the drop are unambiguous:

11.1 If we KEEP the ICU-using .c files in SRCS (the unstripped state)

StageICU needed?Why
Build timeYescc -c CFLocale.c resolves #include <unicode/uloc.h> — ICU's -dev headers must be on the build host. ld resolves uloc_getDefault against libicuuc.so — ICU's shared libraries must be on the build host.
Link timeYeslibCoreFoundation.so.6 ends up with DT_NEEDED libicuuc.so, DT_NEEDED libicui18n.so, DT_NEEDED libicudata.so in its dynamic section.
RuntimeYesEvery binary linking libCoreFoundation.so.6 transitively requires the three libicu*.so files on the running system. rtld resolves them at exec time; missing libs ⇒ shared object "libicuuc.so.74" not found at startup.
ISO disk cost~30 MiB across the three libicu*.so files, dominated by libicudata.so (CLDR tables).

11.2 If we DROP the 9 ICU-using .c files (the plan)

StageICU needed?Why
Build timeNoNone of the remaining 69 .c files #include <unicode/*.h> or reference ICU symbols. The build doesn't need ICU headers, and ld has no unresolved ICU references to chase.
Link timeNolibCoreFoundation.so.6 has no DT_NEEDED entry for any libicu*.so. Its dynamic section lists only libdispatch.so, libBlocksRuntime.so, libpthread.so, libc.so.
RuntimeNoBinaries linking libCoreFoundation have nothing to resolve. ldd /sbin/launchd, ldd /bin/launchctl show no ICU library in their resolution chain. ICU need not be installed on the ISO at all.
ISO disk costZero ICU.

11.3 The footnote for the daemons we'll port later

configd / bootp (IPConfiguration) / PowerManagement / IOKitUser source code calls CF-ICU APIs (CFLocaleCreate, CFCalendar*, CFDateFormatter*, CFNumberFormatter*). Those calls resolve to symbols in libCoreFoundation. Since our libCoreFoundation doesn't export those symbols (we dropped the files), the daemon source has to be patched at port time to use libc replacements (strftime, strptime, localtime_r, direct CFStringCreateWithFormat). That's port-time source work, not an ICU dependency. Once patched, those daemons compile and run with zero ICU involvement at any stage.

Quantum: ~100 LOC of patches across four repos, one-shot per repo as we port them. Each patch is mechanical (replace a 5-line CFDateFormatter setup with a 1-line strftime call) and obvious from the audit citations in §6 and §8.

12. Caveats & what we lose

Honest accounting of the functionality the ISO won't have without ICU. These are real but small for our consumer set:

None of this affects the core functionality of any system service on the ISO. The losses are entirely in the "make output prettier for the user's locale" category, which is the desktop concern, not the system-services concern.

Reversibility: every loss above is one Makefile line away from being restored. Install icu from FreeBSD pkg, add the dropped .c files back to SRCS, rebuild libCoreFoundation, re-link. No SONAME bump needed because the dropped files only add new symbols — binaries linking the no-ICU libCoreFoundation will just see NULL/missing-symbol behavior for those functions; binaries linking the with-ICU build see the full surface.

Future-proofing: if and when gershwin (the desktop overlay) lands on top of freebsd-launchd-mach, ICU comes along with libgnustep-base as its own hard build-time dependency (libs-base's configure.ac aborts without it). At that point the marginal cost of installing ICU is zero, and libCoreFoundation can be rebuilt with the full surface. The decision here is specifically about the standalone freebsd-launchd-mach ISO.

13. Could we vendor Apple's ICU instead? we did

Yes — this is what actually happened. See §0 callout above. We pivoted from apple-oss-distributions/ICU (the repo discussed in this section) to apple/swift-foundation-icu (a smaller, swift-corelibs-CF-targeted fork) because the latter ships headers under the _foundation_unicode/ namespace CF expects natively. The original apple-oss-distributions/ICU vendor was stripped from history via git filter-repo after the pivot. Full empirical port plan: freebsd-libicu-port-plan.html. Section content below is preserved as the original analysis of the (correct in spirit) Option C path.

Project pattern would suggest yes — we already vendor libdispatch, libxpc, launchd-842, libCoreFoundation, bootstrap_cmds from Apple-OSS or swiftlang in src/. ICU has an Apple fork too. So if we ever wanted ICU on this ISO, vendoring Apple's would be on-pattern. Worth knowing the cost.

13.1 Apple's ICU exists at apple-oss-distributions/ICU

Probed locally 2026-05-15. Shallow-clone summary:

AttributeValue
Upstreamgithub.com/apple-oss-distributions/ICU
Repo size (shallow clone)221 MiB
LOC (C + C++ + headers)1,012,463 across icu/icu4c/source/
LicenseUnicode License V3 — permissive, BSD-like, no copyleft. Free to use, copy, modify, distribute, sell. Only obligation is preserving the copyright notice. Compatible with everything else in this project (Apache 2.0, BSD-2-Clause, LGPL).
Top-level layouticu/icu4c/source/{common, i18n, data, extra, io, layoutex, test, tools, ...} + an apple/ subdir with Apple-specific build glue, scripts, and a Swift module.
Build depsC++ compiler (we have clang/clang++ via FreeBSD base), gmake, Python (for some data-generation steps), autoconf-style configure. No external library deps — ICU is self-contained.
Apple's top-level makefileApple-platform-only. The opening comment says it "eliminates all of the stuff for Windows and Linux" and is Xcode-tied. To build on FreeBSD we'd skip Apple's makefile and use the upstream IBM ICU autotools build inside icu/icu4c/source/.
Build artifactApple ships libicucore.dylib on macOS — a single unified library combining what upstream ships as three (libicuuc.so + libicui18n.so + libicudata.so). Apple's data slice is trimmed.
Compiled size (estimated)~5-8 MiB for Apple's slimmed-down libicucore; ~30 MiB for upstream's three-library split with full CLDR data.

13.2 The three options compared

OptionSource surfaceBuild complexityISO sizeFreeBSD-pkg dependencySelf-contained
A. Drop ICU entirely (the plan)0 LOC vendoredNone — no ICU in the build0 MiBNoneYes (no ICU on the ISO to be self-contained about)
B. Install FreeBSD's devel/icu port0 LOC vendoredNone — pkg-installed~30 MiBHard-depends on devel/icu port (FreeBSD-maintained)No — ISO carries a transitive dep on the FreeBSD ports tree
C. Vendor Apple's ICU into src/libICU/1M+ LOC, 221 MiBHigh — rework Apple's Xcode-tied top-level makefile for FreeBSD bsd.lib.mk; bootstrap the data-generation steps; ~weeks of build-system surgery~5-30 MiB depending on data sliceNoneYes — matches the rest of the vendor-everything pattern

13.3 What you'd give up vs. gain at each option

13.4 Recommendation

Option A (drop) is right for this ISO. The audit established zero non-droppable consumers. Option B (FreeBSD pkg) is the right fallback if a future requirement adds an ICU-needing consumer — one line in pkglist-base.txt + one Makefile flip in libCoreFoundation, done in 10 minutes. Option C (vendor Apple's ICU) is a real engineering project (FreeBSD-porting Apple's Xcode-tied build) for which the audit found no driving requirement — defer until we have a concrete reason to need self-contained ICU on the ISO.

The recommendation is "A unless something changes." If a future need surfaces (e.g., we decide to ship a Cocoa app on the bare freebsd-launchd-mach ISO without the full gershwin overlay), revisit. Option C remains the cleanest forward-compat answer if self-containment ever becomes a hard requirement (no FreeBSD-port chain).

13.5 The porting-tax math — what does Option A cost across the project lifetime?

Option A (drop ICU) does add a small per-port tax: every daemon that calls CF-ICU APIs needs ~5-40 LOC of mechanical patches at port time to replace those calls with libc equivalents. Worth pricing out explicitly.

RepoPatch sitesLOC of mechanical patchesPer-port timeWorst-case impact if patch is wrong
configd3 (in 2 files)~2030-60 minDebug log timestamps appear in en_US instead of localized. ENABLE_SC_FORMATTING is already a build-time toggle — setting it OFF compiles two of the three sites out entirely (zero patches needed for those two).
bootp (IPConfiguration)1 feature (2 files)~10 OR skip feature30 minPvD info expiration parsing for RFC 8801 carrier hints rarely deployed in the wild.
PowerManagement3 binaries, 6 files~4060-90 minpmset shows ISO-style timestamps instead of locale-formatted in CLI output.
IOKitUser1 site, 1 file~515 minOne dict-key formatter; trivial CFStringCreateWithFormat(@"%lld") swap.
Total across known consumers~10 sites in 11 files~75-100 LOC~3-4 hoursAll in cosmetic / debug paths. None affect core daemon functionality.

What we save in return:

Per-port economics: a 30-90 minute mechanical-patch session per daemon, paid once at port time. With 4 known consumers totaling ~3-4 hours of work across the project lifetime, this is a small price for the savings. If a future port surfaces 200+ LOC of CF-ICU we'd treat that as a signal to flip and use Option B (FreeBSD pkg), not a reason to keep cycling through patches.

13.6 The trapdoor — flipping back is a four-change commit

If patch volume gets painful at some point, or a future port surfaces a CF-ICU dependency that's not mechanically replaceable, switching from Option A to Option B is:

  1. Add icu to pkglist-base.txt (1 line).
  2. Restore the 9 dropped .c files to libCoreFoundation's SRCS in src/libCoreFoundation/Makefile (1 Makefile section).
  3. git revert the daemon CF-ICU patches (one revert commit per affected port that we already patched).
  4. Rebuild — the libCoreFoundation SONAME stays the same; nothing else needs touching.

So Option A is not a one-way door — it's the "carry small patches while it's cheap to do so, flip if it stops being cheap" trade. The exit ramp stays open.

13.7 What also gets simpler if we drop ICU: buildpkgs.txt

The chroot's build-only package list (buildpkgs.txt) currently carries three packages that were there to support GNUstep's autotools build path: gmake, autoconf, libtool. With GNUstep out of scope on this ISO and ICU dropped, none of these have a remaining consumer:

PkgOriginally forUsed by anything we still build?Action
cmakelibdispatch's CMakeLists.txt buildYes — libdispatch's chroot build still uses cmake -G Ninjakeep
ninjalibdispatch's build generatorYes — samekeep
gmakeGNUstep autotools, ICU upstream, any autoconf-driven vendored sourceNo — every active build now uses either bsd.lib.mk / bsd.prog.mk (bmake, in base) or cmake -G Ninjadrop
autoconfRegenerate configure scripts in autotools-based sources (libs-base, libs-corebase, ICU upstream)No — swift-corelibs-foundation uses CMake; libdispatch uses CMake; nothing autoconf-based remainsdrop
libtoolGNU libtool for autotools-based shared-library buildsNo — same reason as autoconfdrop
pkgconflibs-base's ICU discovery via icu-i18n.pc / icu-uc.pcYes (different reason) — CMake's find_package module-mode probes use pkg-config under the hood; safer to keep, minimal cost. Comment can be updated to drop the libs-base reference.keep, update comment

So the drop-ICU decision drags along a clean-up of the build-pkg list as a free byproduct — three package installs the chroot doesn't need anymore.

14. Audit provenance

This consolidated report aggregates findings from four parallel audit passes performed 2026-05-15 against the local clones at /Users/jmaloney/Documents/launchd/. Method was identical across all four: ripgrep / awk / find over each repo, manual adjudication of grep hits, build-file scan for link-line evidence, false-positive filtering (notably the jq/decNumber and jq/oniguruma cases in text_cmds).

The four source audit documents are preserved verbatim in the working tree:

Each contains per-file line-number citations supporting the verdicts summarized above. Where a verdict in this consolidated report says "stub-replaceable" or "strftime-replaceable," the corresponding source audit document names the exact files and line ranges.

Written 2026-05-15 alongside the launchctl-corefoundation-spike's implementation phase. This audit reverses the spike's §14.4 tentative recommendation ("install ICU as a build dep") in favor of the no-ICU minimal build — the spike's text will be updated to match once this audit is reviewed and committed.