Created
July 25, 2023 00:21
-
-
Save Coronon/bfae90a5299c9a7eb191687d0acc8be6 to your computer and use it in GitHub Desktop.
Easily cross compile and package GO binaries
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import os | |
from pathlib import Path | |
import shutil | |
import subprocess | |
import tempfile | |
from typing import List | |
parser = argparse.ArgumentParser( | |
prog='GoCrossCompile', | |
description='Automatically cross compile and package up go binaries', | |
epilog='©Rubin Raithel - https://rubinraithel.de', | |
) | |
parser.add_argument( | |
'-n', | |
'--name', | |
help = 'Name of the application without any extensions', | |
required = True | |
) | |
parser.add_argument( | |
'-v', | |
'--version', | |
help = 'Version of the application', | |
required = True, | |
) | |
parser.add_argument( | |
'-b', | |
'--builddir', | |
help = 'Directory containing the main package', | |
required = True | |
) | |
parser.add_argument( | |
'-d', | |
'--distdir', | |
help = 'Directory to store built archives in', | |
required = True | |
) | |
parser.add_argument( | |
'-a', | |
'--arch', | |
action = 'append', | |
help = '{GOOS}/{GOARCH} pairs to compile for', | |
) | |
parser.add_argument( | |
'-i', | |
'--include', | |
action = 'append', | |
help = 'Additional files to include', | |
required = False, | |
) | |
def build_binary(name: str, build_dir: Path, dist_dir: Path, goos: str, goarch: str) -> Path: | |
'''Compile a binary at `build_dir` for `{goos}/{goarch}` called `name` | |
Arguments: | |
name {str} -- Name of the binary | |
build_dir {Path} -- Directory of main package to compile | |
goos {str} -- Value for the GOOS environment variable | |
goarch {str} -- Value for the GOARCH environment variable | |
Returns: | |
Path -- Path to the compiled binary | |
''' | |
name_extension = '.exe' if goos == 'windows' else '' | |
binary_path = dist_dir.resolve().joinpath(f'{name}{name_extension}') | |
subprocess.run([ | |
'env', | |
f'GOOS={goos}', | |
f'GOARCH={goarch}', | |
'go', | |
'build', | |
'-ldflags', | |
'-w -s', | |
'-trimpath', | |
'-o', | |
str(binary_path), | |
str(build_dir.resolve()), | |
]) | |
return binary_path | |
def package_binary( | |
name: str, | |
version: str, | |
goos: str, | |
goarch: str, | |
binary_path: Path, | |
includes: List[Path], | |
dist_dir: Path, | |
) -> Path: | |
'''Package up a built binary into a zip archive for distribution | |
Arguments: | |
name {str} -- Name of the built binary (basename for the archive) | |
binary_path {Path} -- Path to the built binary | |
includes {List[Path]} -- Extra files to include in the archive | |
dist_dir {Path} -- Directory to place archive in | |
goos {str} -- GOOS used to built the binary | |
goarch {str} -- GOARCH used to built the binary | |
Returns: | |
Path -- Path to the built archive | |
''' | |
archive_name = f'{name}-{version}-{goos}_{goarch}' | |
archive_format = 'zip' if goos == 'windows' else 'gztar' | |
with tempfile.TemporaryDirectory( | |
f'{goos}_{goarch}', | |
'archive', | |
dist_dir, | |
) as tmp_dir: | |
# Copy files | |
for include in includes: | |
shutil.copyfile(include.resolve(), f'{tmp_dir}/{os.path.basename(include)}') | |
shutil.copyfile(binary_path.resolve(), f'{tmp_dir}/{os.path.basename(binary_path)}') | |
# Make archive | |
archive_path = Path(os.path.join(dist_dir.resolve(), archive_name)) | |
archive = shutil.make_archive(archive_path.resolve(), archive_format, tmp_dir) | |
return Path(archive) | |
def main() -> None: | |
args = parser.parse_args() | |
name = args.name | |
version = args.version | |
build_dir = Path(args.builddir) | |
dist_dir = Path(args.distdir) | |
includes: List[str] = [Path(i) for i in args.include] if args.include is not None else [] | |
# Clear old dist | |
try: | |
shutil.rmtree(dist_dir.resolve()) | |
except: | |
pass | |
# Build binaries and package them | |
arch: str | |
for arch in args.arch: | |
goos, goarch = arch.lower().split('/') | |
print(f'Building {name} for {goos}/{goarch}...') | |
binary_path = build_binary(name, build_dir, dist_dir, goos, goarch) | |
archive_path = package_binary( | |
name, | |
version, | |
goos, | |
goarch, | |
binary_path, | |
includes, | |
dist_dir | |
) | |
os.remove(binary_path.resolve()) | |
print(f'Built {archive_path.resolve()}') | |
print('Done!') | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment