Skip to content

Instantly share code, notes, and snippets.

@chrismacke
Created October 17, 2025 15:05
Show Gist options
  • Select an option

  • Save chrismacke/c123a6c25c211932099628d0504e5808 to your computer and use it in GitHub Desktop.

Select an option

Save chrismacke/c123a6c25c211932099628d0504e5808 to your computer and use it in GitHub Desktop.
Build macOS Universal LibRaw dylib
#!/bin/bash
set -euo pipefail
LIBRAW_VERSION="${LIBRAW_VERSION:-0.21.1}"
LCMS2_VERSION="${LCMS2_VERSION:-2.15}"
DEPLOY_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
ARCHS=(arm64 x86_64)
ROOT_DIR="$(pwd)"
BUILD_DIR="$ROOT_DIR/build"
PREFIX_DIR="$ROOT_DIR/prefix"
UNIVERSAL_DIR="$ROOT_DIR/libraw_universal"
SRC_LIBRAW="$ROOT_DIR/libraw_src"
SRC_LCMS="$ROOT_DIR/lcms2_src"
DL_TAR_LIBRAW="$ROOT_DIR/libraw.tar.gz"
DL_TAR_LCMS="$ROOT_DIR/lcms2.tar.gz"
rm -rf "$BUILD_DIR" "$PREFIX_DIR" "$UNIVERSAL_DIR" "$SRC_LIBRAW" "$SRC_LCMS" "$DL_TAR_LIBRAW" "$DL_TAR_LCMS"
mkdir -p "$BUILD_DIR" "$PREFIX_DIR" "$UNIVERSAL_DIR/lib" "$UNIVERSAL_DIR/include"
refresh_gnu_config() {
local dir="$1"
find "$dir" -type f \( -name config.guess -o -name config.sub \) | while read -r f; do
curl -sL "https://git.savannah.gnu.org/cgit/config.git/plain/$(basename "$f")" -o "$f"
chmod +x "$f"
done
}
# ====== Fetch and extract sources ======
echo "==> Fetching LibRaw $LIBRAW_VERSION"
curl -L -o "$DL_TAR_LIBRAW" "https://www.libraw.org/data/LibRaw-$LIBRAW_VERSION.tar.gz"
mkdir -p "$SRC_LIBRAW"
tar -xzf "$DL_TAR_LIBRAW" -C "$SRC_LIBRAW" --strip-components=1
refresh_gnu_config "$SRC_LIBRAW"
echo "==> Fetching Little CMS $LCMS2_VERSION"
curl -L -o "$DL_TAR_LCMS" "https://downloads.sourceforge.net/project/lcms/lcms/$LCMS2_VERSION/lcms2-$LCMS2_VERSION.tar.gz"
mkdir -p "$SRC_LCMS"
tar -xzf "$DL_TAR_LCMS" -C "$SRC_LCMS" --strip-components=1
refresh_gnu_config "$SRC_LCMS"
SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
# ====== Build LCMS2 normally (for both archs) ======
for ARCH in "${ARCHS[@]}"; do
echo "==> Building LCMS2 for $ARCH"
BUILD_LCMS="$BUILD_DIR/lcms2_$ARCH"
mkdir -p "$BUILD_LCMS"
cp -r "$SRC_LCMS/" "$BUILD_LCMS/"
pushd "$BUILD_LCMS" >/dev/null
export CC=clang
export CXX=clang++
export CFLAGS="-arch $ARCH -g -fPIC -O2 -mmacosx-version-min=$DEPLOY_TARGET -isysroot $SDKROOT"
export CXXFLAGS="$CFLAGS"
export LDFLAGS="-arch $ARCH -mmacosx-version-min=$DEPLOY_TARGET -isysroot $SDKROOT"
export CPPFLAGS="-isysroot $SDKROOT"
ARCH_PREFIX="$PREFIX_DIR/$ARCH"
./configure --disable-static --enable-shared --enable-debug --host="${ARCH}-apple-darwin" --prefix="$ARCH_PREFIX"
make -j"$(sysctl -n hw.logicalcpu)"
make install
# Rename for consistency
mv "$ARCH_PREFIX/lib/"liblcms2.*.dylib "$ARCH_PREFIX/lib/liblcms2.dylib"
install_name_tool -id "@rpath/liblcms2.dylib" "$ARCH_PREFIX/lib/liblcms2.dylib"
popd >/dev/null
done
# ====== Build LibRaw statically for both archs ======
for ARCH in "${ARCHS[@]}"; do
echo "==> Building LibRaw for $ARCH (static only)"
BUILD_RAW="$BUILD_DIR/libraw_$ARCH"
mkdir -p "$BUILD_RAW"
cp -r "$SRC_LIBRAW/" "$BUILD_RAW/"
pushd "$BUILD_RAW" >/dev/null
export CC=clang
export CXX=clang++
export CFLAGS="-arch $ARCH -g -fPIC -O2 -mmacosx-version-min=$DEPLOY_TARGET -isysroot $SDKROOT"
export CXXFLAGS="$CFLAGS"
export LDFLAGS="-arch $ARCH -mmacosx-version-min=$DEPLOY_TARGET -isysroot $SDKROOT"
export CPPFLAGS="-isysroot $SDKROOT"
ARCH_PREFIX="$PREFIX_DIR/$ARCH"
./configure --disable-shared --enable-static --disable-examples --disable-silent-rules --prefix="$ARCH_PREFIX" --host="${ARCH}-apple-darwin"
make -j"$(sysctl -n hw.logicalcpu)"
cp lib/.libs/libraw.a "$ARCH_PREFIX/lib/libraw.a"
popd >/dev/null
done
# ====== Create dylibs manually using clang++ and -all_load ======
for ARCH in "${ARCHS[@]}"; do
echo "==> Creating libraw.dylib for $ARCH with -all_load"
OUT_DYLIB="$BUILD_DIR/libraw_${ARCH}.dylib"
LIBRAW_A="$PREFIX_DIR/$ARCH/lib/libraw.a"
LIBLCMS2_DYLIB="$PREFIX_DIR/$ARCH/lib/liblcms2.dylib"
clang++ -dynamiclib \
-arch $ARCH \
-Wl,-all_load \
-Wl,-install_name,@rpath/libraw.dylib \
"$LIBRAW_A" -o "$OUT_DYLIB" \
-L"$PREFIX_DIR/$ARCH/lib" -llcms2 -lz \
-isysroot "$SDKROOT" \
-mmacosx-version-min=$DEPLOY_TARGET \
-g
# Optional: copy LCMS2 for later merging
cp "$LIBLCMS2_DYLIB" "$BUILD_DIR/liblcms2_${ARCH}.dylib"
done
# ====== lipo to create universal dylibs ======
echo "==> Creating universal dylibs with lipo"
lipo -create \
"$BUILD_DIR/libraw_x86_64.dylib" \
"$BUILD_DIR/libraw_arm64.dylib" \
-output "$UNIVERSAL_DIR/lib/libraw.dylib"
install_name_tool -id "@rpath/libraw.dylib" "$UNIVERSAL_DIR/lib/libraw.dylib"
lipo -create \
"$BUILD_DIR/liblcms2_x86_64.dylib" \
"$BUILD_DIR/liblcms2_arm64.dylib" \
-output "$UNIVERSAL_DIR/lib/liblcms2.dylib"
install_name_tool -id "@rpath/liblcms2.dylib" "$UNIVERSAL_DIR/lib/liblcms2.dylib"
# ====== Generate dSYM files ======
dsymutil "$UNIVERSAL_DIR/lib/libraw.dylib" -o "$UNIVERSAL_DIR/lib/libraw.dylib.dSYM" || echo "⚠️ No debug symbols found in libraw.dylib"
dsymutil "$UNIVERSAL_DIR/lib/liblcms2.dylib" -o "$UNIVERSAL_DIR/lib/liblcms2.dylib.dSYM" || echo "⚠️ No debug symbols found in liblcms2.dylib"
# ====== Headers ======
rsync -a "$PREFIX_DIR/arm64/include/" "$UNIVERSAL_DIR/include/"
# ====== Summary ======
echo ""
echo "✅ Done: universal dylibs with full symbol export"
echo "📦 libraw.dylib: $UNIVERSAL_DIR/lib/libraw.dylib"
echo "📦 liblcms2.dylib: $UNIVERSAL_DIR/lib/liblcms2.dylib"
echo "📂 Headers: $UNIVERSAL_DIR/include"
echo ""
echo "🔍 Symbol count:"
nm -gU "$UNIVERSAL_DIR/lib/libraw.dylib" | wc -l
echo ""
echo "🔎 lipo info:"
lipo -info "$UNIVERSAL_DIR/lib/libraw.dylib"
lipo -info "$UNIVERSAL_DIR/lib/liblcms2.dylib"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment