dockergocgo

Cross compiling a Go native binary for Java code using docker


I'm trying to compile a Go native binary to use in Java code on Mac. The binary needs to be executed on a linux host which has the following specifications:

# uname -a
Linux <hostname> 5.14.0-284.25.1.el9_2.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Jul 20 09:11:28 EDT 2023 x86_64 x86_64 x86_64 GNU/Linux 

For local testing on mac, I simply generate the binary using go build -o libmybinary.so -buildmode=c-shared main.go but to compile it in the correct format for the linux host, I use this instead:

FROM source as builder 

RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o libmybinary.so -buildmode=c-shared main.go

The problem is that if I don't use CGO_ENABLED=1 GOOS=linux GOARCH=amd64, the binary cannot be executed on the host, but if I try to use it, I get this error in docker build:

1.553 # runtime/cgo
1.553 gcc: error: unrecognized command line option '-m64'
------
ERROR: failed to solve: process "/bin/bash -eo pipefail -c CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o libmybinary.so -buildmode=c-shared main.go" did not complete successfully: exit code: 1

I also tried replacing

FROM source as builder 
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o libmybinary.so -buildmode=c-shared main.go

with

FROM --platform=linux/amd64 source as builder 
RUN go build -o libmybinary.so -buildmode=c-shared main.go

and it seems to compile the .so in correct format for the host but my java code couldn't find the exported methods as I think this way does not essentially "cross-compile" within the native image, leading to differences in behaviour with cgo and the underlying C toolchain. I wanted to check what would be the right way to achieve the correctly compiled binary?


Solution

  • Thank you life888888 for such a detailed answer above, taking inspiration from it, I did something simpler for my use case to make it work. Instead of trying to create the binary in Dockerfile itself, I simply used the Dockerfile to create the environment I wanted matching the one I had on the host. This is defined in the BASE_IMAGE argument where it pull the image I needed (which had the right OS for linux and go installed). So this is simply how my Dockerfile looked:

    FROM ${BASE_IMAGE}:${BASE_TAG} as base
    
    WORKDIR /workspace
    
    COPY go.mod go.mod
    COPY go.sum go.sum
    
    ADD . go_mylib
    RUN go mod download
    

    Then I build the container on my M1 Mac using the command:

    $ docker build -t go_mylib:v1 --platform linux/amd64 .
    

    Run the image using:

    $ docker run -i -t --sysctl net.ipv6.conf.all.disable_ipv6=0 --platform linux/amd64 --name go_mylib-v1 go_mylib:v1 /bin/bash
    

    Once inside the container's bash, I go into the project folder and then run the command to create a shared library:

    # cd go_mylib/
    # go build -o libmybinary.so -buildmode=c-shared main.go
    

    And finally exiting the container, I copy the binary generated to my local folder using this command:

    $ docker cp go_mylib-v1:/workspace/go_mylib/libmybinary.so .
    

    (where /workspace/go_mylib/libmybinary.so is the path of the file inside my container and . refers to the current folder in my local system.

    I finally load this library from my Java code by using:

    MyLib INSTANCE = Native.load("mybinary", MyLib.class);
    

    and it works on the host as expected.