blob: e5be80d559b7c4cd487808429c8070d95b2d4eb5 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Common ssh_client util code."""
from __future__ import print_function
import argparse
import logging
import multiprocessing
import os
import re
import shutil
import sys
BIN_DIR = os.path.dirname(os.path.realpath(__file__))
DIR = os.path.dirname(BIN_DIR)
LIBAPPS_DIR = os.path.dirname(DIR)
sys.path.insert(0, os.path.join(LIBAPPS_DIR, 'libdot', 'bin'))
import libdot # pylint: disable=wrong-import-position
# The top output directory. Everything lives under this.
OUTPUT = os.path.join(DIR, 'output')
# Where archives are cached for fetching & unpacking.
DISTDIR = os.path.join(OUTPUT, 'distfiles')
# All package builds happen under this path.
BUILDDIR = os.path.join(OUTPUT, 'build')
# Directory to put build-time tools.
BUILD_BINDIR = os.path.join(OUTPUT, 'bin')
# Some tools like to scribble in $HOME.
HOME = os.path.join(OUTPUT, 'home')
# Where we save shared libs and headers.
SYSROOT = os.path.join(OUTPUT, 'sysroot')
# Base path to our source mirror.
SRC_URI_MIRROR = ('https://commondatastorage.googleapis.com/'
'chromeos-localmirror/secureshell')
# Number of jobs for parallel operations.
JOBS = multiprocessing.cpu_count()
# Help simplify the API for users of ssh_client.py.
run = libdot.run
symlink = libdot.symlink
touch = libdot.touch
unlink = libdot.unlink
def copy(source, dest):
"""Always copy |source| to |dest|."""
logging.info('Copying %s -> %s', source, dest)
os.makedirs(os.path.dirname(dest), exist_ok=True)
shutil.copy(source, dest)
def emake(*args, **kwargs):
"""Run `make` with |args| and automatic -j."""
jobs = kwargs.pop('jobs', JOBS)
run(['make', '-j%s' % (jobs,)] + list(args), **kwargs)
def fetch(uri=None, name=None):
"""Download |uri| into DISTDIR as |name|."""
if uri is None:
uri = os.path.join(SRC_URI_MIRROR, name)
if name is None:
name = os.path.basename(uri)
libdot.fetch(uri, os.path.join(DISTDIR, name))
def stamp_name(workdir, phase, unique):
"""Get a unique name for this particular step.
This is useful for checking whether certain steps have finished (and thus
have been given a completion "stamp").
Args:
workdir: The package-unique work directory.
phase: The phase we're in e.g. "unpack" or "prepare".
unique: A unique name for the step we're checking in this phase.
Returns:
The full file path to the stamp file.
"""
return os.path.join(workdir, '.stamp.%s.%s' % (phase, unique))
def unpack(archive, cwd=None, workdir=None):
"""Unpack |archive| into |cwd|."""
distfile = os.path.join(DISTDIR, archive)
stamp = stamp_name(workdir, 'unpack', os.path.basename(archive))
if workdir and os.path.exists(stamp):
logging.info('Archive already unpacked: %s', archive)
else:
libdot.unpack(distfile, cwd=workdir or cwd)
touch(stamp)
def parse_metadata(metadata):
"""Turn the |metadata| file into a dict."""
ret = {}
re_field = re.compile(r'^(name|version): "(.*)"')
with open(metadata, 'r') as f:
for line in f:
line = line.strip()
m = re_field.match(line)
if m:
ret[m.group(1)] = m.group(2)
return ret
def pnacl_env():
"""Get custom env to build using PNaCl toolchain."""
env = os.environ.copy()
nacl_sdk_root = os.path.join(OUTPUT, 'naclsdk')
toolchain_root = os.path.join(nacl_sdk_root, 'toolchain', 'linux_pnacl')
bin_dir = os.path.join(toolchain_root, 'bin')
compiler_prefix = os.path.join(bin_dir, 'pnacl-')
sysroot = os.path.join(toolchain_root, 'le32-nacl')
sysroot_incdir = os.path.join(sysroot, 'usr', 'include')
sysroot_libdir = os.path.join(sysroot, 'usr', 'lib')
pkgconfig_dir = os.path.join(sysroot_libdir, 'pkgconfig')
env.update({
'NACL_ARCH': 'pnacl',
'NACL_SDK_ROOT': nacl_sdk_root,
'PATH': '%s:%s' % (bin_dir, env['PATH']),
'CC': compiler_prefix + 'clang',
'CXX': compiler_prefix + 'clang++',
'AR': compiler_prefix + 'ar',
'RANLIB': compiler_prefix + 'ranlib',
'STRIP': compiler_prefix + 'strip',
'PKG_CONFIG_PATH': pkgconfig_dir,
'PKG_CONFIG_LIBDIR': sysroot_libdir,
'SYSROOT': sysroot,
'SYSROOT_INCDIR': sysroot_incdir,
'SYSROOT_LIBDIR': sysroot_libdir,
'CPPFLAGS': '-I%s' % (os.path.join(nacl_sdk_root, 'include'),),
'LDFLAGS': '-L%s' % (os.path.join(nacl_sdk_root, 'lib', 'pnacl',
'Release'),),
})
return env
def default_src_unpack(metadata):
"""Default src_unpack phase."""
for archive in metadata['archives']:
name = archive % metadata
fetch(name=name)
unpack(name, workdir=metadata['workdir'])
def default_src_prepare(metadata):
"""Default src_prepare phase."""
filesdir = metadata['filesdir']
workdir = metadata['workdir']
for patch in metadata['patches']:
patch = patch % metadata
name = os.path.basename(patch)
stamp = stamp_name(workdir, 'prepare', name)
if os.path.exists(stamp):
logging.info('Patch already applied: %s', name)
else:
patch = os.path.join(filesdir, patch)
logging.info('Applying patch %s', name)
run(['patch', '-p1'], stdin=open(patch, 'rb'))
touch(stamp)
def default_src_configure(_metadata):
"""Default src_configure phase."""
def default_src_compile(_metadata):
"""Default src_compile phase."""
if os.path.exists('Makefile'):
emake()
def default_src_install(_metadata):
"""Default src_install phase."""
def get_parser(desc):
"""Get a command line parser."""
parser = argparse.ArgumentParser(description=desc)
parser.add_argument('-d', '--debug', action='store_true',
help='Run with debug output.')
parser.add_argument('-j', '--jobs', type=int,
help='Number of jobs to use in parallel.')
return parser
def build_package(module):
"""Build the package in the |module|.
The file system layout is:
output/ OUTPUT
build/ BUILDDIR
mandoc-1.14.3/ metadata['basedir']
work/ metadata['workdir']
$p/ metadata['S']
temp/ metadata['T']
"""
parser = get_parser(module.__doc__)
opts = parser.parse_args()
libdot.setup_logging(debug=opts.debug)
if opts.jobs:
global JOBS # pylint: disable=global-statement
JOBS = opts.jobs
# Create a metadata object from the METADATA file and other settings.
# This object will be used throughout the build to pass around vars.
filesdir = getattr(module, 'FILESDIR')
metadata_file = os.path.join(filesdir, 'METADATA')
metadata = parse_metadata(metadata_file)
metadata.update({
'P': '%(name)s-%(version)s' % metadata,
'PN': '%(name)s' % metadata,
'PV': '%(version)s' % metadata,
})
metadata.update({
'p': metadata['P'].lower(),
'pn': metadata['PN'].lower(),
})
# All package-specific build state is under this directory.
basedir = os.path.join(BUILDDIR, metadata['p'])
workdir = os.path.join(basedir, 'work')
# Package-specific source directory with all the source.
sourcedir = getattr(module, 'S', os.path.join(workdir, metadata['p']))
# Package-specific temp directory.
tempdir = os.path.join(basedir, 'temp')
metadata.update({
'archives': getattr(module, 'ARCHIVES', ()),
'patches': getattr(module, 'PATCHES', ()),
'filesdir': filesdir,
'workdir': workdir,
'T': tempdir,
})
metadata.update({
'S': sourcedir % metadata,
})
sourcedir = metadata['S']
for path in (tempdir, workdir, BUILD_BINDIR, HOME):
os.makedirs(path, exist_ok=True)
os.environ['HOME'] = HOME
os.environ['PATH'] = '%s:%s' % (BUILD_BINDIR, os.environ['PATH'])
# Run all the source phases now to build it.
common_module = sys.modules[__name__]
def run_phase(phase, cwd):
"""Run this single source phase."""
logging.info('>>> %s: Running phase %s', metadata['P'], phase)
func = getattr(module, phase,
getattr(common_module, 'default_%s' % phase))
os.chdir(cwd)
func(metadata)
run_phase('src_unpack', workdir)
run_phase('src_prepare', sourcedir)
run_phase('src_configure', sourcedir)
run_phase('src_compile', sourcedir)
run_phase('src_install', sourcedir)