[MacPorts] #74169: expat: corrupted expat_config.h in +universal binary archive (ar archive instead of C header)

MacPorts noreply at macports.org
Sat Aug 29 22:49:17 UTC 2026


#74169: expat: corrupted expat_config.h in +universal binary archive (ar archive
instead of C header)
---------------------+-----------------------
  Reporter:  cippee  |      Owner:  (none)
      Type:  defect  |     Status:  new
  Priority:  Normal  |  Milestone:
 Component:  ports   |    Version:
Resolution:          |   Keywords:  universal
      Port:  expat   |
---------------------+-----------------------

Comment (by jrjsmrtn):

 I hit this with `libffi` rather than `expat`, and I think the mechanism is
 in the
 muniversal merge: `/usr/bin/libtool` succeeds on non-Mach-O input, so the
 text-merge
 branch is never reached.

 == Reproduction, independent of any port ==

 {{{
 $ printf '#define FOO 1\nint a;\n' > a.h
 $ printf '#define FOO 2\nint b;\n' > b.h

 $ /usr/bin/lipo -create a.h b.h -output out.h
 fatal error: lipo: can't figure out the architecture type of: a.h

 $ /usr/bin/libtool a.h b.h -o out.h
 libtool: warning: not a mach-o 'a.h'
 libtool: warning: not a mach-o 'b.h'
 $ echo $?
 0

 $ file out.h
 out.h: current ar archive random library
 $ head -1 out.h
 !<arch>
 }}}

 96 bytes, containing only `__.SYMDEF SORTED` — byte-identical in form to
 the corrupted
 headers.

 == Where this happens ==

 `_resources/port1.0/group/muniversal-1.0.tcl`, lines 688-694 (and the same
 code in
 muniversal-1.1.tcl, lines 446-449):

 {{{
 # First try lipo, then libtool
 if { ! [catch {system "/usr/bin/lipo -create ..."}] } {
     # lipo worked
 } elseif { ! [catch {system "/usr/bin/libtool ... -o ..."}] } {
     # libtool worked
 } else {
     # lipo and libtool have failed, so assume they are text files to be
 merged
 }}}

 For two differing text files, `lipo` fails as the code expects, but macOS
 `libtool`
 defaults to `-static` and archives arbitrary files, warning and exiting 0.
 `catch` sees
 success, the `elseif` is taken, and the `diff`-based text merge in the
 `else` branch never
 runs.

 That predicts binaries merge correctly (lipo succeeds first) while
 differing text files are
 destroyed, which is what the destroots show.

 == Observed with libffi ==

 macOS 26.6.2 (25G83), arm64, MacPorts 2.12.5, libffi @3.4.8_0+universal:

 {{{
 $ file /opt/local/include/ffi.h /opt/local/include/ffitarget.h
 /opt/local/include/ffi.h:       current ar archive random library
 /opt/local/include/ffitarget.h: current ar archive random library
 }}}

 After `sudo port -s destroot libffi +universal`:

 {{{
 destroot-arm64/opt/local/include/ffi.h    C source, ASCII text    14041
 bytes
 destroot-x86_64/opt/local/include/ffi.h   C source, ASCII text    14039
 bytes
 destroot/opt/local/include/ffi.h          current ar archive          96
 bytes
 }}}

 Five installed files differ between the two architecture destroots:

 {{{
 include/ffi.h          -> ar archive                  (corrupt)
 include/ffitarget.h    -> ar archive                  (corrupt)
 lib/libffi.8.dylib     -> lipo -info: x86_64 arm64    (correct)
 lib/libffi.a           -> lipo -info: x86_64 arm64    (correct)
 lib/libffi.dylib       -> symlink
 }}}

 Everything else is identical across architectures and survives. Rebuilding
 from source
 does not help — `sudo port -s upgrade --force libffi` rewrites the file
 and it is still an
 ar archive — so the precompiled archives are presumably corrupt because
 they were produced
 this way, rather than being damaged in transit.

 == Downstream ==

 `python313 +universal` fails with roughly 90 errors in `Modules/_ctypes` —
 `unknown type
 name 'ffi_type'`, `'ffi_cif'`, `'ffi_closure'`, `'ctypes_state'` — across
 `_ctypes.o`,
 `callbacks.o`, `callproc.o`, `cfield.o`, `malloc_closure.o` and
 `stgdict.o`. The compile
 line has `-I/opt/local/include` and both `-arch` flags, so the header is
 found; it just is
 not a header. Reproduces from a cleaned work directory.

 The same `python313 +universal` builds and installs fine on an x86_64 host
 running macOS
 15.7.9, which has no `+universal` libffi and a valid ASCII `ffi.h`.

 `expat_config.h` fits the same pattern: a generated, architecture-
 dependent header that
 differs between the two builds.

-- 
Ticket URL: <https://trac.macports.org/ticket/74169#comment:3>
MacPorts <https://www.macports.org/>
Ports system for macOS


More information about the macports-tickets mailing list