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.
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:
libicudata.so.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.
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 / API | What it does | What it can't do that ICU can |
|---|---|---|
<locale.h> — setlocale, newlocale, uselocale | Set / 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_langinfo | Read 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, strxfrm | Locale-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, wctomb | Wide-character / multibyte conversion + comparison | Same 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 locale | Gregorian 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 tools | Build locale-data binaries that libc consumes | Build 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:
/usr/share/locale/ (usually a few dozen, lower fidelity).nl_langinfo primitives.strftime is proleptic-Gregorian only. ICU supports Buddhist, Hebrew, Islamic, Japanese, Persian, ROC, and others.<regex.h> is byte-oriented (works fine on UTF-8 if patterns avoid character-class ambiguity); ICU regex understands Unicode properties.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.
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 family | Needs ICU? | Fallback path if ICU absent |
|---|---|---|
CFLocale* | Yes | None — locale data lives in CLDR. |
CFCalendar* | Yes | None — ICU drives all calendar arithmetic. |
CFDateFormatter* | Yes | None — replace with libc strftime/strptime. |
CFNumberFormatter* | Yes | None — replace with printf("%lld"). |
CFListFormatter* | Yes | None. |
CFDateIntervalFormatter* | Yes | None. |
CFRelativeDateTimeFormatter* | Yes | None. |
CFStringTransform* | Yes | None. |
CFRegularExpression* | Yes | Use libc <regex.h>. |
CFCharacterSetGetPredefined | Partial | Hardcoded ASCII for whitespace, alphanumeric. Loses Unicode-aware sets. |
CFApplicationPreferences* | Yes | None (it's a desktop-app concept anyway). |
CFStringCompareWithOptions with kCFCompareLocalized | Yes | Drop the flag, fall back to binary compare. |
CFStringCompareWithOptions any other flags | No | ASCII / binary fast paths in CFString.c. |
CFString Create/GetLength/GetCString/HasPrefix/HasSuffix/Find/Append/Replace | No | Built-in. |
CFString case-fold (ASCII) | No | Built-in. |
CFString encoding conversion: UTF-8 / UTF-16 / ASCII / ISO Latin1 | No | Hardcoded fast paths in CFString.c. |
CFString encoding conversion: anything else (GB18030, Shift_JIS, etc.) | Yes | Lose those encodings. |
CFArray / CFDictionary / CFSet / CFBag | No | Built-in. |
CFData / CFNumber / CFBoolean | No | Built-in. |
CFPropertyList (XML + binary read/write) | No | UTF-8 only; doesn't need ICU. |
CFURL (ASCII / UTF-8 paths) | No | Built-in. |
CFDate (storage of absolute time) | No | Built-in — formatting is the ICU-needing part. |
CFRunLoop / CFSocket / CFStream / CFMachPort | No | Event/IO plumbing. |
CFBundle (load executable, find resources) | No | Built-in. |
CFUUID | No | Built-in. |
CFError | No | Built-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.
Every Apple-source repo we've already vendored, plus every repo on the launchd-adjacent porting roadmap. 22 components total:
| Tier | Components | Count |
|---|---|---|
| Currently vendored / shipped | libCoreFoundation (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 port | configd, bootp (IPConfiguration), mDNSResponder, syslog (asl family), Libnotify (notifyd), DiskArbitration | 6 |
| Apple userland-command repos | file_cmds, network_cmds, shell_cmds, system_cmds, text_cmds | 5 |
| Apple supporting frameworks & tools | PowerManagement (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.
Four parallel agents, one consistent toolkit, repo-by-repo:
#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).CFLocale*, CFCalendar*, CFDateFormatter*, etc.kCFCompareLocalized as a flag to CFStringCompareWithOptions.kCFStringEncodingGB*, kCFStringEncodingShiftJIS, kCFStringEncodingEUC_*, kCFStringEncodingBig5, kCFStringEncodingISO_2022*, etc. ASCII/UTF-8/UTF-16/Latin1 are free; anything else needs ICU's encoding tables.-licu*, libicu*, libicucore, libicudata. Direct link is the unambiguous signal of runtime dependency.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 filesWhat 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 ICUWhat 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 ICUWhat 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 ICUWhat 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 ICUWhat 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 ICUWhat 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 ICUWhat 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.
The six Apple-source daemons on the launchd-adjacent porting roadmap.
configd 3 narrow CF-ICU touchpoints, all replaceableWhat 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:
SCD.c:217-230 — CFCalendar + CFGregorianDate in _SCCopyDescription, behind #ifdef ENABLE_SC_FORMATTING. Pretty-prints absolute times in debug log output. Replace with localtime_r + strftime or stub the formatter to a fixed ISO-8601 string.SCNetworkInterface.c:9513-9532 — date_create_5am_tomorrow for cellular "expensive expiration" timestamp. CFCalendar arithmetic, Gregorian-only. Replace with libc mktime + localtime_r.SCNetworkInterface.c:5688-5709 — CFLocaleCopyCurrent + CFNumberFormatter to localized-format a port number for a debug string. Already has a null-locale fallback; trivial to remove the locale path entirely.Resolution: ~50 LOC of stub work in our configd port, all in cosmetic / debug paths. No core SCDynamicStore functionality depends on ICU.
bootp — IPConfiguration 1 optional feature uses CF-ICUWhat 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:
PvDInfoContext.c:167-242 + IPHPvDInfoRequestServer.m:366-407 — CFLocaleCreate(NULL, CFSTR("en_US_POSIX")) + CFDateFormatter with an ISO-8601 template to parse the expires timestamp on IPv6 PvD (Provisioning Domain) info JSON, per RFC 8801. PvD is a niche feature (carrier-controlled DNS-over-HTTPS hints + custom DNS); rarely deployed in the wild.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 defaultWhat 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:
mDNSCore/mDNS.c:698-712 — #include <unicode/uidna.h> and three uidna_* calls (UTS #46 / IDNA2008 hostname normalization). The entire block is wrapped in #ifdef USE_LIBIDN. USE_LIBIDN is not set in any Makefile, xcconfig, or pbxproj in the repo. The feature is dormant.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 ICUWhat 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 ICUWhat 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 ICUWhat 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.
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 ICUWhat 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 ICUWhat 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 ICUWhat 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 ICUWhat 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 ICUWhat 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:
text_cmds/jq/src/decNumber/ is IBM's decNumber library, distributed under the ICU License (a permissive license). It is NOT the ICU codebase — it's a separate decimal-arithmetic library that happens to use the same license file.text_cmds/jq/modules/oniguruma/oniguruma.h:80,84 defines #define UChar OnigUChar + typedef unsigned char OnigUChar. Hundreds of UChar references under jq are Oniguruma's unsigned-char alias, not ICU's UTF-16 code unit.sort, grep, etc. use libc strcoll/wcscoll/setlocale for locale-aware processing — not ICU.
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 strftimeWhat 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:
pmset.m — CFDateFormatter for human-readable wake/sleep timestamps in the CLI output.pmtool.m — same, for the developer-facing inspection tool.pmconfigd/{RepeatingAutoWake.c, AutoWakeScheduler.c, PMConnection.m, PrivateLib.m} — CFCalendar arithmetic (Gregorian-only) for repeating-wake scheduling, plus CFDateFormatter for log strings.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 ICUWhat 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 ICUWhat 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 replaceableWhat 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:
pwr_mgt.subproj/IOPMAssertions.c:547-560 — convertCFNumberToCFStringRef uses CFLocaleCopyCurrent + CFNumberFormatter just to format an integer as a dictionary key. Trivially replaceable with CFStringCreateWithFormat(NULL, NULL, CFSTR("%lld"), val).Resolution: ~5-line replacement. The function name suggests it predates Apple noticing the locale call wasn't actually needed.
| Component | Tier | Direct ICU | CF-ICU APIs | Locale compare | Exotic encodings | Link line | Verdict |
|---|---|---|---|---|---|---|---|
libCoreFoundation | vendored | in 13 files | n/a (it IS CF) | n/a | n/a | n/a | drop 13 .c files |
libdispatch | vendored | No | No | No | No | No | ICU-free |
libxpc | vendored | No | No | No | No | No | ICU-free |
launchd-842 (incl. launchctl) | vendored | No | No | No | No | No | ICU-free |
libmach | vendored | No | No | No | No | No | ICU-free |
bootstrap | vendored | No | No | No | No | No | ICU-free |
bootstrap_cmds | vendored | No | No | No | No | No | ICU-free |
configd | daemon | No | 3 sites | No | No | No | stub-replaceable |
bootp (IPConfiguration) | daemon | No | 1 feature | No | No | No | strptime-replaceable |
mDNSResponder | daemon | gated off | No | No | No | No | gate stays off |
syslog (asl) | daemon | No | No | No | No | No | ICU-free |
Libnotify (notifyd) | daemon | No | No | No | No | No | ICU-free |
DiskArbitration | daemon | No | No | No | No | No | ICU-free |
file_cmds | cmds | No | No | No | No | No | ICU-free |
network_cmds | cmds | No | No | No | No | No | ICU-free |
shell_cmds | cmds | No | No | No | No | No | ICU-free |
system_cmds | cmds | No | No | No | No | No | ICU-free |
text_cmds | cmds | No | No | No | No | No | ICU-free |
PowerManagement | extras | No | 3 binaries | No | No | No | strftime-replaceable |
adv_cmds | extras | No | No | No | No | No | ICU-free |
CommonCrypto | extras | No | No | No | No | No | ICU-free |
IOKitUser | extras | No | 1 site | No | No | No | 5-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.
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.
.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.
"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:
.c files in SRCS (the unstripped state)| Stage | ICU needed? | Why |
|---|---|---|
| Build time | Yes | cc -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 time | Yes | libCoreFoundation.so.6 ends up with DT_NEEDED libicuuc.so, DT_NEEDED libicui18n.so, DT_NEEDED libicudata.so in its dynamic section. |
| Runtime | Yes | Every 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). | |
.c files (the plan)| Stage | ICU needed? | Why |
|---|---|---|
| Build time | No | None 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 time | No | libCoreFoundation.so.6 has no DT_NEEDED entry for any libicu*.so. Its dynamic section lists only libdispatch.so, libBlocksRuntime.so, libpthread.so, libc.so. |
| Runtime | No | Binaries 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 cost | Zero ICU. | |
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.
Honest accounting of the functionality the ISO won't have without ICU. These are real but small for our consumer set:
strftime + ASCII templates when porting; output is en_US-shaped regardless of user locale. Cost: zero on system-daemon CLI output; cosmetic-only on user-facing tools like pmset.<regex.h> directly.)kCFCharacterSetWhitespace, kCFCharacterSetLetter, etc. become unavailable. Not used by any audited component — everything uses libc isspace/isalnum instead.kCFCompareLocalized won't behave correctly — comparisons fall back to binary order. Not requested by any audited component.USE_LIBIDN is already off upstream; no real-world Bonjour traffic uses it.strptime replacement). RFC 8801 is rarely deployed; carrier-provided DNS hint timestamps are advisory.strftime in en_US shape.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.
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.
apple-oss-distributions/ICUProbed locally 2026-05-15. Shallow-clone summary:
| Attribute | Value |
|---|---|
| Upstream | github.com/apple-oss-distributions/ICU |
| Repo size (shallow clone) | 221 MiB |
| LOC (C + C++ + headers) | 1,012,463 across icu/icu4c/source/ |
| License | Unicode 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 layout | icu/icu4c/source/{common, i18n, data, extra, io, layoutex, test, tools, ...} + an apple/ subdir with Apple-specific build glue, scripts, and a Swift module. |
| Build deps | C++ 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 makefile | Apple-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 artifact | Apple 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. |
| Option | Source surface | Build complexity | ISO size | FreeBSD-pkg dependency | Self-contained |
|---|---|---|---|---|---|
| A. Drop ICU entirely (the plan) | 0 LOC vendored | None — no ICU in the build | 0 MiB | None | Yes (no ICU on the ISO to be self-contained about) |
B. Install FreeBSD's devel/icu port | 0 LOC vendored | None — pkg-installed | ~30 MiB | Hard-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 MiB | High — 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 slice | None | Yes — matches the rest of the vendor-everything pattern |
libgnustep-base's build dep and brings the rich i18n surface back.devel/icu. Zero project-side build complexity.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).
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.
| Repo | Patch sites | LOC of mechanical patches | Per-port time | Worst-case impact if patch is wrong |
|---|---|---|---|---|
configd | 3 (in 2 files) | ~20 | 30-60 min | Debug 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 feature | 30 min | PvD info expiration parsing for RFC 8801 carrier hints rarely deployed in the wild. |
PowerManagement | 3 binaries, 6 files | ~40 | 60-90 min | pmset shows ISO-style timestamps instead of locale-formatted in CLI output. |
IOKitUser | 1 site, 1 file | ~5 | 15 min | One dict-key formatter; trivial CFStringCreateWithFormat(@"%lld") swap. |
| Total across known consumers | ~10 sites in 11 files | ~75-100 LOC | ~3-4 hours | All in cosmetic / debug paths. None affect core daemon functionality. |
What we save in return:
libicuuc.so / libicui18n.so / libicudata.so in /usr/lib. Dominated by the CLDR data tables in libicudata.devel/icu port. Matches the rest of the project's vendor-everything posture.ldd on system daemons — no surprise ICU pulled in via libCoreFoundation.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.
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:
icu to pkglist-base.txt (1 line)..c files to libCoreFoundation's SRCS in src/libCoreFoundation/Makefile (1 Makefile section).git revert the daemon CF-ICU patches (one revert commit per affected port that we already patched).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.
buildpkgs.txtThe 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:
| Pkg | Originally for | Used by anything we still build? | Action |
|---|---|---|---|
cmake | libdispatch's CMakeLists.txt build | Yes — libdispatch's chroot build still uses cmake -G Ninja | keep |
ninja | libdispatch's build generator | Yes — same | keep |
gmake | GNUstep autotools, ICU upstream, any autoconf-driven vendored source | No — every active build now uses either bsd.lib.mk / bsd.prog.mk (bmake, in base) or cmake -G Ninja | drop |
autoconf | Regenerate configure scripts in autotools-based sources (libs-base, libs-corebase, ICU upstream) | No — swift-corelibs-foundation uses CMake; libdispatch uses CMake; nothing autoconf-based remains | drop |
libtool | GNU libtool for autotools-based shared-library builds | No — same reason as autoconf | drop |
pkgconf | libs-base's ICU discovery via icu-i18n.pc / icu-uc.pc | Yes (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.
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:
/Users/jmaloney/Documents/launchd/icu_audit_current.md — vendored sources (7 components)./Users/jmaloney/Documents/launchd/icu_audit_apple_ports.md — daemon repos (6 components)./Users/jmaloney/Documents/launchd/icu_audit_user_cmds.md — command repos (5 components)./Users/jmaloney/Documents/launchd/icu_audit_extras.md — supporting frameworks & tools (4 components).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.