Turn ICU Data into Linkable C Code with genccode
genccode wraps a binary ICU data file up as a C source file, byte array and all, ready to compile straight into a program or library. The examples use genccode 74.2 from Ubuntu's icu-devtools package, version 74.2-1ubuntu3.1.
The route
Jump straight to the step you need, or tick off Done means at the end.
Give it about fifteen minutes. You need a readable binary data file, a writable working directory, and a C toolchain if you plan to compile the result. Nothing here installs ICU data, replaces system files or touches a build in place: everything generated goes into an explicit temporary or build directory.
1. Confirm the installed tool
Check the executable, package version and local option syntax first. These are ordinary read-only commands and need no elevated privileges:
$ command -v genccode
/usr/sbin/genccode
$ dpkg-query -W icu-devtools
icu-devtools 74.2-1ubuntu3.1
$ genccode --help
usage: genccode [-options] filename1 filename2 ...
The installed help is the best reference for this exact build: it lists options that do not show up in every copy of the older manpage, including --object, --revision, --match-arch and --skip-dll-export. Read the full help before reaching for one of those in a portable build script.
Checkpoint
Confirm the command resolves to the ICU installation you expect, and that the package is the version you actually want.
2. Pick an input and a clean destination
genccode reads one or more binary input files. Give it no filename and it exits without doing anything useful, so do not treat an empty argument list as a validation step. A file such as mydata.icu normally becomes mydata_icu.c. The output directory defaults to the current one, but naming an explicit destination stops generated files turning up among your source files by accident.
For a harmless local test, use an input you already have and a fresh directory. This example calls the input INPUT_DATA_FILE; swap in the path to your own file:
$ INPUT_DATA_FILE=/path/to/mydata.icu
$ OUT_DIR="$PWD/genccode-output"
$ mkdir -p "$OUT_DIR"
$ test -r "$INPUT_DATA_FILE" && echo readable
readable
Making a directory in your own working tree is a normal user operation. Do not reach for sudo just because the executable lives under /usr/sbin. You only need elevated privileges if your chosen input or destination is genuinely off limits to your account, and copying the input into a workspace usually beats changing its permissions.
3. Generate the default C file
Pass the destination with --destdir and the input as a separate argument (the short form is -d):
$ genccode --destdir "$OUT_DIR" "$INPUT_DATA_FILE"
generating C code for /path/to/mydata.icu
$ find "$OUT_DIR" -maxdepth 1 -type f -printf '%f %s bytes\n'
mydata_icu.c 123456 bytes
The progress line and the size both depend on your input. What you get is C source, not an object file, so look at its first lines before adding it to a build:
$ sed -n '1,12p' "$OUT_DIR/mydata_icu.c"
/* ... generated source ... */
$ grep -n 'const' "$OUT_DIR/mydata_icu.c" | head
Do not hand-edit generated output: change the command or the input and regenerate instead. A failed run can leave no usable output, or an incomplete file, so check the exit status before compiling:
$ genccode --destdir "$OUT_DIR" "$INPUT_DATA_FILE" && echo generation-ok
generating C code for /path/to/mydata.icu
generation-ok
4. Set stable names for a build
Build systems often need names that do not depend on a vendor's filename. Use --name for the symbol prefix and --filename for the output base name. Reach for --entrypoint when the data has to be exposed through a particular entry point; this installed tool appends _dat to whatever entry point name you give it.
$ genccode \
--destdir "$OUT_DIR" \
--name application_data \
--entrypoint application_data \
--filename application_data \
"$INPUT_DATA_FILE"
generating C code for /path/to/mydata.icu
$ find "$OUT_DIR" -maxdepth 1 -type f -name 'application_data.c' -printf '%f %s bytes\n'
application_data.c 123456 bytes
$ grep -n 'application_data' "$OUT_DIR/application_data.c" | head
Keep these names stable once another source file or linker rule refers to them. An output filename and a C symbol are separate settings, so changing only one can leave a build with a name nobody expects. Check the generated declarations, and only touch the consuming code when the change is deliberate.
5. Generate assembly only when the target actually needs it
--assembly picks platform-specific assembly instead of C. It needs a type such as gcc, nasm or another value from genccode --help; with ICU 74.2 and gcc, the output gets an .S suffix. Match the type to the assembler and target ABI the rest of your build actually uses:
$ genccode --assembly gcc --destdir "$OUT_DIR" \
--filename application_data "$INPUT_DATA_FILE"
generating assembly code for /path/to/mydata.icu
$ find "$OUT_DIR" -maxdepth 1 -type f -name 'application_data.S' -printf '%f %s bytes\n'
application_data.S 123456 bytes
Do not carry this choice between architectures without checking both the help output and the compiler toolchain. C is the easier default when you have no specific assembly requirement, and assembly output is source for a later assembler step, not something already linkable.
6. Compile and verify without overwriting anything
Once the generated C source has the symbol and size you expect, compile it into a separate object directory. This leaves the generated source untouched:
$ mkdir -p "$OUT_DIR/objects"
$ cc -c "$OUT_DIR/application_data.c" -o "$OUT_DIR/objects/application_data.o"
$ file "$OUT_DIR/objects/application_data.o"
... relocatable ...
The exact file output varies by architecture. If compilation complains about an unknown symbol or malformed source, go back to the input type and generated declarations rather than patching the output by hand. If your build already links ICU data through a shared library, adding a second copy this way can create duplicate symbols or unnecessary data: treat the generated object as an alternative, not an automatic replacement.
Recovery
Genccode has no persistent undo; it reads input and writes generated files, full stop. To back out of a mistaken output, stop using it, and remove only the explicit generated directory once you have confirmed it holds nothing hand-written. Renaming it first is the safer move:
$ mv "$OUT_DIR" "$OUT_DIR.old"
$ mkdir -p "$OUT_DIR"
# Regenerate after correcting the command or input.
Do not run a broad recursive delete in a source tree to clean this up. Keep the original binary data file until the compiled program has actually been tested.
Done means
- Tool confirmed. You checked the installed ICU 74.2 command and read its local help.
- Input and destination checked. The input file was readable and the destination was explicit and writable.
- Output verified. A C file was generated and its name and declarations were checked.
- Names recorded. Custom symbol, entry point and filename settings are written down in the build.
- Assembly chosen deliberately. It was only used with a target-appropriate type, and only when actually needed.
- Build isolated. The generated source compiled into a separate object without touching the original input.