Linking and Loading *.dylib Files in Go Barcode Reader Modules on macOS

In previous articles, we developed a Go barcode reader module integrating the Dynamsoft Barcode Reader C/C++ SDK for Windows and Linux. In this article, we will extend the module to support macOS.

Development Environment

  • Mac mini (M2, 2023)
  • macOS Sonoma 14.2

Apple Silicon Mac mini

Prerequisites

  • Xcode Command Line Tools

      xcode-select --install
    
  • Go Environment
  • Dynamsoft Barcode Reader v9.x License Key
  • Dynamic libraries (*.dylib) of Dynamsoft Barcode Reader v9.x for macOS.

    If you download the C++ SDK package from the official website, you might notice that the macOS libraries are missing. However, there’s no need to worry. Dynamsoft’s top-tier desktop barcode SDK for Python includes all C++ dynamic libraries built for various ABIs (arm32, x86, x64, and arm64) and platforms (Windows, Linux and macOS), and these can be found in the official Python Barcode SDK.

    Dynamsoft Python barcode SDK

    First, download the tar.gz file and extract it.

    Python barcode SDK tar package

    Inside, you’ll discover three folders designated for macOS: macos, macos_arm64 and macos_universal2. The macos folder is for Intel-based Macs, macos_arm64 for Apple Silicon Macs, and macos_universal2 contains universal dynamic libraries compatible with both Intel-based and Apple Silicon Macs. To ensure our Go module works seamlessly across all Mac platforms, we will utilize the universal dynamic libraries.

Step 1: Adding *.dylib Files to the Go Module

  1. In the lib directory of your Go module, create a new subfolder named mac. Then, transfer libDynamicPdf.dylib, libDynamsoftBarcodeReader.dylib, and libDynamsoftLicenseClient.dylib from the macos_universal2 directory into the mac folder.

    Add macOS dylib to Go module

  2. In the reader.go file, define macOS-specific LDFLAGS using cgo. Use darwin as the Go build tag to target macOS environments.

     package barcode
    
     import (
     	"unsafe"
        
     	/*
     	   #cgo darwin LDFLAGS: -L${SRCDIR}/lib/mac -lDynamsoftBarcodeReader
            ...
     	*/
     	"C"
     )
    

With these steps, your Go barcode reader module is now set up for macOS compatibility. Integrating this module into a Go application may require additional steps.

Step 2: Ensuring Go Tests Pass on macOS

As with Windows and Linux, it’s crucial to pass the Go tests to verify the functionality of the Go barcode reader module on macOS.

Running Go Tests on macOS

Execute go test in the terminal to initiate the tests.

go test error on macOS

During testing, we may encounter issues such as:

  • permission denied
  • dyld: Library not loaded: libDynamsoftBarcodeReader.dylib

Resolving the Issues

  1. Permission Denied: This issue can often be resolved by running the test with sudo:

     sudo go test
    
  2. Dynamic Library Not Loaded: This error indicates that the dynamic linker cannot find libDynamsoftBarcodeReader.dylib. To resolve this, ensure the library path is correctly specified. Here are some potential solutions:

    • Copy the libDynamsoftBarcodeReader.dylib file into either /usr/local/lib or /usr/lib.

      ✖ Not recommended due to the requirement for root permissions to copy files into system library directories.

    • Set the DYLD_LIBRARY_PATH environment variable, similar to the approach on the Linux platform.

      ✖ Ineffective on macOS Sonoma 14.2.

    • Utilize the install_name_tool command to append the library search path to the executable.

      ✔ A viable option. We need to determine how to generate the executable file for Go testing.

What Does the go test Command Do?

When you execute go test, the Go toolchain compiles the test files (*_test.go) alongside the package under test into a temporary executable designed exclusively for running tests. Once the test binary is compiled, go test immediately executes it. Typically, this executable is deleted after the tests have completed.

However, you can manually generate and execute the test binary with the following commands:

go test -c -o testapp

./testapp

Adding Rpath to the Go Test Binary

  1. Utilize the otool command to inspect the library search path of the Go test binary.

     otool -L testapp
        
     testapp:
         @rpath/libDynamsoftBarcodeReader.dylib
         ...
    

    otool command

    Here, @rpath acts as a placeholder that the dynamic linker (dyld) resolves at runtime, based on the actual rpath entries embedded in the executable.

  2. Employ the install_name_tool command to append the library search path to the Go test binary.

     install_name_tool -add_rpath ./lib/mac testapp
    

    To verify whether the rpath was added successfully, execute the otool command once more.

     otool -l testapp | grep -A2 LC_RPATH
    

Writing a Script to Build and Run a Go Test

Create a Shell script named run_mac_test.sh to compile and execute the Go test binary as follows:

#!/bin/bash

RPATH="./lib/mac"
TARGET="testapp"

go test -c -o $TARGET

if ! otool -l $TARGET | grep -q $RPATH; then
    echo "Adding rpath $RPATH to $TARGET"
    install_name_tool -add_rpath $RPATH $TARGET
else
    echo "RPATH $RPATH already exists in $TARGET"
fi

./$TARGET

rm ./$TARGET

This script first compiles the Go test into a binary named testapp. It then checks whether the specified RPATH is already present in the binary. If not, it adds the RPATH using install_name_tool. After running the test binary, the script cleans up by removing it.

To ensure the script can be executed directly, it must be given execute permissions:

chmod +x run_mac_test.sh

sudo ./run_mac_test.sh

go test on macOS

Step 3: Running Go Barcode Reader Application on macOS

Using the Shell script run_mac_test.sh as a reference, we can create a new script named run_mac.sh to compile and execute the Go barcode reader application found in the example directory.

#!/bin/bash

# Save the original PATH
originalPath=$LD_LIBRARY_PATH

# Get the GOPATH
GOPATH=$(go env GOPATH)

# Find the path to the shared libraries
PACKAGE_PATH=$(find "$GOPATH/pkg/mod/github.com/yushulx" -mindepth 1 -maxdepth 1 -type d | sort -r | head -n 1)
echo "PACKAGE_PATH set to $PACKAGE_PATH"
RPATH="$PACKAGE_PATH/lib/mac"

echo "LIBRARY_PATH set to $LIBRARY_PATH"

TARGET="testapp"

go build -o $TARGET

if ! otool -l $TARGET | grep -q $RPATH; then
    echo "Adding rpath $RPATH to $TARGET"
    install_name_tool -add_rpath $RPATH $TARGET
else
    echo "RPATH $RPATH already exists in $TARGET"
fi

./$TARGET test.png

rm ./$TARGET

The RPATH is configured using the GOPATH and the package path of the latest Go barcode reader module. We utilize the go build command to compile the Go barcode reader application. Subsequently, the install_name_tool command is employed to append the library search path to the executable.

Source Code

https://github.com/yushulx/goBarcodeQrSDK