| Index: ios/crnet/build.py
|
| diff --git a/ios/crnet/build.py b/ios/crnet/build.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..12874bf43bee59b0963ec34b40db4813364f1f15
|
| --- /dev/null
|
| +++ b/ios/crnet/build.py
|
| @@ -0,0 +1,322 @@
|
| +#!/usr/bin/env python
|
| +# Copyright 2014 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +import argparse
|
| +import os
|
| +import shutil
|
| +import subprocess
|
| +import sys
|
| +import tempfile
|
| +import time
|
| +
|
| +
|
| +SUPPORTED_ARCHES = ['i386', 'x86_64', 'armv7', 'arm64']
|
| +
|
| +
|
| +class SubprocessError(Exception):
|
| + pass
|
| +
|
| +
|
| +class ConfigurationError(Exception):
|
| + pass
|
| +
|
| +
|
| +def out_directories(root):
|
| + """Returns all output directories containing crnet objects under root.
|
| +
|
| + Currently this list is just hardcoded.
|
| +
|
| + Args:
|
| + root: prefix for output directories.
|
| + """
|
| + out_dirs = ['Release-iphoneos', 'Release-iphonesimulator']
|
| + return map(lambda x: os.path.join(root, 'out', x), out_dirs)
|
| +
|
| +
|
| +def check_command(command):
|
| + """Runs a command, raising an exception if it fails.
|
| +
|
| + If the command returns a nonzero exit code, prints any data the command
|
| + emitted on stdout and stderr.
|
| +
|
| + Args:
|
| + command: command to execute, in argv format.
|
| +
|
| + Raises:
|
| + SubprocessError: the specified command returned nonzero exit status.
|
| + """
|
| + p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| + (stdout, stderr) = p.communicate()
|
| + if p.returncode == 0:
|
| + return
|
| + message = 'Command failed: {0} (status {1})'.format(command, p.returncode)
|
| + print message
|
| + print 'stdout: {0}'.format(stdout)
|
| + print 'stderr: {0}'.format(stderr)
|
| + raise SubprocessError(message)
|
| +
|
| +
|
| +def file_contains_string(path, string):
|
| + """Returns whether the file named by path contains string.
|
| +
|
| + Args:
|
| + path: path of the file to search.
|
| + string: string to search the file for.
|
| +
|
| + Returns:
|
| + True if file contains string, False otherwise.
|
| + """
|
| + with open(path, 'r') as f:
|
| + for line in f:
|
| + if string in line:
|
| + return True
|
| + return False
|
| +
|
| +
|
| +def is_object_filename(filename):
|
| + """Returns whether the given filename names an object file.
|
| +
|
| + Args:
|
| + filename: filename to inspect.
|
| +
|
| + Returns:
|
| + True if filename names an object file, false otherwise.
|
| + """
|
| + (_, ext) = os.path.splitext(filename)
|
| + return ext in ('.a', '.o')
|
| +
|
| +
|
| +class Step(object):
|
| + """Represents a single step of the crnet build process.
|
| +
|
| + This parent class exists only to define the interface Steps present and keep
|
| + track of elapsed time for each step. Subclasses of Step should override the
|
| + run() method, which is called internally by start().
|
| +
|
| + Attributes:
|
| + name: human-readable name of this step, used in debug output.
|
| + started_at: seconds since epoch that this step started running at.
|
| + """
|
| + def __init__(self, name):
|
| + self._name = name
|
| + self._started_at = None
|
| + self._ended_at = None
|
| +
|
| + @property
|
| + def name(self):
|
| + return self._name
|
| +
|
| + def start(self):
|
| + """Start running this step.
|
| +
|
| + This method keeps track of how long the run() method takes to run and emits
|
| + the elapsed time after run() returns.
|
| + """
|
| + self._started_at = time.time()
|
| + print '{0}: '.format(self._name),
|
| + sys.stdout.flush()
|
| + self._run()
|
| + self._ended_at = time.time()
|
| + print '{0:.2f}s'.format(self._ended_at - self._started_at)
|
| +
|
| + def _run(self):
|
| + """Actually run this step.
|
| +
|
| + Subclasses should override this method to implement their own step logic.
|
| + """
|
| + raise NotImplementedError
|
| +
|
| +
|
| +class CleanStep(Step):
|
| + """Clean the build output directories.
|
| +
|
| + This step deletes intermediates generated by the build process. Some of these
|
| + intermediates (crnet_consumer.app and crnet_resources.bundle) are directories,
|
| + which contain files ninja doesn't know and hence won't remove, so the run()
|
| + method here explicitly deletes those directories before running 'ninja -t
|
| + clean'.
|
| +
|
| + Attributes:
|
| + dirs: list of output directories to clean.
|
| + """
|
| + def __init__(self, root):
|
| + super(CleanStep, self).__init__('clean')
|
| + self._dirs = out_directories(root)
|
| +
|
| + def _run(self):
|
| + """Runs the clean step.
|
| +
|
| + Deletes crnet_consumer.app and crnet_resources.bundle in each output
|
| + directory and runs 'ninja -t clean' in each output directory.
|
| + """
|
| + for d in self._dirs:
|
| + if os.path.exists(os.path.join(d, 'crnet_consumer.app')):
|
| + shutil.rmtree(os.path.join(d, 'crnet_consumer.app'))
|
| + if os.path.exists(os.path.join(d, 'crnet_resources.bundle')):
|
| + shutil.rmtree(os.path.join(d, 'crnet_resources.bundle'))
|
| + check_command(['ninja', '-C', d, '-t', 'clean'])
|
| +
|
| +
|
| +class HooksStep(Step):
|
| + """Validates the gyp config and reruns gclient hooks.
|
| +
|
| + Attributes:
|
| + root: directory to find gyp config under.
|
| + """
|
| + def __init__(self, root):
|
| + super(HooksStep, self).__init__('hooks')
|
| + self._root = root
|
| +
|
| + def _run(self):
|
| + """Runs the hooks step.
|
| +
|
| + Checks that root/build/common.gypi contains target_subarch = both in a crude
|
| + way, then calls 'gclient runhooks'. TODO(ellyjones): parse common.gypi in a
|
| + more robust way.
|
| +
|
| + Raises:
|
| + ConfigurationError: if target_subarch != both
|
| + """
|
| + common_gypi = os.path.join(self._root, 'build', 'common.gypi')
|
| + if not file_contains_string(common_gypi, "'target_subarch%': 'both'"):
|
| + raise ConfigurationError('target_subarch must be both in {0}'.format(
|
| + common_gypi))
|
| + check_command(['gclient', 'runhooks'])
|
| +
|
| +
|
| +class BuildStep(Step):
|
| + """Builds all the intermediate crnet binaries.
|
| +
|
| + All the hard work of this step is done by ninja; this step just shells out to
|
| + ninja to build the crnet_pack target.
|
| +
|
| + Attributes:
|
| + dirs: output directories to run ninja in.
|
| + """
|
| + def __init__(self, root):
|
| + super(BuildStep, self).__init__('build')
|
| + self._dirs = out_directories(root)
|
| +
|
| + def _run(self):
|
| + """Runs the build step.
|
| +
|
| + For each output directory, run ninja to build the crnet_pack target in that
|
| + directory.
|
| + """
|
| + for d in self._dirs:
|
| + check_command(['ninja', '-C', d, 'crnet_pack'])
|
| +
|
| +
|
| +class PackageStep(Step):
|
| + """Packages the built object files for release.
|
| +
|
| + The release format is a tarball, containing one gzipped tarball per
|
| + architecture and a manifest file, which lists metadata about the build.
|
| +
|
| + Attributes:
|
| + outdirs: directories containing built object files.
|
| + workdir: temporary working directory. Deleted at end of the step.
|
| + archdir: temporary directory under workdir. Used for collecting per-arch
|
| + binaries.
|
| + proddir: temporary directory under workdir. Used for intermediate per-arch
|
| + tarballs.
|
| + """
|
| + def __init__(self, root, outfile):
|
| + super(PackageStep, self).__init__('package')
|
| + self._outdirs = out_directories(root)
|
| + self._outfile = outfile
|
| +
|
| + def _run(self):
|
| + """Runs the package step.
|
| +
|
| + Packages each architecture from |root| into an individual .tar.gz file, then
|
| + packages all the .tar.gz files into one .tar file, which is written to
|
| + |outfile|.
|
| + """
|
| + (workdir, archdir, proddir) = self.create_work_dirs()
|
| + for arch in SUPPORTED_ARCHES:
|
| + self.package_arch(archdir, proddir, arch)
|
| + self.package(proddir)
|
| + shutil.rmtree(workdir)
|
| +
|
| + def create_work_dirs(self):
|
| + """Creates working directories and returns their paths."""
|
| + workdir = tempfile.mkdtemp()
|
| + archdir = os.path.join(workdir, 'arch')
|
| + proddir = os.path.join(workdir, 'prod')
|
| + os.mkdir(archdir)
|
| + os.mkdir(proddir)
|
| + return (workdir, archdir, proddir)
|
| +
|
| + def object_files_for_arch(self, arch):
|
| + """Returns a list of object files for the given architecture.
|
| +
|
| + Under each outdir d, per-arch files are stored in d/arch, and object files
|
| + for a given arch contain the arch's name as a substring.
|
| +
|
| + Args:
|
| + arch: architecture name. Must be in SUPPORTED_ARCHES.
|
| +
|
| + Returns:
|
| + List of full pathnames to object files in outdirs for the named arch.
|
| + """
|
| + arch_files = []
|
| + for d in self._outdirs:
|
| + files = os.listdir(os.path.join(d, 'arch'))
|
| + for f in filter(is_object_filename, files):
|
| + if arch in f:
|
| + arch_files.append(os.path.join(d, 'arch', f))
|
| + return arch_files
|
| +
|
| + def package_arch(self, archdir, proddir, arch):
|
| + """Packages an individual architecture.
|
| +
|
| + Copies all the object files for the specified arch into a working directory
|
| + under self.archdir, then tars them up into a gzipped tarball under
|
| + self.proddir.
|
| +
|
| + Args:
|
| + archdir: directory to stage architecture files in.
|
| + proddir: directory to stage result tarballs in.
|
| + arch: architecture name to package. Must be in SUPPORTED_ARCHES.
|
| + """
|
| + arch_files = self.object_files_for_arch(arch)
|
| + os.mkdir(os.path.join(archdir, arch))
|
| + for f in arch_files:
|
| + shutil.copy(f, os.path.join(archdir, arch))
|
| + out_filename = os.path.join(proddir, '{0}.tar.gz'.format(arch))
|
| + check_command(['tar', '-C', archdir, '-czf', out_filename, arch])
|
| +
|
| + def package(self, proddir):
|
| + """Final packaging step. Packages all the arch tarballs into one tarball."""
|
| + arch_tarballs = []
|
| + for a in SUPPORTED_ARCHES:
|
| + arch_tarballs.append('{0}.tar.gz'.format(a))
|
| + check_command(['tar', '-C', proddir, '-cf', self._outfile] +
|
| + arch_tarballs)
|
| +
|
| +
|
| +def main():
|
| + step_classes = {
|
| + 'clean': lambda: CleanStep(args.rootdir),
|
| + 'hooks': lambda: HooksStep(args.rootdir),
|
| + 'build': lambda: BuildStep(args.rootdir),
|
| + 'package': lambda: PackageStep(args.rootdir, args.outfile)
|
| + }
|
| + parser = argparse.ArgumentParser(description='Build and package crnet.')
|
| + parser.add_argument('--outfile', dest='outfile', default='crnet.tar',
|
| + help='Output file to generate (default: crnet.tar)')
|
| + parser.add_argument('--rootdir', dest='rootdir', default='../..',
|
| + help='Root directory to build from (default: ../..)')
|
| + parser.add_argument('steps', metavar='step', nargs='*')
|
| + args = parser.parse_args()
|
| + step_names = args.steps or ['clean', 'hooks', 'build', 'package']
|
| + steps = [step_classes[x]() for x in step_names]
|
| + for step in steps:
|
| + step.start()
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + main()
|
|
|