diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ee8e889..0000000 --- a/Dockerfile +++ /dev/null @@ -1,135 +0,0 @@ -# Go CGO cross compiler -# Copyright (c) 2014 Péter Szilágyi. All rights reserved. -# -# Released under the MIT license. - -FROM ubuntu:14.04 - -MAINTAINER Péter Szilágyi - -# Create a small script to download binaries and validate their checksum -ENV FETCH ./fetch.sh -RUN \ - echo '#!/bin/bash' > $FETCH && \ - echo 'set -e' >> $FETCH && \ - echo 'file=`basename $1`' >> $FETCH && \ - echo 'echo "Downloading $1..."' >> $FETCH && \ - echo 'wget -q $1' >> $FETCH && \ - echo 'echo "$2 $file" > $file.sum' >> $FETCH && \ - echo 'sha1sum -c $file.sum' >> $FETCH && \ - echo 'rm $file.sum' >> $FETCH && \ - chmod +x $FETCH - - -# Make sure apt-get is up to date and dependent packages are installed -RUN \ - apt-get update && \ - apt-get install -y automake autogen build-essential ca-certificates \ - gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-multilib gcc-mingw-w64 \ - clang llvm-dev libtool libxml2-dev uuid-dev libssl-dev patch make \ - xz-utils cpio wget unzip git mercurial --no-install-recommends - -# Configure the container for OSX cross compilation -ENV OSX_SDK_PATH https://github.com/trevd/android_platform_build2/raw/master/osxsdks10.6.tar.gz -ENV OSX_SDK MacOSX10.6.sdk - -RUN \ - git clone https://github.com/tpoechtrager/osxcross.git && \ - sed -i.bak s/read/#read/g /osxcross/build.sh && \ - \ - $FETCH $OSX_SDK_PATH f526b4ae9806e8d31e3b094e3f004f8f160a3fad && \ - tar -xzf `basename $OSX_SDK_PATH` --strip-components 1 SDKs/$OSX_SDK && \ - tar -cjf /osxcross/tarballs/$OSX_SDK.tar.bz2 $OSX_SDK && \ - rm -rf `basename $OSX_SDK_PATH` $OSX_SDK && \ - \ - /osxcross/build.sh - - -# Define the Go packages for each platform -ENV DIST_LINUX_64 http://golang.org/dl/go1.3.linux-amd64.tar.gz -ENV DIST_LINUX_32 http://golang.org/dl/go1.3.linux-386.tar.gz -ENV DIST_LINUX_ARM http://dave.cheney.net/paste/go.1.3.linux-arm~armv5-1.tar.gz -ENV DIST_OSX_64 http://golang.org/dl/go1.3.darwin-amd64-osx10.6.tar.gz -ENV DIST_OSX_32 http://golang.org/dl/go1.3.darwin-386-osx10.6.tar.gz -ENV DIST_WIN_64 http://golang.org/dl/go1.3.windows-amd64.zip -ENV DIST_WIN_32 http://golang.org/dl/go1.3.windows-386.zip - -# Download all the Go packages, install the core Linux package, inject and -# bootstrap the others -RUN \ - $FETCH $DIST_LINUX_64 b6b154933039987056ac307e20c25fa508a06ba6 && \ - $FETCH $DIST_LINUX_32 22db33b0c4e242ed18a77b03a60582f8014fd8a6 && \ - $FETCH $DIST_LINUX_ARM fc059c970a059757778b157b1140a3c56eb1a069 && \ - $FETCH $DIST_OSX_64 82ffcfb7962ca7114a1ee0a96cac51c53061ea05 && \ - $FETCH $DIST_OSX_32 159d2797bee603a80b829c4404c1fb2ee089cc00 && \ - $FETCH $DIST_WIN_64 1e4888e1494aed7f6934acb5c4a1ffb0e9a022b1 && \ - $FETCH $DIST_WIN_32 e4e5279ce7d8cafdf210a522a70677d5b9c7589d && \ - \ - tar -C /usr/local -xzf `basename $DIST_LINUX_64` && \ - rm -rf /usr/local/go/pkg/linux_amd64_race && \ - \ - tar -C /usr/local --wildcards -xzf `basename $DIST_LINUX_32` go/pkg/linux_386 && \ - GOOS=linux GOARCH=386 /usr/local/go/pkg/tool/linux_amd64/dist bootstrap && \ - tar -C /usr/local --wildcards -xzf `basename $DIST_LINUX_ARM` go/pkg/linux_arm && \ - GOOS=linux GOARCH=arm /usr/local/go/pkg/tool/linux_amd64/dist bootstrap && \ - \ - tar -C /usr/local --wildcards -xzf `basename $DIST_OSX_64` go/pkg/darwin_amd64 && \ - GOOS=darwin GOARCH=amd64 /usr/local/go/pkg/tool/linux_amd64/dist bootstrap && \ - tar -C /usr/local --wildcards -xzf `basename $DIST_OSX_32` go/pkg/darwin_386 && \ - GOOS=darwin GOARCH=386 /usr/local/go/pkg/tool/linux_amd64/dist bootstrap && \ - \ - unzip -d /usr/local -q `basename $DIST_WIN_64` go/pkg/windows_amd64/* && \ - GOOS=windows GOARCH=amd64 /usr/local/go/pkg/tool/linux_amd64/dist bootstrap && \ - unzip -d /usr/local -q `basename $DIST_WIN_32` go/pkg/windows_386/* && \ - GOOS=windows GOARCH=386 /usr/local/go/pkg/tool/linux_amd64/dist bootstrap && \ - \ - rm -f `basename $DIST_LINUX_64` `basename $DIST_LINUX_32` `basename $DIST_LINUX_ARM` \ - `basename $DIST_OSX_64` `basename $DIST_OSX_32` `basename $DIST_WIN_64` `basename $DIST_WIN_32` - -ENV PATH /usr/local/go/bin:$PATH -ENV GOPATH /go - - -# Create a small script to go get a package and cross compile it -ENV BUILD ./build.sh -RUN \ - echo '#!/bin/bash' > $BUILD && \ - echo 'set -e' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Fetching $1...' >> $BUILD && \ - echo 'go get $1' >> $BUILD && \ - echo 'cd $GOPATH/src/$1' >> $BUILD && \ - echo 'pack=`basename $1`' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Compiling for linux/amd64...' >> $BUILD && \ - echo 'GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o $pack-linux-amd64' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Compiling for linux/386...' >> $BUILD && \ - echo 'GOOS=linux GOARCH=386 CGO_ENABLED=1 go build -o $pack-linux-386' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Compiling for linux/arm...' >> $BUILD && \ - echo 'CC=arm-linux-gnueabi-gcc \\' >> $BUILD && \ - echo ' GOOS=linux GOARCH=arm CGO_ENABLED=1 go build -o $pack-linux-arm' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Compiling for windows/amd64...' >> $BUILD && \ - echo 'CC=x86_64-w64-mingw32-gcc \\' >> $BUILD && \ - echo ' GOOS=windows GOARCH=amd64 CGO_ENABLED=1 go build -o $pack-windows-amd64.exe' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Compiling for windows/386...' >> $BUILD && \ - echo 'CC=i686-w64-mingw32-gcc \\' >> $BUILD && \ - echo ' GOOS=windows GOARCH=386 CGO_ENABLED=1 go build -o $pack-windows-386.exe' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Compiling for darwin/amd64...' >> $BUILD && \ - echo '`/osxcross/target/bin/osxcross-env`' >> $BUILD && \ - echo 'CC=o64-clang \\' >> $BUILD && \ - echo ' GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -o $pack-darwin-amd64' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Compiling for darwin/386...' >> $BUILD && \ - echo 'CC=o32-clang \\' >> $BUILD && \ - echo ' GOOS=darwin GOARCH=386 CGO_ENABLED=1 go build -o $pack-darwin-386' >> $BUILD && \ - echo >> $BUILD && \ - echo 'echo Moving binaries to host...' >> $BUILD && \ - echo 'cp `ls -t | head -n 7` /build' >> $BUILD && \ - chmod +x $BUILD - -ENTRYPOINT ["./build.sh"] diff --git a/README.md b/README.md index 0bba87a..5ccea14 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -xgo - Go CGO cross compiler -=========================== +# xgo - Go CGO cross compiler Although Go strives to be a cross platform language, cross compilation from one platform to another is not as simple as it could be, as you need the Go sources @@ -26,8 +25,7 @@ libraries. This becomes very annoying when you need access only to some trivial OS specific functionality (e.g. query the CPU load), but need to configure and maintain separate build environments to do it. -Enter xgo ---------- +## Enter xgo My solution to the challenge of cross compiling Go code with embedded C snippets (i.e. CGO_ENABLED=1) is based on the concept of [lightweight Linux containers](http://en.wikipedia.org/wiki/LXC). @@ -35,21 +33,19 @@ All the necessary Go tool-chains, C cross compilers and platform headers/librari have been assembled into a single Docker container, which can then be called as if a single command to compile a Go package to various platforms and architectures. -Installation ------------- +## Installation Although you could build the container manually, it is available as an automatic -trusted build from Docker's container registry (67MB base + 472MB xgo): +trusted build from Docker's container registry (~530MB): - docker pull karalabe/xgo + docker pull karalabe/xgo-latest To prevent having to remember a potentially complex Docker command every time, a lightweight Go wrapper was written on top of it. go get github.com/karalabe/xgo -Usage ------ +## Usage Simply specify the import path you want to build, and xgo will do the rest: @@ -64,3 +60,25 @@ Simply specify the import path you want to build, and xgo will do the rest: -rwxr-xr-x 1 root root 4151688 Aug 7 10:01 iris-linux-arm -rwxr-xr-x 1 root root 4228608 Aug 7 10:01 iris-windows-386.exe -rwxr-xr-x 1 root root 5243904 Aug 7 10:01 iris-windows-amd64.exe + +### Go releases + +As newer versions of the language runtime, libraries and tools get released, +these will get incorporated into xgo too as extensions layers to the base cross +compilation image (only Go 1.3 and above will be supported). + +You can select which Go release to work with through the `-go` command line flag +to xgo and if the specific release was already integrated, it will automatically +be retrieved and installed. + + $ xgo -go 1.3.0 github.com/project-iris/iris + ... + +Since xgo depends on not only the official releases, but also on Dave Cheney's +ARM packages, there will be a slight delay between official Go updates and the +xgo updates. + +Additionally, a few wildcard release strings are also supported: + + - `-go latest` will use the latest Go release + - `-go 1.3.x` will use the latest point release of a specific Go version diff --git a/xgo.go b/xgo.go index c745a91..b72a2b8 100644 --- a/xgo.go +++ b/xgo.go @@ -7,6 +7,8 @@ package main import ( + "bytes" + "flag" "fmt" "io" "log" @@ -14,31 +16,81 @@ import ( "os/exec" ) -// Docker container configured for cross compilation. -var container = "karalabe/xgo" +// Cross compilation docker containers +var dockerBase = "karalabe/xgo-base" +var dockerDist = "karalabe/xgo-" + +// Command line arguments to fine tune the compilation +var goVersion = flag.String("go", "latest", "Go release to use for cross compilation") func main() { - // Make sure docker is actually available on the system - fmt.Println("Checking docker installation...") - if err := run(exec.Command("docker", "version")); err != nil { + flag.Parse() + + // Ensure docker is available + if err := checkDocker(); err != nil { log.Fatalf("Failed to check docker installation: %v.", err) } - fmt.Println() - - // Fetch and configure the compilation settings - if len(os.Args) != 2 { - log.Fatalf("Usage: %s ", os.Args[0]) + // Validate the command line arguments + if len(flag.Args()) != 1 { + log.Fatalf("Usage: %s [options] ", os.Args[0]) } - path := os.Args[1] - pwd, err := os.Getwd() + // Check that all required images are available + found, err := checkDockerImage(dockerDist + *goVersion) + switch { + case err != nil: + log.Fatalf("Failed to check docker image availability: %v.", err) + case !found: + fmt.Println("not found!") + if err := pullDockerImage(dockerDist + *goVersion); err != nil { + log.Fatalf("Failed to pull docker image from the registry: %v.", err) + } + default: + fmt.Println("found.") + } + // Cross compile the requested package into the local folder + if err := compile(flag.Args()[0]); err != nil { + log.Fatalf("Failed to cross compile package: %v.", err) + } +} + +// Checks whether a docker installation can be found and is functional. +func checkDocker() error { + fmt.Println("Checking docker installation...") + if err := run(exec.Command("docker", "version")); err != nil { + return err + } + fmt.Println() + return nil +} + +// Checks whether a required docker image is available locally. +func checkDockerImage(image string) (bool, error) { + fmt.Printf("Checking for required docker image %s... ", image) + out, err := exec.Command("docker", "images", "--no-trunc").Output() + if err != nil { + return false, err + } + return bytes.Contains(out, []byte(image)), nil +} + +// Pulls an image from the docker registry. +func pullDockerImage(image string) error { + fmt.Printf("Pulling %s from docker registry...\n", image) + fmt.Printf("Note, this may take some time, but due to a docker bug, progress cannot be displayed.\n") + return run(exec.Command("docker", "pull", image)) +} + +// Cross compiles a requested package into the current working directory. +func compile(path string) error { + folder, err := os.Getwd() if err != nil { log.Fatalf("Failed to retrieve the working directory: %v.", err) } - // Cross compile the requested package into the local folder fmt.Printf("Cross compiling %s...\n", path) - if err := run(exec.Command("docker", "run", "-v", pwd+":/build", container, path)); err != nil { - log.Fatalf("Failed to cross compile package: %v.", err) + if err := run(exec.Command("docker", "run", "-v", folder+":/build", dockerDist+*goVersion, path)); err != nil { + return err } + return nil } // Executes a command synchronously, redirecting its output to stdout.