Is is possible to install and update Perl (CPAN) modules with universal (x86_64, arm64) architecture support? If yes, then how?
background
On an arm-based macOS computer, a Perl CPAN module can be installed for exactly one designated architecture as follows:
sudo cpan -i Encode
### equivalent since `-arm64` is the native processor in this situation:
sudo arch -arm64 cpan -i Encode
file /Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle
# /Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle:
# Mach-O 64-bit bundle arm64
sudo arch -x86_64 cpan -i Encode
file /Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle
# /Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle:
# Mach-O 64-bit bundle x86_64
Notice, however, Apple's perl itself is a "universal binary":
file /usr/bin/perl
# /usr/bin/perl: Mach-O universal binary with 2 architectures:
# [x86_64:Mach-O 64-bit executable x86_64]
# [arm64e:Mach-O 64-bit executable arm64e]
# /usr/bin/perl (for architecture x86_64):
# Mach-O 64-bit executable x86_64
# /usr/bin/perl (for architecture arm64e):
# Mach-O 64-bit executable arm64e
The XOR of either one architecture or the other presents a conflict when native and non-native applications share the same Perl dependency. For example, GnuCash Finance::Quote does not run on natively on Arm while MacTeX LaTeX Live Update can run natively on either Intel or Arm processors. Both applications use the Pearl Encode module.
The application log error message will be one of the following if the required architecture version is not found:
'/Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64'))
'/Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64'))
Note: A workaround for running the applications is to install the common Perl module dependancy for the x86_64 architecture, and then run the universal capable application in Rosetta2 (x86_64) mode.
Additional Findings
cc option '-bundle'
cc -bundle was found in the saved install log.
rm -f blib/arch/auto/Encode/Encode.bundle
cc -bundle -undefined dynamic_lookup Encode.o def_t.o encengine.o -o blib/arch/auto/Encode/Encode.bundle
chmod 755 blib/arch/auto/Encode/Encode.bundle
…
Manifying 18 pod documents
Files found in blib/arch: installing files in blib/lib into architecture dependent library tree
Installing /Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle
However, man cc and cc --help do not provide any developer information about a -bundle option for the cc clang LLVM compiler. So, it's not clear what -bundle is actually doing, or how a Perl novice might use this piece of information.
lipo
It would appear that the "safest way to build Universal binaries is to compile the modules separately and then use lipo to merge the resulting .bundle files." See: meta::cpan Config_u.pm
The Apple article "Building a Universal macOS Binary" provides a such multi-step example:
The following example shows a makefile that compiles a single-source file twice—once for each architecture. It then creates a universal binary by merging the resulting executable files together with the
lipotool.
x86_app: main.c
$(CC) main.c -o x86_app -target x86_64-apple-macos10.12
arm_app: main.c
$(CC) main.c -o arm_app -target arm64-apple-macos11
universal_app: x86_app arm_app
lipo -create -output universal_app x86_app arm_app
lipo requires the individual architecture files as inputs to -create the universal file.
file Encode.bundle
A search and review for all Encode.bundle files found a mixture of universal and non-universal binaries.
find / -name "Encode.bundle"
file /Applications/FreeCAD_0.20.app/Contents/Resources/lib/perl5/5.32/core_perl/auto/Encode/Encode.bundle
file /Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle
file /System/Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle
file /System/Library/Perl/5.34/darwin-thread-multi-2level/auto/Encode/Encode.bundle
file /Users/USERNAME/.cpan/build/Encode-3.19-0/blib/arch/auto/Encode/Encode.bundle
# /Applications/FreeCAD_0.20.app/…/core_perl/auto/Encode/Encode.bundle:
# Mach-O 64-bit bundle x86_64
# /Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle:
# Mach-O 64-bit bundle x86_64
# /System/Library/Perl/5.30/darwin-thread-multi-2level/auto/Encode/Encode.bundle:
# Mach-O universal binary with 2 architectures:
# [x86_64:Mach-O 64-bit bundle x86_64]
# [arm64e:Mach-O 64-bit bundle arm64e]
# /System/Library/Perl/5.34/darwin-thread-multi-2level/auto/Encode/Encode.bundle:
# Mach-O universal binary with 2 architectures:
# [x86_64:Mach-O 64-bit bundle x86_64]
# [arm64e:Mach-O 64-bit bundle arm64e]
# /Users/USERNAME/.cpan/build/Encode-3.19-0/blib/arch/auto/Encode/Encode.bundle:
# Mach-O 64-bit bundle x86_64
observations:
file /System/Library/Perl/…Encode.bundleshows that universal binary use does indeed exist for Pearl.file /Library/Perl/…Encode.bundleindicate that a user install and/or update may be masking the universal/System/Library/Perl/…Encode.bundlefrom shared application use.
objectives
Ideally, an overall solution would:
- be generally enough to not require modification of each individual module which gets added.
- work for both an initial module installation and any subsequent updates.
- does not create dependency conflicts with in the Perl installation.
possible approaches
Whether implicitly or expressly invoked, lipo appear to be needed to create the univeral binary.
Just thinking out-loud about some approach directions:
modify the Perl make file? (how would one safely do this? is this a practical approach?)
create an updated version of Config_u?
perl -MConfig_u Makefile.PL
have parallel
/Perl/arm64/…and/Perl/x86_64/…trees which are thenlipomerged into some/Perl/some_universal_version/…via a script.Could this be a easy as
sudo arch -x86_64 -arm64 -arm64e cpan -i Encode?