From d875c20c94e022e6adb44c14bd5e484ebf5d8c8d Mon Sep 17 00:00:00 2001 From: Adam Chalkley Date: Sun, 4 Oct 2020 04:34:30 -0500 Subject: [PATCH] Refactor binary generation options 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 --- .../workflows/lint-and-build-using-make.yml | 80 ++++++- Makefile | 195 +++++++++++++++--- README.md | 7 + contrib/mysql2sqlite/docker-compose.yml | 33 +++ docs/build.md | 53 +++-- docs/references.md | 6 + 6 files changed, 324 insertions(+), 50 deletions(-) create mode 100644 contrib/mysql2sqlite/docker-compose.yml diff --git a/.github/workflows/lint-and-build-using-make.yml b/.github/workflows/lint-and-build-using-make.yml index 15cfed50..d6c569ca 100644 --- a/.github/workflows/lint-and-build-using-make.yml +++ b/.github/workflows/lint-and-build-using-make.yml @@ -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" @@ -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 diff --git a/Makefile b/Makefile index bcc36ff6..40f5258c 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 ..." diff --git a/README.md b/README.md index 5f2d5755..151dae33 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/contrib/mysql2sqlite/docker-compose.yml b/contrib/mysql2sqlite/docker-compose.yml new file mode 100644 index 00000000..63c380f4 --- /dev/null +++ b/contrib/mysql2sqlite/docker-compose.yml @@ -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"] diff --git a/docs/build.md b/docs/build.md index 5155667b..cddb289a 100644 --- a/docs/build.md +++ b/docs/build.md @@ -20,19 +20,23 @@ 1. `cd mysql2sqlite` 1. Install dependencies - for Ubuntu Linux - - `sudo apt-get install make gcc` - - for CentOS Linux - - `sudo yum install make gcc` + - `sudo apt-get install make bsdmainutils gcc gcc-multilib gcc-mingw-w64` - for Windows - Emulated environments (*easier*) - - Skip all of this and build using the default `go build` command in - Windows (see below for use of the `-mod=vendor` flag) - build using Windows Subsystem for Linux Ubuntu environment and just - copy out the Windows binaries from that environment + copy out the Windows binaries from that environment or build within a + path accessible from both Windows and WSL (e.g., + `/mnt/c/Users/YOUR_USERNAME/Desktop/mysql2sqlite`) - If already running a Docker environment, use a container with the Go - tool-chain already installed - - If already familiar with LXD, create a container and follow the - installation steps given previously to install required dependencies + tool-chain already installed along with the necessary packages to + allow cross-compilation. If using the official `golang` Docker image, + you will need to install the same packages listed previously for + Ubuntu. Alternatively, you may also use the Alpine-based Docker images created for statically linked, cgo-enabled cross-compilation. + - `atc0005/go-ci:go-ci-stable-alpine-buildx86` + - `atc0005/go-ci:go-ci-stable-alpine-buildx64` + - If already familiar with LXD, create an Ubuntu container and follow + the installation steps given previously to install required + dependencies - Native tooling (*harder*) - see the StackOverflow Question `32127524` link in the [References](references.md) section for potential options for @@ -41,22 +45,31 @@ [References](references.md) section for options for installing `gcc` and related packages on Windows 1. Build binaries - - for the current operating system, explicitly using bundled dependencies - in top-level `vendor` folder - - `go build -mod=vendor ./cmd/check_mysql2sqlite/` - - `go build -mod=vendor ./cmd/mysql2sqlite/` - - for all supported platforms (where `make` is installed) - - `make all` - - for use on Windows - - `make windows` - - for use on Linux - - `make linux` + - dynamically linked + - for the current operating system, explicitly using + bundled dependencies in top-level `vendor` folder + - `CGO_ENABLED=1 go build -mod=vendor ./cmd/check_mysql2sqlite/` + - `CGO_ENABLED=1 go build -mod=vendor ./cmd/mysql2sqlite/` + - for all supported platforms (where `make` is installed) + - `make all` + - statically linked + - for all supported platforms + - `make all-static` + - `make docker` + - requires that you have a working Docker installation first + - links against `musl libc` (smaller) instead of `glibc` (more + common, default option for dynamic linkage) + - for just windows + - `make windows-static` + - for just linux + - `make linux-static` + - run `make` without options to see the full list of supported "recipes" 1. Locate the newly compiled binaries from the applicable `/tmp` subdirectory path. - if using `Makefile` - look in `/tmp/mysql2sqlite/release_assets/check_mysql2sqlite/` - look in `/tmp/mysql2sqlite/release_assets/mysql2sqlite/` - - if using `go build` + - if using `go build` (with options provided earlier) - look in `/tmp/mysql2sqlite/` See the [deploy](deploy.md) doc for instructions for how to deploy the newly diff --git a/docs/references.md b/docs/references.md index e538dbdb..35eec43c 100644 --- a/docs/references.md +++ b/docs/references.md @@ -46,6 +46,10 @@ absolutely essential) while developing this application. - - +- Docker + - + - provides base images that the `atc0005/go-ci` images build upon + - Libraries/packages - Configuration - @@ -85,3 +89,5 @@ absolutely essential) while developing this application. ### Related projects - +- + - provides Docker images used for musl libc static builds via `Makefile`