|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- #!/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.
-
- # Modified from go/bootstrap.py in Chromium infrastructure's repository to patch
- # out everything but the core toolchain.
- #
- # https://chromium.googlesource.com/infra/infra/
-
- """Prepares a local hermetic Go installation.
-
- - Downloads and unpacks the Go toolset in ../golang.
- """
-
- import contextlib
- import logging
- import os
- import platform
- import shutil
- import stat
- import subprocess
- import sys
- import tarfile
- import tempfile
- import urllib
- import zipfile
-
- # TODO(vadimsh): Migrate to new golang.org/x/ paths once Golang moves to
- # git completely.
-
- LOGGER = logging.getLogger(__name__)
-
-
- # /path/to/util/bot
- ROOT = os.path.dirname(os.path.abspath(__file__))
-
- # Where to install Go toolset to. GOROOT would be <TOOLSET_ROOT>/go.
- TOOLSET_ROOT = os.path.join(os.path.dirname(ROOT), 'golang')
-
- # Default workspace with infra go code.
- WORKSPACE = os.path.join(ROOT, 'go')
-
- # Platform depended suffix for executable files.
- EXE_SFX = '.exe' if sys.platform == 'win32' else ''
-
- # Pinned version of Go toolset to download.
- TOOLSET_VERSION = 'go1.4'
-
- # Platform dependent portion of a download URL. See http://golang.org/dl/.
- TOOLSET_VARIANTS = {
- ('darwin', 'x86-32'): 'darwin-386-osx10.8.tar.gz',
- ('darwin', 'x86-64'): 'darwin-amd64-osx10.8.tar.gz',
- ('linux2', 'x86-32'): 'linux-386.tar.gz',
- ('linux2', 'x86-64'): 'linux-amd64.tar.gz',
- ('win32', 'x86-32'): 'windows-386.zip',
- ('win32', 'x86-64'): 'windows-amd64.zip',
- }
-
- # Download URL root.
- DOWNLOAD_URL_PREFIX = 'https://storage.googleapis.com/golang'
-
-
- class Failure(Exception):
- """Bootstrap failed."""
-
-
- def get_toolset_url():
- """URL of a platform specific Go toolset archive."""
- # TODO(vadimsh): Support toolset for cross-compilation.
- arch = {
- 'amd64': 'x86-64',
- 'x86_64': 'x86-64',
- 'i386': 'x86-32',
- 'x86': 'x86-32',
- }.get(platform.machine().lower())
- variant = TOOLSET_VARIANTS.get((sys.platform, arch))
- if not variant:
- # TODO(vadimsh): Compile go lang from source.
- raise Failure('Unrecognized platform')
- return '%s/%s.%s' % (DOWNLOAD_URL_PREFIX, TOOLSET_VERSION, variant)
-
-
- def read_file(path):
- """Returns contents of a given file or None if not readable."""
- assert isinstance(path, (list, tuple))
- try:
- with open(os.path.join(*path), 'r') as f:
- return f.read()
- except IOError:
- return None
-
-
- def write_file(path, data):
- """Writes |data| to a file."""
- assert isinstance(path, (list, tuple))
- with open(os.path.join(*path), 'w') as f:
- f.write(data)
-
-
- def remove_directory(path):
- """Recursively removes a directory."""
- assert isinstance(path, (list, tuple))
- p = os.path.join(*path)
- if not os.path.exists(p):
- return
- LOGGER.info('Removing %s', p)
- # Crutch to remove read-only file (.git/* in particular) on Windows.
- def onerror(func, path, _exc_info):
- if not os.access(path, os.W_OK):
- os.chmod(path, stat.S_IWUSR)
- func(path)
- else:
- raise
- shutil.rmtree(p, onerror=onerror if sys.platform == 'win32' else None)
-
-
- def install_toolset(toolset_root, url):
- """Downloads and installs Go toolset.
-
- GOROOT would be <toolset_root>/go/.
- """
- if not os.path.exists(toolset_root):
- os.makedirs(toolset_root)
- pkg_path = os.path.join(toolset_root, url[url.rfind('/')+1:])
-
- LOGGER.info('Downloading %s...', url)
- download_file(url, pkg_path)
-
- LOGGER.info('Extracting...')
- if pkg_path.endswith('.zip'):
- with zipfile.ZipFile(pkg_path, 'r') as f:
- f.extractall(toolset_root)
- elif pkg_path.endswith('.tar.gz'):
- with tarfile.open(pkg_path, 'r:gz') as f:
- f.extractall(toolset_root)
- else:
- raise Failure('Unrecognized archive format')
-
- LOGGER.info('Validating...')
- if not check_hello_world(toolset_root):
- raise Failure('Something is not right, test program doesn\'t work')
-
-
- def download_file(url, path):
- """Fetches |url| to |path|."""
- last_progress = [0]
- def report(a, b, c):
- progress = int(a * b * 100.0 / c)
- if progress != last_progress[0]:
- print >> sys.stderr, 'Downloading... %d%%' % progress
- last_progress[0] = progress
- # TODO(vadimsh): Use something less crippled, something that validates SSL.
- urllib.urlretrieve(url, path, reporthook=report)
-
-
- @contextlib.contextmanager
- def temp_dir(path):
- """Creates a temporary directory, then deletes it."""
- tmp = tempfile.mkdtemp(dir=path)
- try:
- yield tmp
- finally:
- remove_directory([tmp])
-
-
- def check_hello_world(toolset_root):
- """Compiles and runs 'hello world' program to verify that toolset works."""
- with temp_dir(toolset_root) as tmp:
- path = os.path.join(tmp, 'hello.go')
- write_file([path], r"""
- package main
- func main() { println("hello, world\n") }
- """)
- out = subprocess.check_output(
- [get_go_exe(toolset_root), 'run', path],
- env=get_go_environ(toolset_root, tmp),
- stderr=subprocess.STDOUT)
- if out.strip() != 'hello, world':
- LOGGER.error('Failed to run sample program:\n%s', out)
- return False
- return True
-
-
- def ensure_toolset_installed(toolset_root):
- """Installs or updates Go toolset if necessary.
-
- Returns True if new toolset was installed.
- """
- installed = read_file([toolset_root, 'INSTALLED_TOOLSET'])
- available = get_toolset_url()
- if installed == available:
- LOGGER.debug('Go toolset is up-to-date: %s', TOOLSET_VERSION)
- return False
-
- LOGGER.info('Installing Go toolset.')
- LOGGER.info(' Old toolset is %s', installed)
- LOGGER.info(' New toolset is %s', available)
- remove_directory([toolset_root])
- install_toolset(toolset_root, available)
- LOGGER.info('Go toolset installed: %s', TOOLSET_VERSION)
- write_file([toolset_root, 'INSTALLED_TOOLSET'], available)
- return True
-
-
- def get_go_environ(
- toolset_root,
- workspace=None):
- """Returns a copy of os.environ with added GO* environment variables.
-
- Overrides GOROOT, GOPATH and GOBIN. Keeps everything else. Idempotent.
-
- Args:
- toolset_root: GOROOT would be <toolset_root>/go.
- workspace: main workspace directory or None if compiling in GOROOT.
- """
- env = os.environ.copy()
- env['GOROOT'] = os.path.join(toolset_root, 'go')
- if workspace:
- env['GOBIN'] = os.path.join(workspace, 'bin')
- else:
- env.pop('GOBIN', None)
-
- all_go_paths = []
- if workspace:
- all_go_paths.append(workspace)
- env['GOPATH'] = os.pathsep.join(all_go_paths)
-
- # New PATH entries.
- paths_to_add = [
- os.path.join(env['GOROOT'], 'bin'),
- env.get('GOBIN'),
- ]
-
- # Make sure not to add duplicates entries to PATH over and over again when
- # get_go_environ is invoked multiple times.
- path = env['PATH'].split(os.pathsep)
- paths_to_add = [p for p in paths_to_add if p and p not in path]
- env['PATH'] = os.pathsep.join(paths_to_add + path)
-
- return env
-
-
- def get_go_exe(toolset_root):
- """Returns path to go executable."""
- return os.path.join(toolset_root, 'go', 'bin', 'go' + EXE_SFX)
-
-
- def bootstrap(logging_level):
- """Installs all dependencies in default locations.
-
- Supposed to be called at the beginning of some script (it modifies logger).
-
- Args:
- logging_level: logging level of bootstrap process.
- """
- logging.basicConfig()
- LOGGER.setLevel(logging_level)
- ensure_toolset_installed(TOOLSET_ROOT)
-
-
- def prepare_go_environ():
- """Returns dict with environment variables to set to use Go toolset.
-
- Installs or updates the toolset if necessary.
- """
- bootstrap(logging.INFO)
- return get_go_environ(TOOLSET_ROOT, WORKSPACE)
-
-
- def find_executable(name, workspaces):
- """Returns full path to an executable in some bin/ (in GOROOT or GOBIN)."""
- basename = name
- if EXE_SFX and basename.endswith(EXE_SFX):
- basename = basename[:-len(EXE_SFX)]
- roots = [os.path.join(TOOLSET_ROOT, 'go', 'bin')]
- for path in workspaces:
- roots.extend([
- os.path.join(path, 'bin'),
- ])
- for root in roots:
- full_path = os.path.join(root, basename + EXE_SFX)
- if os.path.exists(full_path):
- return full_path
- return name
-
-
- def main(args):
- if args:
- print >> sys.stderr, sys.modules[__name__].__doc__,
- return 2
- bootstrap(logging.DEBUG)
- return 0
-
-
- if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
|