Fiw-Gentoo-Dots/tools/build-release.py

82 lines
4 KiB
Python

#!/usr/bin/env python3
"""Build the portable Linux amd64 TUI bundle and retain dependency licences."""
import gzip
import json
import os
import re
import subprocess
import sys
import tempfile
import urllib.request
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO / 'lib'))
from launcher import digest, source_hash
def json_stream(text):
decoder = json.JSONDecoder()
while text.strip():
item, end = decoder.raw_decode(text.lstrip())
yield item
text = text.lstrip()[end:]
def build():
version = (REPO / 'VERSION').read_text().strip()
if not re.fullmatch(r'\d+\.\d+\.\d+', version):
raise ValueError('VERSION must contain a numeric release version.')
env = dict(os.environ, CGO_ENABLED='0', GOOS='linux', GOARCH='amd64', GOAMD64='v1',
GOEXPERIMENT='', GOFLAGS='-mod=readonly', GOTOOLCHAIN='local')
output = REPO / 'assets/bin'
output.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix='fiw-dots-release-') as temp:
executable = Path(temp) / 'fiw-dots'
subprocess.run(['go', 'build', '-trimpath', '-buildvcs=false',
'-ldflags=-s -w -X main.tuiVersion=' + version,
'-o', str(executable), './cmd/dots'], cwd=REPO, env=env, check=True)
data = executable.read_bytes()
archive = gzip.compress(data, mtime=0)
name = 'fiw-dots-linux-amd64.gz'
metadata = {'version': version, 'source_sha256': source_hash(),
'binaries': {'linux-amd64': {'file': name, 'sha256': digest(data),
'gzip_sha256': digest(archive), 'size': len(data)}}}
packages = subprocess.run(['go', 'list', '-deps', '-json', './cmd/dots'], cwd=REPO, env=env,
text=True, capture_output=True, check=True)
modules = {package['Module']['Path']: package['Module'] for package in json_stream(packages.stdout)
if package.get('Module') and not package['Module'].get('Main')}
notices = ['# Third-party licences for the bundled TUI\n']
for _, module in sorted(modules.items()):
directory = Path(module['Dir'])
licences = sorted(path for path in directory.iterdir() if path.is_file()
and path.name.lower().startswith(('license', 'copying', 'notice')))
if not licences:
raise RuntimeError('Missing licence notice for ' + module['Path'])
for path in licences:
notices += ['\n## ' + module['Path'] + ' ' + module['Version'] + ' / ' + path.name + '\n', path.read_text()]
goroot = subprocess.run(['go', 'env', 'GOROOT'], env=env, text=True, capture_output=True, check=True).stdout.strip()
license_path = Path(goroot) / 'LICENSE'
if license_path.is_file():
go_license = license_path.read_text()
else:
# Gentoo omits the upstream licence file from GOROOT. Obtain that
# exact toolchain's notice from the official Go source repository.
goversion = subprocess.run(['go', 'env', 'GOVERSION'], env=env, text=True, capture_output=True, check=True).stdout.strip()
match = re.match(r'go\d+\.\d+(?:\.\d+)?', goversion)
if not match:
raise RuntimeError('Cannot identify the Go toolchain licence source.')
url = 'https://raw.githubusercontent.com/golang/go/' + match.group() + '/LICENSE'
with urllib.request.urlopen(url, timeout=20) as response:
go_license = response.read().decode()
notices += ['\n## Go standard library and runtime\n', go_license]
(output / name).write_bytes(archive)
(output / 'tui.json').write_text(json.dumps(metadata, indent=2) + '\n')
(output / 'SHA256SUMS').write_text(digest(archive) + ' ' + name + '\n')
(output / 'LICENSES.txt').write_text('\n'.join(notices))
print('Built v' + version + ': ' + str(len(data)) + ' bytes; compressed ' + str(len(archive)) + ' bytes.')
print('Source and binary fingerprints: assets/bin/tui.json')
if __name__ == '__main__':
build()