1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """The cros chrome-sdk command for the simple chrome workflow."""
13 import distutils.version
15 from chromite import cros
16 from chromite.lib import cache
17 from chromite.lib import chrome_util
18 from chromite.lib import commandline
19 from chromite.lib import cros_build_lib
20 from chromite.lib import git
21 from chromite.lib import gs
22 from chromite.lib import osutils
23 from chromite.lib import stats
24 from chromite.cbuildbot import cbuildbot_config
25 from chromite.cbuildbot import constants
28 COMMAND_NAME = 'chrome-sdk'
29 CUSTOM_VERSION = 'custom'
32 def Log(*args, **kwargs):
33 """Conditional logging.
36 silent: If set to True, then logs with level DEBUG. logs with level INFO
37 otherwise. Defaults to False.
39 silent = kwargs.pop('silent', False)
40 level = logging.DEBUG if silent else logging.INFO
41 logging.log(level, *args, **kwargs)
44 class MissingSDK(Exception):
45 """Error thrown when we cannot find an SDK."""
47 def __init__(self, board, version=None):
48 msg = 'Cannot find SDK for %r' % (board,)
49 if version is not None:
50 msg += ' with version %s' % (version,)
51 Exception.__init__(self, msg)
54 class SDKFetcher(object):
55 """Functionality for fetching an SDK environment.
57 For the version of ChromeOS specified, the class downloads and caches
60 SDK_BOARD_ENV = '%SDK_BOARD'
61 SDK_PATH_ENV = '%SDK_PATH'
62 SDK_VERSION_ENV = '%SDK_VERSION'
64 SDKContext = collections.namedtuple(
65 'SDKContext', ['version', 'target_tc', 'key_map'])
67 TARBALL_CACHE = 'tarballs'
70 TARGET_TOOLCHAIN_KEY = 'target_toolchain'
72 def __init__(self, cache_dir, board, clear_cache=False, chrome_src=None,
73 sdk_path=None, silent=False):
74 """Initialize the class.
77 cache_dir: The toplevel cache dir to use.
78 board: The board to manage the SDK for.
79 clear_cache: Clears the sdk cache during __init__.
80 chrome_src: The location of the chrome checkout. If unspecified, the
81 cwd is presumed to be within a chrome checkout.
82 sdk_path: The path (whether a local directory or a gs:// path) to fetch
84 silent: If set, the fetcher prints less output.
86 self.cache_base = os.path.join(cache_dir, COMMAND_NAME)
88 logging.warning('Clearing the SDK cache.')
89 osutils.RmDir(self.cache_base, ignore_missing=True)
90 self.tarball_cache = cache.TarballCache(
91 os.path.join(self.cache_base, self.TARBALL_CACHE))
92 self.misc_cache = cache.DiskCache(
93 os.path.join(self.cache_base, self.MISC_CACHE))
95 self.config = cbuildbot_config.FindCanonicalConfigForBoard(board)
96 self.gs_base = '%s/%s' % (constants.DEFAULT_ARCHIVE_BUCKET,
98 self.clear_cache = clear_cache
99 self.chrome_src = chrome_src
100 self.sdk_path = sdk_path
103 # For external configs, there is no need to run 'gsutil config', because
104 # the necessary files are all accessible to anonymous users.
105 internal = self.config['internal']
106 self.gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=internal)
108 if self.sdk_path is None:
109 self.sdk_path = os.environ.get(self.SDK_PATH_ENV)
111 def _UpdateTarball(self, url, ref):
112 """Worker function to fetch tarballs"""
113 with osutils.TempDir(base_dir=self.tarball_cache.staging_dir) as tempdir:
114 local_path = os.path.join(tempdir, os.path.basename(url))
115 Log('SDK: Fetching %s', url, silent=self.silent)
116 self.gs_ctx.Copy(url, tempdir, debug_level=logging.DEBUG)
117 ref.SetDefault(local_path, lock=True)
119 def _GetMetadata(self, version):
120 """Return metadata (in the form of a dict) for a given version."""
122 version_base = self._GetVersionGSBase(version)
123 with self.misc_cache.Lookup(
124 self._GetCacheKeyForComponent(version, constants.METADATA_JSON)) as ref:
125 if ref.Exists(lock=True):
126 raw_json = osutils.ReadFile(ref.path)
128 metadata_path = os.path.join(version_base, constants.METADATA_JSON)
129 partial_metadata_path = os.path.join(version_base,
130 constants.PARTIAL_METADATA_JSON)
132 raw_json = self.gs_ctx.Cat(metadata_path,
133 debug_level=logging.DEBUG).output
134 except gs.GSNoSuchKey:
135 logging.info('Could not read %s, falling back to %s',
136 metadata_path, partial_metadata_path)
137 raw_json = self.gs_ctx.Cat(partial_metadata_path,
138 debug_level=logging.DEBUG).output
140 ref.AssignText(raw_json)
142 return json.loads(raw_json)
144 def _GetChromeLKGM(self, chrome_src_dir):
145 """Get ChromeOS LKGM checked into the Chrome tree.
148 Version number in format '3929.0.0'.
150 version = osutils.ReadFile(os.path.join(
151 chrome_src_dir, constants.PATH_TO_CHROME_LKGM))
154 def _GetRepoCheckoutVersion(self, repo_root):
155 """Get the version specified in chromeos_version.sh.
158 Version number in format '3929.0.0'.
160 chromeos_version_sh = os.path.join(repo_root, constants.VERSION_FILE)
161 sourced_env = osutils.SourceEnvironment(
162 chromeos_version_sh, ['CHROMEOS_VERSION_STRING'],
163 env={'CHROMEOS_OFFICIAL': '1'})
164 return sourced_env['CHROMEOS_VERSION_STRING']
166 def _GetNewestFullVersion(self, version=None):
167 """Gets the full version number of the latest build for the given |version|.
170 version: The version number or branch to look at. By default, look at
171 builds on the current branch.
174 Version number in the format 'R30-3929.0.0'.
177 version = git.GetChromiteTrackingBranch()
178 version_file = '%s/LATEST-%s' % (self.gs_base, version)
180 full_version = self.gs_ctx.Cat(version_file).output
181 assert full_version.startswith('R')
183 except gs.GSNoSuchKey:
186 def _GetNewestManifestVersion(self):
187 """Gets the latest uploaded SDK version.
190 Version number in the format '3929.0.0'.
192 full_version = self._GetNewestFullVersion()
193 return None if full_version is None else full_version.split('-')[1]
195 def GetDefaultVersion(self):
196 """Get the default SDK version to use.
198 If we are in an existing SDK shell, the default version will just be
199 the current version. Otherwise, we will try to calculate the
200 appropriate version to use based on the checkout.
202 if os.environ.get(self.SDK_BOARD_ENV) == self.board:
203 sdk_version = os.environ.get(self.SDK_VERSION_ENV)
204 if sdk_version is not None:
207 with self.misc_cache.Lookup((self.board, 'latest')) as ref:
208 if ref.Exists(lock=True):
209 version = osutils.ReadFile(ref.path).strip()
210 # Deal with the old version format.
211 if version.startswith('R'):
212 version = version.split('-')[1]
217 def _SetDefaultVersion(self, version):
218 """Set the new default version."""
219 with self.misc_cache.Lookup((self.board, 'latest')) as ref:
220 ref.AssignText(version)
222 def UpdateDefaultVersion(self):
223 """Update the version that we default to using.
226 A tuple of the form (version, updated), where |version| is the
227 version number in the format '3929.0.0', and |updated| indicates
228 whether the version was indeed updated.
230 checkout_dir = self.chrome_src if self.chrome_src else os.getcwd()
231 checkout = commandline.DetermineCheckout(checkout_dir)
232 current = self.GetDefaultVersion() or '0'
233 if checkout.chrome_src_dir:
234 target = self._GetChromeLKGM(checkout.chrome_src_dir)
235 elif checkout.type == commandline.CHECKOUT_TYPE_REPO:
236 target = self._GetRepoCheckoutVersion(checkout.root)
237 if target != current:
238 lv_cls = distutils.version.LooseVersion
239 if lv_cls(target) > lv_cls(current):
240 # Hit the network for the newest uploaded version for the branch.
241 newest = self._GetNewestManifestVersion()
242 # The SDK for the version of the checkout has not been uploaded yet,
243 # so fall back to the latest uploaded SDK.
244 if newest is not None and lv_cls(target) > lv_cls(newest):
247 target = self._GetNewestManifestVersion()
250 raise MissingSDK(self.board)
252 self._SetDefaultVersion(target)
253 return target, target != current
255 def GetFullVersion(self, version):
256 """Add the release branch to a ChromeOS platform version.
259 version: A ChromeOS platform number of the form XXXX.XX.XX, i.e.,
263 The version with release branch prepended. I.e., R28-3918.0.0.
265 assert not version.startswith('R')
267 with self.misc_cache.Lookup(('full-version', version)) as ref:
268 if ref.Exists(lock=True):
269 return osutils.ReadFile(ref.path).strip()
271 # Find out the newest version from the LATEST (or LATEST-%s) file.
272 full_version = self._GetNewestFullVersion(version=version)
274 if full_version is None:
275 raise MissingSDK(self.board, version)
277 ref.AssignText(full_version)
280 def _GetVersionGSBase(self, version):
281 """The base path of the SDK for a particular version."""
282 if self.sdk_path is not None:
285 full_version = self.GetFullVersion(version)
286 return os.path.join(self.gs_base, full_version)
288 def _GetCacheKeyForComponent(self, version, component):
289 """Builds the cache key tuple for an SDK component."""
290 version_section = version
291 if self.sdk_path is not None:
292 version_section = self.sdk_path.replace('/', '__').replace(':', '__')
293 return (self.board, version_section, component)
295 @contextlib.contextmanager
296 def Prepare(self, components, version=None, target_tc=None,
298 """Ensures the components of an SDK exist and are read-locked.
300 For a given SDK version, pulls down missing components, and provides a
301 context where the components are read-locked, which prevents the cache from
302 deleting them during its purge operations.
304 If both target_tc and toolchain_url arguments are provided, then this
305 does not download metadata.json for the given version. Otherwise, this
306 function requires metadata.json for the given version to exist.
309 gs_ctx: GSContext object.
310 components: A list of specific components(tarballs) to prepare.
311 version: The version to prepare. If not set, uses the version returned by
312 GetDefaultVersion(). If there is no default version set (this is the
313 first time we are being executed), then we update the default version.
314 target_tc: Target toolchain name to use, e.g. x86_64-cros-linux-gnu
315 toolchain_url: Format pattern for path to fetch toolchain from,
316 e.g. 2014/04/%(target)s-2014.04.23.220740.tar.xz
319 An SDKFetcher.SDKContext namedtuple object. The attributes of the
321 version: The version that was prepared.
322 target_tc: Target toolchain name.
323 key_map: Dictionary that contains CacheReference objects for the SDK
324 artifacts, indexed by cache key.
326 if version is None and self.sdk_path is None:
327 version = self.GetDefaultVersion()
329 version, _ = self.UpdateDefaultVersion()
330 components = list(components)
335 if not target_tc or not toolchain_url:
336 metadata = self._GetMetadata(version)
337 target_tc = target_tc or metadata['toolchain-tuple'][0]
338 toolchain_url = toolchain_url or metadata['toolchain-url']
340 # Fetch toolchains from separate location.
341 if self.TARGET_TOOLCHAIN_KEY in components:
342 fetch_urls[self.TARGET_TOOLCHAIN_KEY] = os.path.join(
343 'gs://', constants.SDK_GS_BUCKET,
344 toolchain_url % {'target': target_tc})
345 components.remove(self.TARGET_TOOLCHAIN_KEY)
347 version_base = self._GetVersionGSBase(version)
348 fetch_urls.update((t, os.path.join(version_base, t)) for t in components)
350 for key, url in fetch_urls.iteritems():
351 cache_key = self._GetCacheKeyForComponent(version, key)
352 ref = self.tarball_cache.Lookup(cache_key)
355 if not ref.Exists(lock=True):
356 # TODO(rcui): Parallelize this. Requires acquiring locks *before*
357 # generating worker processes; therefore the functionality needs to
358 # be moved into the DiskCache class itself -
359 # i.e.,DiskCache.ParallelSetDefault().
360 self._UpdateTarball(url, ref)
362 ctx_version = version
363 if self.sdk_path is not None:
364 ctx_version = CUSTOM_VERSION
365 yield self.SDKContext(ctx_version, target_tc, key_map)
367 # TODO(rcui): Move to using cros_build_lib.ContextManagerStack()
368 cros_build_lib.SafeRun([ref.Release for ref in key_map.itervalues()])
371 class GomaError(Exception):
372 """Indicates error with setting up Goma."""
375 class ClangError(Exception):
376 """Indicates error with setting up Clang."""
380 @cros.CommandDecorator(COMMAND_NAME)
381 class ChromeSDKCommand(cros.CrosCommand):
382 """Set up an environment for building Chrome on Chrome OS.
384 Pulls down SDK components for building and testing Chrome for Chrome OS,
385 sets up the environment for building Chrome, and runs a command in the
386 environment, starting a bash session if no command is specified.
388 The bash session environment is set up by a user-configurable rc file located
389 at ~/.chromite/chrome_sdk.bashrc.
392 # Note, this URL is not accessible outside of corp.
393 _GOMA_URL = ('https://clients5.google.com/cxx-compiler-service/'
394 'download/goma_ctl.py')
396 _CLANG_DIR = 'third_party/llvm-build/Release+Asserts/bin'
397 _CLANG_UPDATE_SH = 'tools/clang/scripts/update.sh'
410 SDK_GOMA_PORT_ENV = 'SDK_GOMA_PORT'
411 SDK_GOMA_DIR_ENV = 'SDK_GOMA_DIR'
413 GOMACC_PORT_CMD = ['./gomacc', 'port']
414 FETCH_GOMA_CMD = ['wget', _GOMA_URL]
416 # Override base class property to enable stats upload.
419 # Override base class property to use cache related commandline options.
420 use_caching_options = True
423 def upload_stats_timeout(self):
424 # Give a longer timeout for interactive SDK shell invocations, since the
425 # user will not notice a longer wait because it's happening in the
428 return super(ChromeSDKCommand, self).upload_stats_timeout
430 return stats.StatsUploader.UPLOAD_TIMEOUT
433 def ValidateVersion(version):
434 if version.startswith('R') or len(version.split('.')) != 3:
435 raise argparse.ArgumentTypeError(
436 '--version should be in the format 3912.0.0')
440 def AddParser(cls, parser):
441 def ExpandGSPath(path):
442 """Expand a path, possibly a gs:// URL."""
443 if path.startswith(gs.BASE_GS_URL):
445 return osutils.ExpandPath(path)
447 super(ChromeSDKCommand, cls).AddParser(parser)
449 '--board', required=True, help='The board SDK to use.')
451 '--bashrc', type=osutils.ExpandPath,
452 default=constants.CHROME_SDK_BASHRC,
453 help='A bashrc file used to set up the SDK shell environment. '
454 'Defaults to %s.' % constants.CHROME_SDK_BASHRC)
456 '--chroot', type=osutils.ExpandPath,
457 help='Path to a ChromeOS chroot to use. If set, '
458 '<chroot>/build/<board> will be used as the sysroot that Chrome '
459 'is built against. The version shown in the SDK shell prompt '
460 'will then have an asterisk prepended to it.')
462 '--chrome-src', type=osutils.ExpandPath,
463 help='Specifies the location of a Chrome src/ directory. Required if '
464 'running with --clang if not running from a Chrome checkout.')
466 '--clang', action='store_true', default=False,
467 help='Sets up the environment for building with clang. Due to a bug '
468 'with ninja, requires --make and possibly --chrome-src to be set.')
470 '--cwd', type=osutils.ExpandPath,
471 help='Specifies a directory to switch to after setting up the SDK '
472 'shell. Defaults to the current directory.')
474 '--internal', action='store_true', default=False,
475 help='Sets up SDK for building official (internal) Chrome '
476 'Chrome, rather than Chromium.')
478 '--sdk-path', type=ExpandGSPath,
479 help='Provides a path, whether a local directory or a gs:// path, to '
480 'pull SDK components from.')
482 '--make', action='store_true', default=False,
483 help='If set, gyp_chromium will generate Make files instead of Ninja '
484 'files. Note: Make files are spread out through the source tree, '
485 'and not concentrated in the out_<board> directory, so you can '
486 'only have one Make config running at a time.')
488 '--nogoma', action='store_false', default=True, dest='goma',
489 help="Disables Goma in the shell by removing it from the PATH.")
491 '--version', default=None, type=cls.ValidateVersion,
492 help="Specify version of SDK to use, in the format '3912.0.0'. "
493 "Defaults to determining version based on the type of checkout "
494 "(Chrome or ChromeOS) you are executing from.")
496 'cmd', nargs='*', default=None,
497 help='The command to execute in the SDK environment. Defaults to '
498 'starting a bash shell.')
500 parser.add_option_to_group(
501 parser.caching_group, '--clear-sdk-cache', action='store_true',
503 help='Removes everything in the SDK cache before starting.')
505 group = parser.add_option_group('Metadata Overrides (Advanced)',
506 description='Provide all of these overrides in order to remove '
507 'dependencies on metadata.json existence.')
508 parser.add_option_to_group(
509 group, '--target-tc', action='store', default=None,
510 help='Override target toolchain name, e.g. x86_64-cros-linux-gnu')
511 parser.add_option_to_group(
512 group, '--toolchain-url', action='store', default=None,
513 help='Override toolchain url format pattern, e.g. '
514 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
516 def __init__(self, options):
517 cros.CrosCommand.__init__(self, options)
518 self.board = options.board
521 # Initialized later based on options passed in.
525 def _CreatePS1(board, version, chroot=None):
526 """Returns PS1 string that sets commandline and xterm window caption.
528 If a chroot path is set, then indicate we are using the sysroot from there
529 instead of the stock sysroot by prepending an asterisk to the version.
532 board: The SDK board.
533 version: The SDK version.
534 chroot: The path to the chroot, if set.
536 custom = '*' if chroot else ''
537 sdk_version = '(sdk %s %s%s)' % (board, custom, version)
538 label = '\\u@\\h: \\w'
539 window_caption = "\\[\\e]0;%(sdk_version)s %(label)s \\a\\]"
540 command_line = "%(sdk_version)s \\[\\e[1;33m\\]%(label)s \\$ \\[\\e[m\\]"
541 ps1 = window_caption + command_line
542 return (ps1 % {'sdk_version': sdk_version,
545 def _FixGoldPath(self, var_contents, toolchain_path):
546 """Point to the gold linker in the toolchain tarball.
548 Accepts an already set environment variable in the form of '<cmd>
549 -B<gold_path>', and overrides the gold_path to the correct path in the
550 extracted toolchain tarball.
553 var_contents: The contents of the environment variable.
554 toolchain_path: Path to the extracted toolchain tarball contents.
557 Environment string that has correct gold path.
559 cmd, _, gold_path = var_contents.partition(' -B')
560 gold_path = os.path.join(toolchain_path, gold_path.lstrip('/'))
561 return '%s -B%s' % (cmd, gold_path)
563 def _SetupTCEnvironment(self, sdk_ctx, options, env):
564 """Sets up toolchain-related environment variables."""
565 target_tc_path = sdk_ctx.key_map[self.sdk.TARGET_TOOLCHAIN_KEY].path
566 tc_bin_path = os.path.join(target_tc_path, 'bin')
567 env['PATH'] = '%s:%s' % (tc_bin_path, os.environ['PATH'])
569 for var in ('CXX', 'CC', 'LD'):
570 env[var] = self._FixGoldPath(env[var], target_tc_path)
573 # clang++ requires C++ header paths to be explicitly specified.
574 # See discussion on crbug.com/86037.
575 target_tc = sdk_ctx.target_tc
576 gcc_path = os.path.join(tc_bin_path, '%s-gcc' % target_tc)
577 gcc_version = cros_build_lib.DebugRunCommand(
578 [gcc_path, '-dumpversion'], redirect_stdout=True).output.strip()
579 gcc_lib = 'usr/lib/gcc/%(targ)s/%(ver)s/include/g++-v%(major_ver)s' % {
582 'major_ver': gcc_version[0],
584 tc_gcc_lib = os.path.join(target_tc_path, gcc_lib)
586 for p in ('', target_tc, 'backward'):
587 includes.append('-isystem %s' % os.path.join(tc_gcc_lib, p))
589 env['CXX'] = 'clang++ %s' % ' '.join(includes)
591 clang_path = os.path.join(options.chrome_src, self._CLANG_DIR)
592 env['CC_host'] = os.path.join(clang_path, 'clang')
593 env['CXX_host'] = os.path.join(clang_path, 'clang++')
596 env['PATH'] = '%s:%s' % (clang_path, env['PATH'])
598 def _SetupEnvironment(self, board, sdk_ctx, options, goma_dir=None,
600 """Sets environment variables to export to the SDK shell."""
602 sysroot = os.path.join(options.chroot, 'build', board)
603 if not os.path.isdir(sysroot) and not options.cmd:
604 logging.warning("Because --chroot is set, expected a sysroot to be at "
605 "%s, but couldn't find one.", sysroot)
607 sysroot = sdk_ctx.key_map[constants.CHROME_SYSROOT_TAR].path
609 environment = os.path.join(sdk_ctx.key_map[constants.CHROME_ENV_TAR].path,
611 env = osutils.SourceEnvironment(environment, self.EBUILD_ENV)
612 self._SetupTCEnvironment(sdk_ctx, options, env)
614 # Add managed components to the PATH.
615 env['PATH'] = '%s:%s' % (constants.CHROMITE_BIN_DIR, env['PATH'])
616 env['PATH'] = '%s:%s' % (os.path.dirname(self.sdk.gs_ctx.gsutil_bin),
619 # Export internally referenced variables.
620 os.environ[self.sdk.SDK_BOARD_ENV] = board
621 if self.options.sdk_path:
622 os.environ[self.sdk.SDK_PATH_ENV] = self.options.sdk_path
623 os.environ[self.sdk.SDK_VERSION_ENV] = sdk_ctx.version
625 # Export the board/version info in a more accessible way, so developers can
626 # reference them in their chrome_sdk.bashrc files, as well as within the
628 for var in [self.sdk.SDK_VERSION_ENV, self.sdk.SDK_BOARD_ENV]:
629 env[var.lstrip('%')] = os.environ[var]
631 # Export Goma information.
633 env[self.SDK_GOMA_DIR_ENV] = goma_dir
634 env[self.SDK_GOMA_PORT_ENV] = goma_port
636 # SYSROOT is necessary for Goma and the sysroot wrapper.
637 env['SYSROOT'] = sysroot
638 gyp_dict = chrome_util.ProcessGypDefines(env['GYP_DEFINES'])
639 gyp_dict['sysroot'] = sysroot
640 gyp_dict.pop('order_text_section', None)
641 gyp_dict['host_clang'] = 1
643 gyp_dict['clang'] = 1
644 gyp_dict['werror'] = ''
645 gyp_dict['clang_use_chrome_plugins'] = 0
646 gyp_dict['use_allocator'] = 0
648 gyp_dict['branding'] = 'Chrome'
649 gyp_dict['buildtype'] = 'Official'
651 gyp_dict.pop('branding', None)
652 gyp_dict.pop('buildtype', None)
653 gyp_dict.pop('internal_gles2_conform_tests', None)
655 # Enable goma if requested.
657 gyp_dict['use_goma'] = 1
658 gyp_dict['gomadir'] = goma_dir
660 env['GYP_DEFINES'] = chrome_util.DictToGypDefines(gyp_dict)
662 # PS1 sets the command line prompt and xterm window caption.
663 full_version = sdk_ctx.version
664 if full_version != CUSTOM_VERSION:
665 full_version = self.sdk.GetFullVersion(sdk_ctx.version)
666 env['PS1'] = self._CreatePS1(self.board, full_version,
667 chroot=options.chroot)
669 out_dir = 'out_%s' % self.board
670 env['builddir_name'] = out_dir
671 env['GYP_GENERATORS'] = 'make' if options.make else 'ninja'
672 env['GYP_GENERATOR_FLAGS'] = 'output_dir=%s' % out_dir
673 env['GYP_CROSSCOMPILE'] = '1'
677 def _VerifyGoma(user_rc):
678 """Verify that the user has no goma installations set up in user_rc.
680 If the user does have a goma installation set up, verify that it's for
684 user_rc: User-supplied rc file.
686 user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
687 goma_ctl = osutils.Which('goma_ctl.py', user_env.get('PATH'))
688 if goma_ctl is not None:
690 '%s is adding Goma to the PATH. Using that Goma instead of the '
691 'managed Goma install.', user_rc)
694 def _VerifyChromiteBin(user_rc):
695 """Verify that the user has not set a chromite bin/ dir in user_rc.
698 user_rc: User-supplied rc file.
700 user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
701 chromite_bin = osutils.Which('parallel_emerge', user_env.get('PATH'))
702 if chromite_bin is not None:
704 '%s is adding chromite/bin to the PATH. Remove it from the PATH to '
705 'use the the default Chromite.', user_rc)
708 def _VerifyClang(user_rc):
709 """Verify that the user has not set a clang bin/ dir in user_rc.
712 user_rc: User-supplied rc file.
714 user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
715 clang_bin = osutils.Which('clang', user_env.get('PATH'))
716 if clang_bin is not None:
717 clang_dir = os.path.dirname(clang_bin)
718 if not osutils.Which('goma_ctl.py', clang_dir):
720 '%s is adding Clang to the PATH. Because of this, Goma is being '
721 'bypassed. Remove it from the PATH to use Goma with the default '
724 @contextlib.contextmanager
725 def _GetRCFile(self, env, user_rc):
726 """Returns path to dynamically created bashrc file.
728 The bashrc file sets the environment variables contained in |env|, as well
729 as sources the user-editable chrome_sdk.bashrc file in the user's home
730 directory. That rc file is created if it doesn't already exist.
733 env: A dictionary of environment variables that will be set by the rc
735 user_rc: User-supplied rc file.
737 if not os.path.exists(user_rc):
738 osutils.Touch(user_rc, makedirs=True)
740 self._VerifyGoma(user_rc)
741 self._VerifyChromiteBin(user_rc)
742 if self.options.clang:
743 self._VerifyClang(user_rc)
745 # We need a temporary rc file to 'wrap' the user configuration file,
746 # because running with '--rcfile' causes bash to ignore bash special
747 # variables passed through subprocess.Popen, such as PS1. So we set them
750 # Having a wrapper rc file will also allow us to inject bash functions into
751 # the environment, not just variables.
752 with osutils.TempDir() as tempdir:
753 # Only source the user's ~/.bashrc if running in interactive mode.
755 '[[ -e ~/.bashrc && $- == *i* ]] && . ~/.bashrc\n',
758 for key, value in env.iteritems():
759 contents.append("export %s='%s'\n" % (key, value))
760 contents.append('. "%s"\n' % user_rc)
762 rc_file = os.path.join(tempdir, 'rcfile')
763 osutils.WriteFile(rc_file, contents)
766 def _GomaPort(self, goma_dir):
767 """Returns current active Goma port."""
768 port = cros_build_lib.RunCommand(
769 self.GOMACC_PORT_CMD, cwd=goma_dir, debug_level=logging.DEBUG,
770 error_code_ok=True, capture_output=True).output.strip()
773 def _FetchGoma(self):
774 """Fetch, install, and start Goma, using cached version if it exists.
777 A tuple (dir, port) containing the path to the cached goma/ dir and the
780 common_path = os.path.join(self.options.cache_dir, constants.COMMON_CACHE)
781 common_cache = cache.DiskCache(common_path)
783 ref = common_cache.Lookup(('goma', '2'))
785 Log('Installing Goma.', silent=self.silent)
786 with osutils.TempDir() as tempdir:
787 goma_dir = os.path.join(tempdir, 'goma')
789 result = cros_build_lib.DebugRunCommand(
790 self.FETCH_GOMA_CMD, cwd=goma_dir, error_code_ok=True)
791 if result.returncode:
792 raise GomaError('Failed to fetch Goma')
793 # Update to latest version of goma. We choose the outside-chroot version
794 # ('goobuntu') over the chroot version ('chromeos') by supplying
795 # input='1' to the following prompt:
797 # What is your platform?
798 # 1. Goobuntu 2. Precise (32bit) 3. Lucid (32bit) 4. Debian
799 # 5. Chrome OS 6. MacOS ? -->
800 cros_build_lib.DebugRunCommand(
801 ['python', 'goma_ctl.py', 'update'], cwd=goma_dir, input='1\n')
802 ref.SetDefault(goma_dir)
805 Log('Starting Goma.', silent=self.silent)
806 cros_build_lib.DebugRunCommand(
807 ['python', 'goma_ctl.py', 'ensure_start'], cwd=goma_dir)
808 port = self._GomaPort(goma_dir)
809 Log('Goma is started on port %s', port, silent=self.silent)
811 raise GomaError('No Goma port detected')
813 return goma_dir, port
815 def _SetupClang(self):
816 """Install clang if needed."""
817 clang_path = os.path.join(self.options.chrome_src, self._CLANG_DIR)
818 if not os.path.exists(clang_path):
820 update_sh = os.path.join(self.options.chrome_src, self._CLANG_UPDATE_SH)
821 if not os.path.isfile(update_sh):
822 raise ClangError('%s not found.' % update_sh)
823 results = cros_build_lib.DebugRunCommand(
824 [update_sh], cwd=self.options.chrome_src, error_code_ok=True)
825 if results.returncode:
826 raise ClangError('Clang update failed with error code %s' %
827 (results.returncode,))
828 if not os.path.exists(clang_path):
829 raise ClangError('%s not found.' % clang_path)
830 except ClangError as e:
831 logging.error('Encountered errors while installing/updating clang: %s',
835 """Perform the command."""
836 if os.environ.get(SDKFetcher.SDK_VERSION_ENV) is not None:
837 cros_build_lib.Die('Already in an SDK shell.')
839 if self.options.clang and not self.options.make:
840 cros_build_lib.Die('--clang requires --make to be set.')
842 if not self.options.chrome_src:
843 checkout = commandline.DetermineCheckout(os.getcwd())
844 self.options.chrome_src = checkout.chrome_src_dir
846 checkout = commandline.DetermineCheckout(self.options.chrome_src)
847 if not checkout.chrome_src_dir:
848 cros_build_lib.Die('Chrome checkout not found at %s',
849 self.options.chrome_src)
850 self.options.chrome_src = checkout.chrome_src_dir
852 if self.options.clang and not self.options.chrome_src:
853 cros_build_lib.Die('--clang requires --chrome-src to be set.')
855 if self.options.version and self.options.sdk_path:
856 cros_build_lib.Die('Cannot specify both --version and --sdk-path.')
858 self.silent = bool(self.options.cmd)
859 # Lazy initialize because SDKFetcher creates a GSContext() object in its
860 # constructor, which may block on user input.
861 self.sdk = SDKFetcher(self.options.cache_dir, self.options.board,
862 clear_cache=self.options.clear_sdk_cache,
863 chrome_src=self.options.chrome_src,
864 sdk_path=self.options.sdk_path,
867 prepare_version = self.options.version
868 if not prepare_version and not self.options.sdk_path:
869 prepare_version, _ = self.sdk.UpdateDefaultVersion()
871 components = [self.sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR]
872 if not self.options.chroot:
873 components.append(constants.CHROME_SYSROOT_TAR)
877 if self.options.goma:
879 goma_dir, goma_port = self._FetchGoma()
880 except GomaError as e:
881 logging.error('Goma: %s. Bypass by running with --nogoma.', e)
883 if self.options.clang:
886 with self.sdk.Prepare(components, version=prepare_version,
887 target_tc=self.options.target_tc,
888 toolchain_url=self.options.toolchain_url) as ctx:
889 env = self._SetupEnvironment(self.options.board, ctx, self.options,
890 goma_dir=goma_dir, goma_port=goma_port)
891 with self._GetRCFile(env, self.options.bashrc) as rcfile:
892 bash_cmd = ['/bin/bash']
895 if not self.options.cmd:
896 bash_cmd.extend(['--rcfile', rcfile, '-i'])
898 # The '"$@"' expands out to the properly quoted positional args
899 # coming after the '--'.
900 bash_cmd.extend(['-c', '"$@"', '--'])
901 bash_cmd.extend(self.options.cmd)
902 # When run in noninteractive mode, bash sources the rc file set in
903 # BASH_ENV, and ignores the --rcfile flag.
904 extra_env = {'BASH_ENV': rcfile}
906 # Bash behaves differently when it detects that it's being launched by
907 # sshd - it ignores the BASH_ENV variable. So prevent ssh-related
908 # environment variables from being passed through.
909 os.environ.pop('SSH_CLIENT', None)
910 os.environ.pop('SSH_CONNECTION', None)
911 os.environ.pop('SSH_TTY', None)
913 cmd_result = cros_build_lib.RunCommand(
914 bash_cmd, print_cmd=False, debug_level=logging.CRITICAL,
915 error_code_ok=True, extra_env=extra_env, cwd=self.options.cwd)
917 return cmd_result.returncode