Linking and Loading *.dylib Files in Go Barcode Reader Modules on macOS
Tags: go, macos, cgo
Published: 2024-03-20 05:09:51

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](https://www.dynamsoft.com/codepool/img/2024/03/m2-mac-mini.png)

## Prerequisites
- **Xcode Command Line Tools**
    
    ```bash
    xcode-select --install
    ```
    
- [Go Environment](https://go.dev/dl/)
- [Dynamsoft Barcode Reader v9.x License Key](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr&source=codepool)
- 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](https://pypi.org/project/dbr/#files). 

    ![Dynamsoft Python barcode SDK](https://www.dynamsoft.com/codepool/img/2024/03/dynamsoft-python-barcode-sdk.png)

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

    ![Python barcode SDK tar package](https://www.dynamsoft.com/codepool/img/2024/03/python-barcode-sdk-tar-package.png)

    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](https://www.dynamsoft.com/codepool/img/2024/03/go-module-add-mac-dylib.png)

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

    ```go
    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](https://www.dynamsoft.com/codepool/img/2024/03/go-test-issue.png)

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`:

    ```bash
    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:

```bash
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.

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

    ![otool command](https://www.dynamsoft.com/codepool/img/2024/03/otool-check-dylib-dependency.png)

    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.

    ```bash
    install_name_tool -add_rpath ./lib/mac testapp
    ```

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

    ```bash
    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:

```bash
#!/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:

```bash
chmod +x run_mac_test.sh

sudo ./run_mac_test.sh
```

![go test on macOS](https://www.dynamsoft.com/codepool/img/2024/03/go-test-barcode-reader-macos.png)

## 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.

```bash
#!/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](https://github.com/yushulx/goBarcodeQrSDK)