Skip to content
This repository was archived by the owner on Dec 16, 2022. It is now read-only.

Commit 12147cc

Browse files
author
Paulo Gomes
committed
Statically build libgit2 and its dependencies using musl toolchain.
This removes the dependency to glibc and allows the project to be less dependent to configurations picked by official OS packages. Signed-off-by: Paulo Gomes <[email protected]>
1 parent 70c676b commit 12147cc

14 files changed

+568
-115
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
build/
2+
vendor/

Dockerfile

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
FROM scratch
22

3-
COPY ./hack/Makefile Makefile
3+
COPY ./hack/Makefile /Makefile
4+
COPY ./hack/static.sh /static.sh

Dockerfile.test

+102-29
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,126 @@
11
# This Dockerfile tests the hack/Makefile output against git2go.
2-
ARG BASE_VARIANT=bullseye
2+
ARG BASE_VARIANT=alpine
33
ARG GO_VERSION=1.17.6
44
ARG XX_VERSION=1.1.0
55

66
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
77

8-
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_VARIANT} as gostable
8+
FROM golang:${GO_VERSION}-${BASE_VARIANT} as gostable
99

1010
FROM gostable AS go-linux
1111

12-
FROM go-${TARGETOS} AS build-base-bullseye
12+
FROM --platform=$BUILDPLATFORM ${BASE_VARIANT} AS build-deps
13+
14+
RUN apk add --no-cache \
15+
bash \
16+
curl \
17+
build-base \
18+
linux-headers \
19+
perl \
20+
cmake \
21+
pkgconfig \
22+
gcc \
23+
musl-dev \
24+
clang \
25+
lld
1326

1427
COPY --from=xx / /
1528

16-
# Align golang base image with bookworm.
17-
# TODO: Replace this with a golang bookworm variant, once it is released.
18-
RUN echo "deb http://deb.debian.org/debian bookworm main" > /etc/apt/sources.list.d/bookworm.list \
19-
&& echo "deb-src http://deb.debian.org/debian bookworm main" /etc/apt/sources.list.d/bookworm.list \
20-
&& xx-apt update \
21-
&& xx-apt -t bookworm upgrade -y
29+
ARG TARGETPLATFORM
2230

23-
COPY ./hack/Makefile /libgit2/Makefile
31+
RUN xx-apk add --no-cache \
32+
xx-c-essentials
2433

25-
RUN make -C /libgit2/ cmake
34+
RUN xx-apk add --no-cache \
35+
xx-cxx-essentials
2636

2737
ARG TARGETPLATFORM
28-
RUN make -C /libgit2/ dependencies
38+
RUN xx-apk add --no-cache \
39+
build-base \
40+
pkgconfig \
41+
gcc \
42+
musl-dev \
43+
clang \
44+
lld \
45+
llvm \
46+
linux-headers
2947

30-
FROM build-base-${BASE_VARIANT} as libgit2-bullseye
48+
WORKDIR /build
49+
COPY hack/static.sh .
3150

3251
ARG TARGETPLATFORM
33-
RUN FLAGS=$(xx-clang --print-cmake-defines) make -C /libgit2/ libgit2
52+
ENV CC=xx-clang
53+
ENV CXX=xx-clang++
54+
55+
RUN CHOST=$(xx-clang --print-target-triple) \
56+
./static.sh build_libz
57+
58+
RUN CHOST=$(xx-clang --print-target-triple) \
59+
./static.sh build_openssl
60+
61+
RUN export LIBRARY_PATH="/usr/local/$(xx-info triple)/lib:/usr/local/$(xx-info triple)/lib64:${LIBRARY_PATH}" && \
62+
export PKG_CONFIG_PATH="/usr/local/$(xx-info triple)/lib/pkgconfig:/usr/local/$(xx-info triple)/lib64/pkgconfig" && \
63+
export OPENSSL_ROOT_DIR="/usr/local/$(xx-info triple)" && \
64+
export OPENSSL_CRYPTO_LIBRARY="/usr/local/$(xx-info triple)/lib64" && \
65+
export OPENSSL_INCLUDE_DIR="/usr/local/$(xx-info triple)/include/openssl"
66+
67+
RUN ./static.sh build_libssh2
68+
RUN ./static.sh build_libgit2
69+
70+
71+
FROM go-${TARGETOS} AS build
72+
73+
# Copy cross-compilation tools
74+
COPY --from=xx / /
75+
# Copy compiled libraries
76+
COPY --from=build-deps /usr/local/ /usr/local/
3477

35-
FROM libgit2-${BASE_VARIANT} as test-bullseye
78+
RUN apk add clang lld pkgconfig
3679

37-
# Cache clone
38-
ARG GIT2GO_TAG
39-
RUN git config --global advice.detachedHead false \
40-
&& git clone --depth=1 --branch=${GIT2GO_TAG} https://github.com/libgit2/git2go /git2go \
41-
&& cd /git2go \
42-
&& go mod tidy
80+
WORKDIR /root/smoketest
81+
COPY tests/smoketest/go.mod .
82+
COPY tests/smoketest/go.sum .
83+
RUN go mod download
4384

44-
# Set workdir
45-
WORKDIR /git2go
85+
COPY tests/smoketest/main.go .
4686

47-
# Run tests
4887
ARG TARGETPLATFORM
49-
ARG CACHE_BUST
50-
RUN set -eux; \
51-
echo "=> Dynamic test at $CACHE_BUST for $TARGETPLATFORM" \
52-
&& CGO_ENABLED=1 xx-go run script/check-MakeGitError-thread-lock.go \
53-
&& CGO_ENABLED=1 xx-go test ./...
88+
# Some dependencies have to installed
89+
# for the target platform: https://github.com/tonistiigi/xx#go--cgo
90+
RUN xx-apk add --no-cache \
91+
musl-dev \
92+
gcc
93+
94+
ENV CGO_ENABLED=1
95+
RUN export LIBRARY_PATH="/usr/local/$(xx-info triple)/lib:/usr/local/$(xx-info triple)/lib64:${LIBRARY_PATH}" && \
96+
export PKG_CONFIG_PATH="/usr/local/$(xx-info triple)/lib/pkgconfig:/usr/local/$(xx-info triple)/lib64/pkgconfig" && \
97+
export FLAGS="$(pkg-config --static --libs --cflags libssh2 openssl libgit2)" && \
98+
CGO_LDFLAGS="${FLAGS} -static" \
99+
xx-go build \
100+
-ldflags "-s -w" \
101+
-tags 'netgo,osusergo,static_build' \
102+
-o static-test-runner -trimpath main.go;
103+
104+
# Ensure that the generated binary is valid for the target platform
105+
RUN xx-verify --static static-test-runner
106+
107+
# This can be deployed into a gcr.io/distroless/static, however
108+
# the alpine has been chosen so it can run the static application
109+
# using the `RUN` statement.
110+
FROM ${BASE_VARIANT}
111+
112+
WORKDIR /root/smoketest
113+
COPY tests/smoketest/keys /root/smoketest/keys
114+
COPY --from=build \
115+
/root/smoketest/static-test-runner .
116+
117+
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
118+
119+
# To do docker run instead, replace the RUN statement with:
120+
# ENTRYPOINT [ "/root/smoketest/static-test-runner" ]
121+
122+
# The approach below was preferred as it provides a way to
123+
# assert the functionality across the supported architectures
124+
# without any extra steps.
125+
126+
RUN /root/smoketest/static-test-runner

Makefile

+5-7
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ STATIC_TEST_TAG := test
55
PLATFORMS ?= linux/amd64,linux/arm/v7,linux/arm64
66
BUILD_ARGS ?=
77

8-
GIT2GO_TAG ?= v31.6.1
9-
108
.PHONY: build
119
build:
1210
docker buildx build \
@@ -20,18 +18,18 @@ test:
2018
docker buildx build \
2119
--platform=$(PLATFORMS) \
2220
--tag $(IMG):$(TAG) \
23-
--build-arg IMG=$(IMG) \
24-
--build-arg TAG=$(TAG) \
25-
--build-arg GIT2GO_TAG=$(GIT2GO_TAG) \
26-
--build-arg CACHE_BUST="$(shell date --rfc-3339=ns --utc)" \
27-
--file Dockerfile.test .
21+
--file Dockerfile.test \
22+
$(BUILD_ARGS) .
2823

2924
.PHONY: builder
3025
builder:
26+
# create local builder
3127
docker buildx create --name local-builder \
3228
--platform $(PLATFORMS) \
3329
--driver-opt network=host \
3430
--driver-opt env.BUILDKIT_STEP_LOG_MAX_SIZE=1073741274 \
3531
--driver-opt env.BUILDKIT_STEP_LOG_MAX_SPEED=5000000000000 \
3632
--buildkitd-flags '--allow-insecure-entitlement security.insecure' \
3733
--use
34+
# install qemu emulators
35+
docker run -it --rm --privileged tonistiigi/binfmt --install all

README.md

+44-77
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# golang-with-libgit2
22

3-
This repository contains a `Dockerfile` which `Makefile` content can be used to build the [libgit2][] dependency
4-
chain for **AMD64, ARM64 and ARMv7** binaries of Go projects that depend on [git2go][].
3+
This repository contains a `Dockerfile` with two files: `Makefile` and `static.sh`.
4+
Both of which can be used to build the [libgit2][] dependency chain for **AMD64, ARM64 and ARMv7** binaries
5+
of Go projects that depend on [git2go][].
6+
7+
The `Makefile` is useful for development environments and will leverage OS specific packages to build `libgit2`.
8+
The `static.sh` will build all `libgit2` dependencies from source using `musl` toolchain. This enables for a full
9+
static binary with the freedom of configuring each of the dependencies in chain.
510

611
### :warning: **Public usage discouraged**
712

@@ -42,86 +47,20 @@ while testing these against the git2go code before releasing the image.
4247
- [ ] [libgit2/git2go#836](https://github.com/libgit2/git2go/issues/836)
4348
- [ ] [libgit2/git2go#837](https://github.com/libgit2/git2go/issues/837)
4449

45-
## Usage
46-
47-
To make use of the image published by the `Dockerfile`, copy over the `Makefile` from the image as a prerequisite to
48-
your Go build. Once copied over to the image, the set of targets can be used to compile `libgit2` for the
49-
`$BUILDPLATFORM` and/or `$TARGETPLATFORM` (see [example](#Dockerfile-example)).
50-
51-
### Runtime dependencies
52-
53-
The following dependencies should be present in the image running the application:
54-
55-
- `libc6`
56-
- `ca-certificates`
57-
- `zlib1g/bookworm`
58-
- `libssl1.1/bookworm`
59-
- `libssh2-1/bookworm`
60-
61-
**Note:** at present, all dependencies suffixed with `bookworm` must be installed from Debian's `bookworm` release,
62-
[due to a misconfiguration in `libssh2-1` in earlier versions][libssh2-1-misconfiguration].
63-
64-
### `Dockerfile` example
65-
66-
```Dockerfile
67-
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
68-
FROM fluxcd/golang-with-libgit2 as libgit2
69-
70-
FROM --platform=$BUILDPLATFORM golang:1.17.6-bullseye as build
71-
72-
# Copy the build utiltiies
73-
COPY --from=xx / /
74-
COPY --from=libgit2 /Makefile /libgit2/
75-
76-
# Install the libgit2 build dependencies
77-
RUN make -C /libgit2 cmake
78-
79-
ARG TARGETPLATFORM
80-
RUN make -C /libgit2 dependencies
81-
82-
# Compile and install libgit2
83-
RUN FLAGS=$(xx-clang --print-cmake-defines) make -C /libgit2 libgit2 \
84-
&& mkdir -p /libgit2/lib/ \
85-
&& cp -d /usr/lib/$(xx-info triple)/libgit2.so* /libgit2/lib/
50+
---
51+
**NOTE**
8652

87-
# Configure workspace
88-
WORKDIR /workspace
53+
The issues above do not affect libgit2 built with `static.sh` as all its
54+
dependencies have been configured to be optimal for its use, as the first supported version of libgit2 is `1.3.0`.
8955

90-
# Copy modules manifests
91-
COPY go.mod go.mod
92-
COPY go.sum go.sum
56+
---
9357

94-
# Cache modules
95-
RUN go mod download
96-
97-
# Copy source code
98-
COPY main.go main.go
99-
100-
# Build the binary
101-
ENV CGO_ENABLED=1
102-
ARG TARGETPLATFORM
103-
RUN xx-go build -o app \
104-
main.go
105-
106-
FROM debian:bookworm-slim as controller
107-
108-
# Install runtime dependencies
109-
RUN apt update \
110-
&& apt install -y zlib1g/bookworm libssl1.1 libssh2-1 \
111-
&& apt install -y ca-certificates \
112-
&& apt clean \
113-
&& apt autoremove --purge -y \
114-
&& rm -rf /var/lib/apt/lists/*
115-
116-
# Copy libgit2.so*
117-
COPY --from=build /libgit2/lib/ /usr/local/lib/
118-
RUN ldconfig
58+
## Usage
11959

120-
# Copy over binary from build
121-
COPY --from=build /workspace/app /usr/local/bin/
60+
The [Dockerfile.test](./Dockerfile.test) file provides a working example on how to statically build a golang application that has a dependency to libgit2 and git2go.
12261

123-
ENTRYPOINT [ "app" ]
124-
```
62+
The example will statically build all dependencies based on the versions specified on `static.sh`.
63+
Then statically build the golang application and deploy it into an image based off `gcr.io/distroless/static`.
12564

12665
## Contributing
12766

@@ -162,6 +101,34 @@ In case changes happen to the `Dockerfile` while the `libgit2` version does not
162101
be suffixed with `-<seq num in range>`. For example, `libgit2-1.1.1-2` for the **third** container image
163102
with the same version.
164103

104+
### Debugging cross-compilation
105+
106+
Below are a few tips on how to overcome cross-compilation issues:
107+
108+
1) Ensure all qemu emulators are installed:
109+
```sh
110+
docker run -it --rm --privileged tonistiigi/binfmt --install all
111+
```
112+
113+
2) Check that the generated libraries are aligned with the target architecture:
114+
115+
Leveraging `readelf` from `binutils` (i.e. `apk add binutils`), check the target machine
116+
architecture:
117+
118+
```sh
119+
$ readelf -h /usr/local/aarch64-alpine-linux-musl/lib/libcrypto.a | grep Machine |sort -u
120+
Machine: AArch64
121+
$ readelf -h /usr/local/aarch64-alpine-linux-musl/lib/libgit2.a | grep Machine | sort -u
122+
Machine: AArch64
123+
$ readelf -h /usr/local/aarch64-alpine-linux-musl/lib/libssh2.a | grep Machine | sort -u
124+
Machine: AArch64
125+
$ readelf -h /usr/local/aarch64-alpine-linux-musl/lib/libssl.a | grep Machine | sort -u
126+
Machine: AArch64
127+
$ readelf -h /usr/local/aarch64-alpine-linux-musl/lib/libz.a | grep Machine | sort -u
128+
Machine: AArch64
129+
```
130+
131+
165132
[xx]: https://github.com/tonistiigi/xx
166133
[Go container image]: https://hub.docker.com/_/golang
167134
[libgit2]: https://github.com/libgit2/libgit2

hack/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ CMAKE_VERSION ?= 3.21.3
2626
# Libgit2 version to be compiled and installed.
2727
LIBGIT2_VERSION ?= 1.1.1
2828
# In some scenarios libgit2 needs to be checked out to a specific commit.
29-
# This takes presidence over LIBGIT_VERSION if defined.
29+
# This takes precedence over LIBGIT_VERSION if defined.
3030
# Ref: https://github.com/libgit2/git2go/issues/834
3131
LIBGIT2_REVISION ?=
3232

0 commit comments

Comments
 (0)