Skip to content

Commit

Permalink
Refactor binary generation options
Browse files Browse the repository at this point in the history
The previous release generated Windows binaries that were
broken (GH-53) and Linux binaries that were unintentionally
dynamic (GH-48).

This commit provides an updated Makefile and GitHub
workflow which provides multiple build options for both
dynamic and static linking, but defaults to the previous
dynamically linked behavior. The GitHub Workflow jobs
based on the Makefile build tasks use a 20 minute
timeout vs the 10 minute timeout used previously.

The Makefile build options now explicitly enable the
`CGO_ENABLED` environment variable so that all builds
have the required cgo functionality enabled.

A `docker` Makefile recipe is provided to generate
binaries using new Docker images from the `atc0005/go-ci`
project based on the official Golang Alpine Linux
image. The result is statically linked binaries based on
the musl C library instead of glibc. The intent is to
help prevent licensing issues surround the GNU C
library's LGPL licensing (which I do not fully understand).

Multiple build tags are specified for static builds which
enable Go-specific replacements for common glibc-provided
features:

- `osusergo`
- `netgo`

and a build tag specific to disabling SQLite extensions,
which we do not use with this specific project:

- `sqlite_omit_load_extension`

Minor documentation updates have been included which update
the build requirements and specific steps for building
binaries for this project. Further updates are likely
needed to add polish.

A Docker Compose file has been included for kicking off
multiple static binary builds in parallel, but it may end
up getting tossed in a later PR if we don't make sufficient
use of it.

- refs GH-48
- refs golang/go 38789
- refs golang/go 26492

- refs atc0005/go-ci#85
  • Loading branch information
atc0005 committed Oct 8, 2020
1 parent 9642c01 commit d875c20
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 50 deletions.
80 changes: 75 additions & 5 deletions .github/workflows/lint-and-build-using-make.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ jobs:
- name: Run Go linting tools using project Makefile
run: make linting

build_code_with_makefile:
name: Build codebase using Makefile
# This is run from *within* a container that is itself within the GitHub
# Actions environment. All of these commands are run within our container.
build_dynamic_binaries_with_makefile:
name: Build dynamically linked binaries using Makefile
runs-on: ubuntu-latest
# Default: 360 minutes
timeout-minutes: 10
timeout-minutes: 20
container:
image: "index.docker.io/golang:latest"

Expand All @@ -68,8 +70,76 @@ jobs:
uses: actions/checkout@v2.3.3

# bsdmainutils provides "column" which is used by the Makefile
- name: Install Ubuntu packages
run: apt-get update && apt-get install -y --no-install-recommends make gcc bsdmainutils
# other packages are needed for cross-compilation
- name: Install Ubuntu packages needed for cross-compilation
run: |
apt-get update && \
apt-get install -y --no-install-recommends \
make \
bsdmainutils \
gcc \
gcc-multilib \
gcc-mingw-w64
- name: Build using project Makefile
run: make all

# This is run from *within* a container that is itself within the GitHub
# Actions environment. All of these commands are run within our container.
build_static_binaries_with_makefile:
name: Build statically linked binaries using Makefile
runs-on: ubuntu-latest
# Default: 360 minutes
timeout-minutes: 20
container:
image: "index.docker.io/golang:latest"

steps:
- name: Print go version
run: go version

- name: Check out code into the Go module directory
uses: actions/checkout@v2.3.3

# bsdmainutils provides "column" which is used by the Makefile
# other packages are needed for cross-compilation
- name: Install Ubuntu packages needed for cross-compilation
run: |
apt-get update && \
apt-get install -y --no-install-recommends \
make \
bsdmainutils \
gcc \
gcc-multilib \
gcc-mingw-w64
- name: Build using project Makefile
run: make all-static

# This is run directly within the GitHub Actions environment and calls the
# `docker` command to perform specific build tasks within Docker containers.
# Prep steps are run within the GitHub Actions environment and not within
# the containers.
build_static_binaries_with_makefile_docker_recipe:
name: Build static binaries using Docker images
runs-on: ubuntu-latest
# Default: 360 minutes
timeout-minutes: 20

steps:
- name: Print go version
run: go version

- name: Check out code into the Go module directory
uses: actions/checkout@v2.3.3

# bsdmainutils provides "column" which is used by the Makefile
- name: Install Ubuntu packages
run: |
sudo apt-get update && \
sudo apt-get install -y --no-install-recommends \
bsdmainutils \
make
- name: Build using project Makefile Docker recipe
run: make docker
195 changes: 170 additions & 25 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,82 @@
# https://gist.github.com/subfuzion/0bd969d08fe0d8b5cc4b23c795854a13
# https://stackoverflow.com/questions/10858261/abort-makefile-if-variable-not-set
# https://stackoverflow.com/questions/38801796/makefile-set-if-variable-is-empty
# https://www.gnu.org/software/make/manual/make.html#Flavors

SHELL = /bin/bash
SHELL := /bin/bash

# Space-separated list of cmd/BINARY_NAME directories to build
WHAT = mysql2sqlite check_mysql2sqlite
WHAT := mysql2sqlite check_mysql2sqlite

# What package holds the "version" variable used in branding/version output?
# VERSION_VAR_PKG = $(shell go list .)
# VERSION_VAR_PKG = main
VERSION_VAR_PKG = $(shell go list .)/internal/config
# VERSION_VAR_PKG := $(shell go list .)
# VERSION_VAR_PKG := main
VERSION_VAR_PKG := $(shell go list .)/internal/config

OUTPUTDIR = release_assets
OUTPUTDIR := release_assets

# https://gist.github.com/TheHippo/7e4d9ec4b7ed4c0d7a39839e6800cc16
VERSION = $(shell git describe --always --long --dirty)
VERSION := $(shell git describe --always --long --dirty)

# The default `go build` process embeds debugging information. Building
# without that debugging information reduces the binary size by around 28%.
BUILDCMD = go build -mod=vendor -a -ldflags="-s -w -X $(VERSION_VAR_PKG).Version=$(VERSION)"
GOCLEANCMD = go clean -mod=vendor ./...
GITCLEANCMD = git clean -xfd
CHECKSUMCMD = sha256sum -b
#
# We also include additional flags in an effort to generate static binaries
# that do not have external dependencies. As of Go 1.15 this still appears to
# be a mixed bag, so YMMV.
#
# See https://github.com/golang/go/issues/26492 for more information.
#
# -s
# Omit the symbol table and debug information.
#
# -w
# Omit the DWARF symbol table.
#
# -tags 'osusergo,netgo'
# Use pure Go implementation of user and group id/name resolution.
# Use pure Go implementation of DNS resolver.
#
# -extldflags '-static'
# Pass 'static' flag to external linker.
#
# -linkmode=external
# https://golang.org/src/cmd/cgo/doc.go
#
# NOTE: Using external linker requires installation of `gcc-multilib`
# package when building 32-bit binaries on a Debian/Ubuntu system. It also
# seems to result in an unstable build that crashes on startup. This *might*
# be specific to the WSL environment used for builds. Further testing is
# needed to confirm.
#
# CGO_ENABLED=1
# CGO is disabled by default for cross-compilation. You need to enable it
# explicitly to use CGO for multiple architectures.
BUILD_LDFLAGS_COMMON := -s -w -X $(VERSION_VAR_PKG).version=$(VERSION)
BUILD_LDFLAGS_STATIC := -linkmode=external -extldflags '-static'
BUILDCMD_COMMON := CGO_ENABLED=1 go build -mod=vendor -a
BUILDCMD_STATIC := $(BUILDCMD_COMMON) -tags 'osusergo,netgo,sqlite_omit_load_extension' -ldflags "$(BUILD_LDFLAGS_STATIC) $(BUILD_LDFLAGS_COMMON)"
BUILDCMD_DYNAMIC := $(BUILDCMD_COMMON) -ldflags "$(BUILD_LDFLAGS_COMMON)"

BUILD_TYPE_STATIC := static
BUILD_TYPE_DYNAMIC := dynamic

# Default build command and type if not overridden
BUILDCMD := $(BUILDCMD_DYNAMIC)
BUILDTYPE := $(BUILD_TYPE_DYNAMIC)

.DEFAULT_GOAL := help
# Use mingw as C compiler to build Windows cgo-enabled binaries.
WINCOMPILERX86 := CC=i686-w64-mingw32-gcc
WINCOMPILERX64 := CC=x86_64-w64-mingw32-gcc

DOCKER_BUILD_IMG_X86 := atc0005/go-ci:go-ci-stable-alpine-buildx86
DOCKER_BUILD_IMG_X64 := atc0005/go-ci:go-ci-stable-alpine-buildx64

GOCLEANCMD := go clean -mod=vendor ./...
GITCLEANCMD := git clean -xfd
CHECKSUMCMD := sha256sum -b

.DEFAULT_GOAL := help

##########################################################################
# Targets will not work properly if a file with the same name is ever
Expand Down Expand Up @@ -122,46 +174,139 @@ pristine: goclean gitclean

.PHONY: all
# https://stackoverflow.com/questions/3267145/makefile-execute-another-target
## all: generates assets for Linux distros and Windows
## all: generates dynamically linked assets for Linux and Windows systems
all: clean windows linux
@echo "Completed all cross-platform builds ..."

.PHONE: all-static
## all-static: generates statically linked x86 and x64 assets for Linux and Windows systems
all-static: clean windows-static linux-static
@echo "Completed all cross-platform builds ..."

.PHONY: windows
## windows: generates assets for Windows systems
windows:
@echo "Building release assets for windows ..."
## windows: generates dynamically linked x86 and x64 Windows assets
windows: windows-x86 windows-x64
@echo "Completed build tasks for windows"

.PHONY: windows-static
## windows-static: generates dynamically linked x86 and x64 Windows assets
windows-static: windows-x86-static windows-x64-static
@echo "Completed build tasks for windows"

.PHONY: windows-x86
## windows-x86: generates dynamically linked Windows x86 assets
windows-x86:
@echo "Building ($(BUILDTYPE)) release assets for windows x86 ..."
@for target in $(WHAT); do \
mkdir -p $(OUTPUTDIR)/$$target && \
echo "Building $$target 386 binaries" && \
env GOOS=windows GOARCH=386 $(BUILDCMD) -o $(OUTPUTDIR)/$$target/$$target-$(VERSION)-windows-386.exe ./cmd/$$target && \
env GOOS=windows GOARCH=386 $(WINCOMPILERX86) $(BUILDCMD) -o $(OUTPUTDIR)/$$target/$$target-$(VERSION)-windows-386.exe ./cmd/$$target && \
echo "Generating $$target x86 checksum files" && \
cd $(OUTPUTDIR)/$$target && \
$(CHECKSUMCMD) $$target-$(VERSION)-windows-386.exe > $$target-$(VERSION)-windows-386.exe.sha256 && \
cd $$OLDPWD; \
done
@echo "Completed ($(BUILDTYPE)) release assets build tasks for windows x86"

.PHONY: windows-x86-static
## windows-x86-static: generates assets statically, specifically for Windows x86 systems
windows-x86-static: BUILDCMD = $(BUILDCMD_STATIC)
windows-x86-static: BUILDTYPE = $(BUILD_TYPE_STATIC)
windows-x86-static: windows-x86

.PHONY: windows-x64
## windows-x64: generates assets specifically for x64 Windows systems
windows-x64:
@echo "Building ($(BUILDTYPE)) release assets for windows x64 ..."
@for target in $(WHAT); do \
mkdir -p $(OUTPUTDIR)/$$target && \
echo "Building $$target amd64 binaries" && \
env GOOS=windows GOARCH=amd64 $(BUILDCMD) -o $(OUTPUTDIR)/$$target/$$target-$(VERSION)-windows-amd64.exe ./cmd/$$target && \
env GOOS=windows GOARCH=amd64 $(WINCOMPILERX64) $(BUILDCMD) -o $(OUTPUTDIR)/$$target/$$target-$(VERSION)-windows-amd64.exe ./cmd/$$target && \
echo "Generating $$target checksum files" && \
cd $(OUTPUTDIR)/$$target && \
$(CHECKSUMCMD) $$target-$(VERSION)-windows-386.exe > $$target-$(VERSION)-windows-386.exe.sha256 && \
$(CHECKSUMCMD) $$target-$(VERSION)-windows-amd64.exe > $$target-$(VERSION)-windows-amd64.exe.sha256 && \
cd $$OLDPWD; \
done
@echo "Completed ($(BUILDTYPE)) release assets build tasks for windows x64"

@echo "Completed build tasks for windows"
.PHONY: windows-x64-static
## windows-x64-static: generates assets statically, specifically for Windows x64 systems
windows-x64-static: BUILDCMD = $(BUILDCMD_STATIC)
windows-x64-static: BUILDTYPE = $(BUILD_TYPE_STATIC)
windows-x64-static: windows-x64

.PHONY: linux
## linux: generates assets for Linux distros
linux:
@echo "Building release assets for linux ..."
## linux: generates dynamically linked x86 and x64 assets for Linux distros
linux: linux-x86 linux-x64
@echo "Completed ($(BUILDTYPE)) release assets build tasks for linux"

.PHONE: linux-static
## linux-static: generates statically linked x86 and x64 assets for Linux distros
linux-static: linux-x86-static linux-x64-static
@echo "Completed ($(BUILDTYPE)) release assets build tasks for linux"

.PHONY: linux-x86
## linux-x86: generates assets specifically for Linux x86 systems
linux-x86:
@echo "Building ($(BUILDTYPE)) release assets for linux x86 ..."

@for target in $(WHAT); do \
mkdir -p $(OUTPUTDIR)/$$target && \
echo "Building $$target 386 binaries" && \
env GOOS=linux GOARCH=386 $(BUILDCMD) -o $(OUTPUTDIR)/$$target/$$target-$(VERSION)-linux-386 ./cmd/$$target && \
echo "Generating $$target checksum files" && \
cd $(OUTPUTDIR)/$$target && \
$(CHECKSUMCMD) $$target-$(VERSION)-linux-386 > $$target-$(VERSION)-linux-386.sha256 && \
cd $$OLDPWD; \
done

@echo "Completed ($(BUILDTYPE)) release assets build tasks for linux x86"

.PHONY: linux-x86-static
## linux-x86-static: generates assets statically, specifically for Linux x86 systems
linux-x86-static: BUILDCMD = $(BUILDCMD_STATIC)
linux-x86-static: BUILDTYPE = $(BUILD_TYPE_STATIC)
linux-x86-static: linux-x86

.PHONY: linux-x64
## linux-x64: generates assets specifically for Linux x64 systems
linux-x64:
@echo "Building ($(BUILDTYPE)) release assets for linux x64 ..."

@for target in $(WHAT); do \
mkdir -p $(OUTPUTDIR)/$$target && \
echo "Building $$target amd64 binaries" && \
env GOOS=linux GOARCH=amd64 $(BUILDCMD) -o $(OUTPUTDIR)/$$target/$$target-$(VERSION)-linux-amd64 ./cmd/$$target && \
echo "Generating $$target checksum files" && \
cd $(OUTPUTDIR)/$$target && \
$(CHECKSUMCMD) $$target-$(VERSION)-linux-386 > $$target-$(VERSION)-linux-386.sha256 && \
$(CHECKSUMCMD) $$target-$(VERSION)-linux-amd64 > $$target-$(VERSION)-linux-amd64.sha256 && \
cd $$OLDPWD; \
done

@echo "Completed build tasks for linux"
@echo "Completed ($(BUILDTYPE)) release assets build tasks for linux x64"


.PHONY: linux-x64-static
## linux-x64-static: generates assets statically, specifically for Linux x64 systems
linux-x64-static: BUILDCMD = $(BUILDCMD_STATIC)
linux-x64-static: BUILDTYPE = $(BUILD_TYPE_STATIC)
linux-x64-static: linux-x64

.PHONY: docker
## docker: generates assets for Linux distros and Windows using Docker
docker: clean
@docker run \
--rm \
-i \
-v $$PWD:$$PWD \
-w $$PWD \
$(DOCKER_BUILD_IMG_X86) \
make windows-x86-static linux-x86-static
@docker run \
--rm \
-i \
-v $$PWD:$$PWD \
-w $$PWD \
$(DOCKER_BUILD_IMG_X64) \
make windows-x64-static linux-x64-static
@echo "Completed all cross-platform builds via Docker containers ..."
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,20 @@ been tested.

### Building source code

These requirements are specific to Ubuntu 18.04+. Packages will likely be
named differently for other distributions.

- Go 1.14+
- `CGO_ENABLED=1` environment variable (if not set by default)
- requirement of SQLite database driver used
- `GCC`
- `GCC multilib`
- `GCC for Windows` (`mingw-w64`)
- `make`
- if using the provided `Makefile`

See the [build](docs/build.md) instructions for more information.

### Running

- Windows 7, Server 2008R2 or later
Expand Down
33 changes: 33 additions & 0 deletions contrib/mysql2sqlite/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
# Copyright 2020 Adam Chalkley
#
# https://github.com/atc0005/mysql2sqlite
#
# Licensed under the MIT License. See LICENSE file in the project root for
# full license information.

# Purpose: Batch build static binaries from this project's source code.

# Usage:
#
# Copy this file to the root of the repo and run it via `docker-compose up`.
# This will kick off two parallel builds and thoroughly thrash the build
# system. The upside is that this *may* better utilize multiple cores vs
# spinning up one container at a time.

version: "2"

services:
mysql2sqlite-x86:
image: atc0005/go-ci:go-ci-stable-alpine-buildx86
volumes:
- "./:/src"
working_dir: "/src"
entrypoint: ["make", "windows-x86-static", "linux-x86-static"]

mysql2sqlite-x64:
image: atc0005/go-ci:go-ci-stable-alpine-buildx64
volumes:
- "./:/src"
working_dir: "/src"
entrypoint: ["make", "windows-x64-static", "linux-x64-static"]
Loading

0 comments on commit d875c20

Please sign in to comment.