Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(105)

Unified Diff: build/build.py

Issue 2095173002: Teach build.py to cross-compile go-based packages. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Teach build.py to cross-compile go-based packages. Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: build/build.py
diff --git a/build/build.py b/build/build.py
index cb280ab64ad71267c0f068e8838b42c30cb61c93..f5b68312726c060fce097999ebd5009cafe77f81 100755
--- a/build/build.py
+++ b/build/build.py
@@ -7,11 +7,14 @@
invokes CIPD client to package and upload chunks of it to the CIPD repository as
individual packages.
-See build/packages/*.yaml for definition of packages.
+See build/packages/*.yaml for definition of packages and README.md for more
+details.
"""
import argparse
+import contextlib
import glob
+import hashlib
import json
import os
import platform
@@ -42,6 +45,56 @@ class UploadException(Exception):
"""Raised on errors during package upload step."""
+class PackageDef(object):
+ """Represents parsed package *.yaml file."""
+
+ def __init__(self, path, pkg_def):
+ self.path = path
+ self.pkg_def = pkg_def
+
+ @property
+ def name(self):
+ """Returns name of YAML file (without the directory path and extension)."""
+ return os.path.splitext(os.path.basename(self.path))[0]
+
+ @property
+ def uses_python_env(self):
+ """Returns True if 'uses_python_env' in the YAML file is set."""
+ return self.pkg_def.get('uses_python_env')
+
+ @property
+ def go_packages(self):
+ """Returns a list of Go packages that must be installed for this package."""
+ return self.pkg_def.get('go_packages') or []
+
+ def should_build(self, builder):
+ """Returns True if package should be built in the current environment.
+
+ Takes into account 'builders' and 'supports_cross_compilation' properties of
+ package definition file.
+ """
+ # If '--builder' is not specified, ignore 'builders' property. Otherwise, if
+ # 'builders' YAML attribute it not empty, verify --builder is listed there.
+ builders = self.pkg_def.get('builders')
+ if builder and builders and builder not in builders:
Vadim Sh. 2016/06/25 03:51:04 this is existing logic (moved from 'should_process
+ return False
+
+ # If cross compiling, pick only packages that supports cross compilation.
+ if is_cross_compiling():
Vadim Sh. 2016/06/25 03:51:04 this is new
+ return bool(self.pkg_def.get('supports_cross_compilation'))
+
+ return True
+
+
+def is_cross_compiling():
+ """Returns true if using GOOS or GOARCH env vars.
+
+ We also check at the start of the script that if one of them is used, then
+ the other is specified as well.
+ """
+ return bool(os.environ.get('GOOS')) or bool(os.environ.get('GOARCH'))
+
+
def run_python(script, args):
"""Invokes a python script.
@@ -53,11 +106,11 @@ def run_python(script, args):
args=['python', '-u', script] + list(args), executable=sys.executable)
-def run_cipd(go_workspace, cmd, args):
+def run_cipd(cipd_exe, cmd, args):
"""Invokes CIPD, parsing -json-output result.
Args:
- go_workspace: path to 'infra/go' or 'infra_internal/go'.
+ cipd_exe: path to cipd client binary to run.
cmd: cipd subcommand to run.
args: list of command line arguments to pass to the subcommand.
@@ -69,10 +122,7 @@ def run_cipd(go_workspace, cmd, args):
fd, temp_file = tempfile.mkstemp(suffix='.json', prefix='cipd_%s' % cmd)
os.close(fd)
- cmd_line = [
- os.path.join(go_workspace, 'bin', 'cipd' + EXE_SUFFIX),
- cmd, '-json-output', temp_file,
- ] + list(args)
+ cmd_line = [cipd_exe, cmd, '-json-output', temp_file] + list(args)
print 'Running %s' % ' '.join(cmd_line)
exit_code = subprocess.call(args=cmd_line, executable=cmd_line[0])
@@ -101,20 +151,32 @@ def print_title(title):
print '-' * 80
-def build_go(go_workspace, packages):
- """Bootstraps go environment and rebuilds (and installs) Go packages.
+def print_go_step_title(title):
+ """Same as 'print_title', but also appends values of GOOS and GOARCH."""
+ if is_cross_compiling():
+ title += '\nwith'
+ title += '\n GOOS=%s' % (
+ os.environ['GOOS'] if 'GOOS' in os.environ else '<native>')
+ title += '\n GOARCH=%s' % (
+ os.environ['GOARCH'] if 'GOARCH' in os.environ else '<native>')
+ print_title(title)
+
- Compiles and installs packages into default GOBIN, which is <path>/go/bin/
- (it is setup by go/env.py and depends on what workspace is used).
+@contextlib.contextmanager
+def hacked_workspace(go_workspace, goos=None, goarch=None):
Vadim Sh. 2016/06/25 03:51:04 this is mostly existing logic, moved into separate
+ """Symlinks Go workspace into new root, modifies os.environ.
+
+ Go toolchain embeds absolute paths to *.go files into the executable. Use
+ symlink with stable path to make executables independent of checkout path.
Args:
go_workspace: path to 'infra/go' or 'infra_internal/go'.
- packages: list of packages to build (can include '...' patterns).
- """
- print_title('Compiling Go code: %s' % ', '.join(packages))
+ goos: if set, overrides GOOS environment variable (removes it if '').
+ goarch: if set, overrides GOARCH environment variable (removes it if '').
- # Go toolchain embeds absolute paths to *.go files into the executable. Use
- # symlink with stable path to make executables independent of checkout path.
+ Yields:
+ Path where go_workspace is symlinked to.
+ """
new_root = None
new_workspace = go_workspace
if sys.platform != 'win32':
@@ -127,12 +189,41 @@ def build_go(go_workspace, packages):
assert not rel.startswith('..'), rel
new_workspace = os.path.join(new_root, rel)
- # Remove any stale binaries and libraries.
- shutil.rmtree(os.path.join(new_workspace, 'bin'), ignore_errors=True)
- shutil.rmtree(os.path.join(new_workspace, 'pkg'), ignore_errors=True)
+ orig_environ = os.environ.copy()
+
+ if goos is not None:
+ if goos == '':
+ os.environ.pop('GOOS', None)
+ else:
+ os.environ['GOOS'] = goos
+ if goarch is not None:
+ if goarch == '':
+ os.environ.pop('GOARCH', None)
+ else:
+ os.environ['GOARCH'] = goarch
- # Recompile ('-a').
try:
+ yield new_workspace
+ finally:
+ if new_root:
+ os.remove(new_root)
+ os.environ = orig_environ
+
+
+def run_go_install(go_workspace, packages, goos=None, goarch=None):
+ """Rebuilds (and installs) Go packages into GOBIN via 'go install ...'.
+
+ Compiles and installs packages into default GOBIN, which is <go_workspace>/bin
+ (it is setup by go/env.py).
+
+ Args:
+ go_workspace: path to 'infra/go' or 'infra_internal/go'.
+ packages: list of packages to build (can include '...' patterns).
+ goos: if set, overrides GOOS environment variable (removes it if '').
+ goarch: if set, overrides GOARCH environment variable (removes it if '').
+ """
+ with hacked_workspace(go_workspace, goos, goarch) as new_workspace:
+ print_go_step_title('Building:\n %s' % '\n '.join(packages))
subprocess.check_call(
args=[
'python', '-u', os.path.join(new_workspace, 'env.py'),
@@ -140,31 +231,97 @@ def build_go(go_workspace, packages):
] + list(packages),
executable=sys.executable,
stderr=subprocess.STDOUT)
- finally:
- if new_root:
- os.remove(new_root)
-def enumerate_packages_to_build(package_def_dir, package_def_files=None):
- """Returns a list of absolute paths to files in build/packages/*.yaml.
+def run_go_build(go_workspace, package, output, goos=None, goarch=None):
+ """Builds single Go package (rebuilding all its dependencies).
Args:
+ package: package to build.
+ output: where to put the resulting binary.
+ goos: if set, overrides GOOS environment variable (removes it if '').
+ goarch: if set, overrides GOARCH environment variable (removes it if '').
+ """
+ with hacked_workspace(go_workspace, goos, goarch) as new_workspace:
+ print_go_step_title('Building %s' % package)
+ subprocess.check_call(
+ args=[
+ 'python', '-u', os.path.join(new_workspace, 'env.py'),
+ 'go', 'build', '-a', '-v', '-o', output, package,
+ ],
+ executable=sys.executable,
+ stderr=subprocess.STDOUT)
+
+
+def build_go_code(go_workspace, pkg_defs):
+ """Builds and installs all Go packages used by the given PackageDefs.
+
+ Understands GOOS and GOARCH and uses slightly different build strategy when
+ cross-compiling. In the end <go_workspace>/bin will have all built binaries,
+ and only them (regardless of whether we are cross compiling or not).
+
+ Args:
+ go_workspace: path to 'infra/go' or 'infra_internal/go'.
+ pkg_defs: list of PackageDef objects that define what to build.
+ """
+ # Make sure there are no stale files in the workspace.
+ shutil.rmtree(os.path.join(go_workspace, 'bin'), ignore_errors=True)
+ shutil.rmtree(os.path.join(go_workspace, 'pkg'), ignore_errors=True)
+ os.mkdir(os.path.join(go_workspace, 'bin'))
+ os.mkdir(os.path.join(go_workspace, 'pkg'))
+
+ # Grab a set of all go packages we need to build and install into GOBIN.
+ to_install = []
+ for p in pkg_defs:
+ to_install.extend(p.go_packages)
+ to_install = sorted(set(to_install))
+ if not to_install:
+ return
+
+ if not is_cross_compiling():
+ # If not cross compiling, build all Go code in a single "go install" step,
+ # it's faster that way. We can't do that when cross-compiling, since
+ # 'go install' isn't supposed to be used for cross-compilation and the
+ # toolset actively complains with "go install: cannot install cross-compiled
+ # binaries when GOBIN is set".
+ run_go_install(go_workspace, to_install)
+ else:
+ # If cross compiling, build packages one by one and put the resulting
+ # binaries into GOBIN, as if they were installed there. It's where the rest
+ # of the build.py code expected them to be (see also 'root' property in
+ # package definition YAMLs). It's much slower than single 'go install' since
+ # we rebuild all dependent libraries (including std lib!) at each 'go build'
+ # invocation.
+ go_bin = os.path.join(go_workspace, 'bin')
+ exe_suffix = get_target_package_vars()['exe_suffix']
+ for pkg in to_install:
+ name = pkg[pkg.rfind('/')+1:]
+ run_go_build(go_workspace, pkg, os.path.join(go_bin, name + exe_suffix))
+
+
+def enumerate_packages(py_venv, package_def_dir, package_def_files):
+ """Returns a list PackageDef instances for files in build/packages/*.yaml.
+
+ Args:
+ py_env: path to python ENV where to look for YAML parser.
package_def_dir: path to build/packages dir to search for *.yaml.
package_def_files: optional list of filenames to limit results to.
Returns:
- List of absolute paths to *.yaml files under packages_dir.
+ List of PackageDef instances parsed from *.yaml files under packages_dir.
"""
- # All existing package by default.
- if not package_def_files:
- return sorted(glob.glob(os.path.join(package_def_dir, '*.yaml')))
paths = []
- for name in package_def_files:
- abs_path = os.path.join(package_def_dir, name)
- if not os.path.isfile(abs_path):
- raise BuildException('No such package definition file: %s' % name)
- paths.append(abs_path)
- return sorted(paths)
+ if not package_def_files:
+ # All existing package by default.
+ paths = glob.glob(os.path.join(package_def_dir, '*.yaml'))
+ else:
+ # Otherwise pick only the ones in 'package_def_files' list.
+ for name in package_def_files:
+ abs_path = os.path.join(package_def_dir, name)
+ if not os.path.isfile(abs_path):
+ raise BuildException('No such package definition file: %s' % name)
+ paths.append(abs_path)
+ return [PackageDef(p, read_yaml(py_venv, p)) for p in sorted(paths)]
def read_yaml(py_venv, path):
@@ -193,14 +350,6 @@ def read_yaml(py_venv, path):
return json.loads(out)
-def should_process_on_builder(pkg_def_file, py_venv, builder):
- """Returns True if package should be processed by current CI builder."""
- if not builder:
- return True
- builders = read_yaml(py_venv, pkg_def_file).get('builders')
- return not builders or builder in builders
-
-
def get_package_vars():
"""Returns a dict with variables that describe the current environment.
@@ -208,6 +357,54 @@ def get_package_vars():
${variable_name}. It allows to reuse exact same definition file for similar
packages (e.g. packages with same cross platform binary, but for different
platforms).
+
+ If running in cross-compilation mode, uses GOOS and GOARCH to figure out the
+ target platform instead of examining the host environment.
+ """
+ if is_cross_compiling():
+ return get_target_package_vars()
+ return get_host_package_vars()
+
+
+def get_target_package_vars():
+ """Returns a dict with variables that describe cross compilation target env.
+
+ It contains only 'platform' and 'exe_suffix'. See 'get_package_vars' for more
+ details.
+ """
+ assert is_cross_compiling()
+ goos = os.environ['GOOS']
+ goarch = os.environ['GOARCH']
+
+ if goarch not in ['386', 'amd64', 'arm']:
+ raise BuildException('Unsupported GOARCH %s' % goarch)
+
+ # There are many ARMs, pick the concrete instruction set. 'v6' is the default.
+ if goarch == 'arm':
+ goarm = os.environ.get('GOARM', '6')
+ if goarm == '6':
+ arch = 'armv6l'
+ elif goarm == '7':
+ arch = 'armv7l'
+ else:
+ raise BuildException('Unsupported GOARM value %s' % goarm)
+ else:
+ arch = goarch
+
+ # We use 'mac' instead of 'darwin'.
Vadim Sh. 2016/06/25 03:51:03 I can't remember why :(
+ if goos == 'darwin':
+ goos = 'mac'
+
+ return {
+ 'exe_suffix': '.exe' if goos == 'windows' else '',
+ 'platform': '%s-%s' % (goos, arch),
+ }
+
+
+def get_host_package_vars():
+ """Returns a dict with variables that describe the current host environment.
+
+ See 'get_package_vars' for more details.
"""
# linux, mac or windows.
platform_variant = {
@@ -256,12 +453,12 @@ def get_package_vars():
}
-def build_pkg(go_workspace, pkg_def_file, out_file, package_vars):
+def build_pkg(cipd_exe, pkg_def, out_file, package_vars):
"""Invokes CIPD client to build a package.
Args:
- go_workspace: path to 'infra/go' or 'infra_internal/go'.
- pkg_def_file: path to *.yaml file with package definition.
+ cipd_exe: path to cipd client binary to use.
+ pkg_def: instance of PackageDef representing this package.
out_file: where to store the built package.
package_vars: dict with variables to pass as -pkg-var to cipd.
@@ -271,18 +468,18 @@ def build_pkg(go_workspace, pkg_def_file, out_file, package_vars):
Raises:
BuildException on error.
"""
- print_title('Building: %s' % os.path.basename(pkg_def_file))
+ print_title('Building: %s' % os.path.basename(out_file))
# Make sure not stale output remains.
if os.path.isfile(out_file):
os.remove(out_file)
# Build the package.
- args = ['-pkg-def', pkg_def_file]
+ args = ['-pkg-def', pkg_def.path]
for k, v in sorted(package_vars.items()):
args.extend(['-pkg-var', '%s:%s' % (k, v)])
args.extend(['-out', out_file])
- exit_code, json_output = run_cipd(go_workspace, 'pkg-build', args)
+ exit_code, json_output = run_cipd(cipd_exe, 'pkg-build', args)
if exit_code:
print
print >> sys.stderr, 'FAILED! ' * 10
@@ -294,11 +491,11 @@ def build_pkg(go_workspace, pkg_def_file, out_file, package_vars):
return info
-def upload_pkg(go_workspace, pkg_file, service_url, tags, service_account):
+def upload_pkg(cipd_exe, pkg_file, service_url, tags, service_account):
"""Uploads existing *.cipd file to the storage and tags it.
Args:
- go_workspace: path to 'infra/go' or 'infra_internal/go'.
+ cipd_exe: path to cipd client binary to use.
pkg_file: path to *.cipd file to upload.
service_url: URL of a package repository service.
tags: a list of tags to attach to uploaded package instance.
@@ -319,7 +516,7 @@ def upload_pkg(go_workspace, pkg_file, service_url, tags, service_account):
if service_account:
args.extend(['-service-account-json', service_account])
args.append(pkg_file)
- exit_code, json_output = run_cipd(go_workspace, 'pkg-register', args)
+ exit_code, json_output = run_cipd(cipd_exe, 'pkg-register', args)
if exit_code:
print
print >> sys.stderr, 'FAILED! ' * 10
@@ -329,6 +526,78 @@ def upload_pkg(go_workspace, pkg_file, service_url, tags, service_account):
return info
+def build_cipd_client(go_workspace, out_dir):
+ """Builds cipd client binary for the host platform.
+
+ Ignores GOOS and GOARCH env vars. Puts the client binary into
+ '<out_dir>/.cipd_client/cipd_<digest>'.
+
+ This binary is used by build.py itself and later by test_packages.py.
+
+ Args:
+ go_workspace: path to Go workspace root (contains 'env.py', 'src', etc).
+ out_dir: build output directory, will be used to store the binary.
+
+ Returns:
+ Path to the built binary.
+ """
+ # To avoid rebuilding cipd client all the time, we cache it in out/*, using
Vadim Sh. 2016/06/25 03:51:03 this is the most hacky part of this CL we need CI
+ # a combination of DEPS+deps.lock+bootstrap.py as a cache key (they define
+ # exact set of sources used to build the cipd binary).
+ #
+ # We can't just use the client in infra.git/cipd/* because it is built by this
+ # script itself: it introduced bootstrap dependency cycle in case we need to
+ # add a new platform or if we wipe cipd backend storage.
+ seed_paths = [
+ os.path.join(ROOT, 'DEPS'),
+ os.path.join(ROOT, 'go', 'deps.lock'),
+ os.path.join(ROOT, 'go', 'bootstrap.py'),
Vadim Sh. 2016/06/25 03:51:03 indirectly defines stuff like Go toolset version
+ ]
+ digest = hashlib.sha1()
+ for p in seed_paths:
+ with open(p, 'rb') as f:
+ digest.update(f.read())
+ cache_key = digest.hexdigest()[:20]
+
+ # Already have it?
+ cipd_out_dir = os.path.join(out_dir, '.cipd_client')
+ cipd_exe = os.path.join(cipd_out_dir, 'cipd_%s%s' % (cache_key, EXE_SUFFIX))
+ if os.path.exists(cipd_exe):
+ return cipd_exe
+
+ # Nuke all previous copies, make sure out_dir exists.
+ if os.path.exists(cipd_out_dir):
+ for p in glob.glob(os.path.join(cipd_out_dir, 'cipd_*')):
+ os.remove(p)
+ else:
+ os.makedirs(cipd_out_dir)
+
+ # Build cipd client binary for the host platform.
+ run_go_build(
+ go_workspace,
+ package='github.com/luci/luci-go/client/cmd/cipd',
+ output=cipd_exe,
+ goos='',
+ goarch='')
+
+ return cipd_exe
+
+
+def get_build_out_file(package_out_dir, pkg_def):
+ """Returns a path where to put built *.cipd package file.
+
+ Args:
+ package_out_dir: root directory where to put *.cipd files.
+ pkg_def: instance of PackageDef being built.
+ """
+ # When cross-compiling, append a suffix to package file name to indicate that
+ # it's for foreign platform.
+ sfx = ''
+ if is_cross_compiling():
+ sfx = '+' + get_target_package_vars()['platform']
+ return os.path.join(package_out_dir, pkg_def.name + sfx + '.cipd')
+
+
def run(
py_venv,
go_workspace,
@@ -343,7 +612,7 @@ def run(
tags,
service_account_json,
json_output):
- """Rebuild python and Go universes and CIPD packages.
+ """Rebuilds python and Go universes and CIPD packages.
Args:
py_venv: path to 'infra/ENV' or 'infra_internal/ENV'.
@@ -365,16 +634,16 @@ def run(
"""
assert build or upload, 'Both build and upload are False, nothing to do'
- # Remove stale output so that test_packages.py do not test old files when
- # invoked without arguments.
- if build:
- for path in glob.glob(os.path.join(package_out_dir, '*.cipd')):
- os.remove(path)
+ # We need both GOOS and GOARCH or none.
+ if is_cross_compiling():
+ if not os.environ.get('GOOS') or not os.environ.get('GOARCH'):
+ print >> sys.stderr, (
+ 'When cross-compiling both GOOS and GOARCH environment variables '
+ 'must be set.')
+ return 1
- packages_to_build = [
- p for p in enumerate_packages_to_build(package_def_dir, package_def_files)
- if should_process_on_builder(p, py_venv, builder)
- ]
+ all_packages = enumerate_packages(py_venv, package_def_dir, package_def_files)
+ packages_to_build = [p for p in all_packages if p.should_build(builder)]
print_title('Overview')
print 'Service URL: %s' % service_url
@@ -383,8 +652,8 @@ def run(
print 'Package definition files to process on %s:' % builder
else:
print 'Package definition files to process:'
- for pkg_def_file in packages_to_build:
- print ' %s' % os.path.basename(pkg_def_file)
+ for pkg_def in packages_to_build:
+ print ' %s' % pkg_def.name
if not packages_to_build:
print ' <none>'
print
@@ -402,32 +671,49 @@ def run(
print 'Nothing to do.'
return 0
+ # Remove old build artifacts to avoid stale files in case the script crashes
Vadim Sh. 2016/06/25 03:51:03 it used to remove everything under out/*, now buil
+ # for some reason.
+ if build:
+ print_title('Cleaning %s' % package_out_dir)
+ if not os.path.exists(package_out_dir):
+ os.makedirs(package_out_dir)
+ cleaned = False
+ for pkg_def in packages_to_build:
+ out_file = get_build_out_file(package_out_dir, pkg_def)
+ if os.path.exists(out_file):
+ print 'Removing stale %s' % os.path.basename(out_file)
+ os.remove(out_file)
+ cleaned = True
+ if not cleaned:
+ print 'Nothing to clean'
+
+ # Build the cipd client needed later to build or upload packages.
+ cipd_exe = build_cipd_client(go_workspace, package_out_dir)
+
# Build the world.
if build:
- build_callback()
+ build_callback(packages_to_build)
# Package it.
failed = []
succeeded = []
- for pkg_def_file in packages_to_build:
- # path/name.yaml -> out/name.cipd.
- name = os.path.splitext(os.path.basename(pkg_def_file))[0]
- out_file = os.path.join(package_out_dir, name + '.cipd')
+ for pkg_def in packages_to_build:
+ out_file = get_build_out_file(package_out_dir, pkg_def)
try:
info = None
if build:
- info = build_pkg(go_workspace, pkg_def_file, out_file, package_vars)
+ info = build_pkg(cipd_exe, pkg_def, out_file, package_vars)
if upload:
info = upload_pkg(
- go_workspace,
+ cipd_exe,
out_file,
service_url,
tags,
service_account_json)
assert info is not None
- succeeded.append({'pkg_def_name': name, 'info': info})
+ succeeded.append({'pkg_def_name': pkg_def.name, 'info': info})
except (BuildException, UploadException) as e:
- failed.append({'pkg_def_name': name, 'error': str(e)})
+ failed.append({'pkg_def_name': pkg_def.name, 'error': str(e)})
print_title('Summary')
for d in failed:
@@ -448,23 +734,24 @@ def run(
return 1 if failed else 0
-def build_infra():
- """Builds infra.git multiverse."""
- # Python side.
- print_title('Making sure python virtual environment is fresh')
- run_python(
- script=os.path.join(ROOT, 'bootstrap', 'bootstrap.py'),
- args=[
- '--deps_file',
- os.path.join(ROOT, 'bootstrap', 'deps.pyl'),
- os.path.join(ROOT, 'ENV'),
- ])
- # Go side.
- build_go(os.path.join(ROOT, 'go'), [
- 'infra/...',
- 'github.com/luci/luci-go/client/...',
- 'github.com/luci/luci-go/tools/...',
- ])
+def build_infra(pkg_defs):
+ """Builds infra.git multiverse.
+
+ Args:
+ pkg_defs: list of PackageDef instances for packages being built.
+ """
+ # Skip building python if not used or if cross-compiling.
+ if any(p.uses_python_env for p in pkg_defs) and not is_cross_compiling():
+ print_title('Making sure python virtual environment is fresh')
+ run_python(
+ script=os.path.join(ROOT, 'bootstrap', 'bootstrap.py'),
+ args=[
+ '--deps_file',
+ os.path.join(ROOT, 'bootstrap', 'deps.pyl'),
+ os.path.join(ROOT, 'ENV'),
+ ])
+ # Build all necessary go binaries.
+ build_go_code(os.path.join(ROOT, 'go'), pkg_defs)
def main(
« no previous file with comments | « build/README.md ('k') | build/out/.gitignore » ('j') | build/packages/cipd_client.yaml » ('J')

Powered by Google App Engine
This is Rietveld 408576698