Skip to content
Snippets Groups Projects
build-binary.py 7.15 KiB
Newer Older
#!/usr/bin/env python3
import os
from glob import glob
from subprocess import check_call

from utils import get_build_versions, ex, get_sha256, zip_files, get_final_file_name, \
    get_sources_file_name, get_pom_file_name, reset_time, get_version_number, check_go_version, \
    get_version_and_tool, GO_PATH, GO_ROOT, NDK_DIR


def main():
    # get version from command line or show usage information
    tool, command_line_version = get_version_and_tool()

    # Get the latest versions for building
    tool_version, versions = get_build_versions(tool, command_line_version)
    print("Building %s %s" % (tool, tool_version))

    # Install Go
    install_go(tool, tool_version, versions)

    # Install Android NDK
    install_android_ndk(tool, tool_version)

    # Checkout source at specific version
    checkout_source_repo(tool, versions)

    # Build and package for various platforms and architectures
    build_android(tool, versions)
    package_android(tool, versions)

    build_linux(tool, versions)
    package_linux(tool, versions)

    build_windows(tool, versions)
    package_windows(tool, versions)


    # This needs to be always the same path, otherwise it breaks reproducibility
    return '/tmp/%s' % versions['repo_dir']


def install_go(tool, tool_version, versions):
    ex(['./install-go.py', tool, tool_version])
    go_bin_path = os.path.join(GO_ROOT, 'bin')
    os.environ['GOPATH'] = GO_PATH
    os.environ['PATH'] = go_bin_path + os.pathsep + os.getenv('PATH')
    os.environ['GO111MODULE'] = 'on'
    check_go_version(versions)


def install_android_ndk(tool, tool_version):
    ex(['./install-android-ndk.py', tool, tool_version])
    os.environ['ANDROID_NDK_HOME'] = os.path.abspath(NDK_DIR)


def checkout_source_repo(tool, versions):
    repo_dir = get_repo_dir(versions)
    if os.path.isdir(repo_dir):
        # get latest commits and tags from remote
        check_call(['git', 'fetch', 'origin'], cwd=repo_dir)
    else:
        # clone repo
        check_call(['git', 'clone', versions['repo_url'], repo_dir])

    # checkout version
    print("Checking out %s" % versions['revision'])
    check_call(['git', 'checkout', '-f', versions['revision']], cwd=repo_dir)

    # undo all changes
    check_call(['git', 'reset', '--hard'], cwd=repo_dir)

    # clean all untracked files and directories (-d) from repo
    check_call(['git', 'clean', '-dffx'], cwd=repo_dir)


def build_android(tool, versions):
    env = os.environ.copy()
    env['GOARCH'] = "arm"
    env['GOARM'] = "7"
    build_android_arch(tool, versions, env, "arm-linux-androideabi", "arm")

    env = os.environ.copy()
    env['GOARCH'] = "arm64"
    build_android_arch(tool, versions, env, "aarch64-linux-android", "arm64")

    env = os.environ.copy()
    env['GOARCH'] = "386"
    build_android_arch(tool, versions, env, "i686-linux-android", "x86")

    env = os.environ.copy()
    env['GOARCH'] = "amd64"
    build_android_arch(tool, versions, env, "x86_64-linux-android", "x86_64")


def build_android_arch(tool, versions, env, clang_arch, ndk_arch):
    toolchain = os.path.join("toolchain", ndk_arch)
    if not os.path.isdir(toolchain):
        toolchain_maker = os.path.join(NDK_DIR, "build", "tools", "make-standalone-toolchain.sh")
        ex([toolchain_maker, "--arch=%s" % ndk_arch, "--install-dir=%s" % toolchain])

    env['CC'] = "%s/bin/%s-clang" % (os.path.abspath(toolchain), clang_arch)
    env['CGO_ENABLED'] = "1"
    env['CGO_CFLAGS'] = "-O2"  # removes -g
    env['GOOS'] = "android"

    build_mode = "pie"
    extldflags = " -extldflags=-pie"

    filename = "%s_%s_pie.zip" % (tool, ndk_arch)
    print("Building %s" % filename)

    output_file = os.path.abspath(os.path.join(os.path.curdir, tool))
    go_flags = ['-asmflags', '-trimpath', '-o', output_file]
    repo_dir = get_repo_dir(versions)
    ex(['go', 'build', '-buildmode=%s' % build_mode, '-ldflags', '-w -s' + extldflags] + go_flags +
       [os.path.join('.', versions['build_path'])], env=env, cwd=repo_dir)

    zip_files([tool], filename, versions)
    os.remove(tool)


def build_linux(tool, versions):
    build_desktop_arch(tool, versions, 'linux', 'armhf', 'arm', '7')
    build_desktop_arch(tool, versions, 'linux', 'aarch64', 'arm64')
    build_desktop_arch(tool, versions, 'linux', 'x86_64', 'amd64')


def build_windows(tool, versions):
    build_desktop_arch(tool, versions, 'windows', 'x86_64', 'amd64')


def build_desktop_arch(tool, versions, goos, arch, goarch, goarm=None):
    env = os.environ.copy()
    env['CGO_ENABLED'] = "0"
    env['GOOS'] = goos
    env['GOARCH'] = goarch
    if goarm: env['GOARM'] = goarm
    build_path = os.path.join('.', versions['build_path'])
    filename = "%s_%s-%s.zip" % (tool, goos, arch)
    print("Building %s" % filename)
    output_file = os.path.abspath(os.path.join(os.path.curdir, tool))
    go_flags = ['-asmflags', '-trimpath', '-o', output_file]
    repo_dir = get_repo_dir(versions)
    ex(['go', 'build', '-ldflags', '-w -s'] + go_flags + [build_path], env=env, cwd=repo_dir)
    zip_files([tool], filename, versions)
    os.remove(tool)


def package_android(tool, versions):
    file_list = [
        '%s_arm_pie.zip' % tool,
        '%s_arm64_pie.zip' % tool,
        '%s_x86_pie.zip' % tool,
        '%s_x86_64_pie.zip' % tool
    ]
    package(tool, versions, file_list, 'android')


def package_linux(tool, versions):
    file_list = [
        '%s_linux-armhf.zip' % tool,
        '%s_linux-aarch64.zip' % tool,
        '%s_linux-x86_64.zip' % tool
    ]
    package(tool, versions, file_list, 'linux')


def package_windows(tool, versions):
    file_list = ['%s_windows-x86_64.zip' % tool]
    package(tool, versions, file_list, 'windows')


def package(tool, versions, file_list, platform):
    zip_file = get_final_file_name(tool, versions, platform)
    zip_files(file_list, zip_file, versions)
    create_sources_jar(tool, versions, platform)
    create_pom_file(tool, versions, platform)

    # print hashes for debug purposes
    for file in file_list + [zip_file]:
        sha256hash = get_sha256(file)
        prefix = '->' if file == zip_file else ''
        print("%s %s: %s" % (prefix, file, sha256hash))


def create_sources_jar(tool, versions, platform):
    repo_dir = get_repo_dir(versions)
    # clean all untracked files and directories (-d) from repo
    check_call(['git', 'clean', '-dffx'], cwd=repo_dir)
    # vendorize dependencies
    ex(['go', 'mod', 'vendor'], cwd=repo_dir)
    jar_files = []
    for file in glob(os.path.join(repo_dir, '*')):
        reset_time(file, versions)
        jar_files.append(os.path.relpath(file, repo_dir))
    jar_file = get_sources_file_name(tool, versions, platform)
    jar_path = os.path.abspath(jar_file)
    check_call(['jar', 'cf', jar_path] + jar_files, cwd=repo_dir)
    return jar_file


def create_pom_file(tool, versions, platform):
    tool_version = get_version_number(versions)
    pom_file = get_pom_file_name(tool, versions, platform)
    template = 'template-%s-%s.pom' % (tool, platform)
    with open(template, 'rt') as infile:
        with open(pom_file, 'wt') as outfile:
            for line in infile:
                outfile.write(line.replace('VERSION', tool_version))
    return pom_file


if __name__ == "__main__":
    main()