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."""
7 from __future__ import print_function
15 import distutils.version
17 from chromite import cros
18 from chromite.lib import cache
19 from chromite.lib import chrome_util
20 from chromite.lib import commandline
21 from chromite.lib import cros_build_lib
22 from chromite.lib import git
23 from chromite.lib import gs
24 from chromite.lib import osutils
25 from chromite.lib import stats
26 from chromite.cbuildbot import constants
29 COMMAND_NAME = 'chrome-sdk'
30 CUSTOM_VERSION = 'custom'
33 def Log(*args, **kwargs):
34 """Conditional logging.
37 silent: If set to True, then logs with level DEBUG. logs with level INFO
38 otherwise. Defaults to False.
40 silent = kwargs.pop('silent', False)
41 level = logging.DEBUG if silent else logging.INFO
42 logging.log(level, *args, **kwargs)
45 class MissingSDK(Exception):
46 """Error thrown when we cannot find an SDK."""
48 def __init__(self, board, version=None):
49 msg = 'Cannot find SDK for %r' % (board,)
50 if version is not None:
51 msg += ' with version %s' % (version,)
52 Exception.__init__(self, msg)
55 class SDKFetcher(object):
56 """Functionality for fetching an SDK environment.
58 For the version of ChromeOS specified, the class downloads and caches
61 SDK_BOARD_ENV = '%SDK_BOARD'
62 SDK_PATH_ENV = '%SDK_PATH'
63 SDK_VERSION_ENV = '%SDK_VERSION'
65 SDKContext = collections.namedtuple(
66 'SDKContext', ['version', 'target_tc', 'key_map'])
68 TARBALL_CACHE = 'tarballs'
71 TARGET_TOOLCHAIN_KEY = 'target_toolchain'
73 def __init__(self, cache_dir, board, clear_cache=False, chrome_src=None,
74 sdk_path=None, toolchain_path=None, silent=False):
75 """Initialize the class.
78 cache_dir: The toplevel cache dir to use.
79 board: The board to manage the SDK for.
80 clear_cache: Clears the sdk cache during __init__.
81 chrome_src: The location of the chrome checkout. If unspecified, the
82 cwd is presumed to be within a chrome checkout.
83 sdk_path: The path (whether a local directory or a gs:// path) to fetch
85 toolchain_path: The path (whether a local directory or a gs:// path) to
86 fetch toolchain components from.
87 silent: If set, the fetcher prints less output.
89 # Delay this import because it is super slow. http://crbug.com/404575
90 from chromite.cbuildbot import cbuildbot_config
92 self.cache_base = os.path.join(cache_dir, COMMAND_NAME)
94 logging.warning('Clearing the SDK cache.')
95 osutils.RmDir(self.cache_base, ignore_missing=True)
96 self.tarball_cache = cache.TarballCache(
97 os.path.join(self.cache_base, self.TARBALL_CACHE))
98 self.misc_cache = cache.DiskCache(
99 os.path.join(self.cache_base, self.MISC_CACHE))
101 self.config = cbuildbot_config.FindCanonicalConfigForBoard(board)
102 self.gs_base = '%s/%s' % (constants.DEFAULT_ARCHIVE_BUCKET,
104 self.clear_cache = clear_cache
105 self.chrome_src = chrome_src
106 self.sdk_path = sdk_path
107 self.toolchain_path = toolchain_path
110 # For external configs, there is no need to run 'gsutil config', because
111 # the necessary files are all accessible to anonymous users.
112 internal = self.config['internal']
113 self.gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=internal)
115 if self.sdk_path is None:
116 self.sdk_path = os.environ.get(self.SDK_PATH_ENV)
118 if self.toolchain_path is None:
119 self.toolchain_path = 'gs://%s' % constants.SDK_GS_BUCKET
121 def _UpdateTarball(self, url, ref):
122 """Worker function to fetch tarballs"""
123 with osutils.TempDir(base_dir=self.tarball_cache.staging_dir) as tempdir:
124 local_path = os.path.join(tempdir, os.path.basename(url))
125 Log('SDK: Fetching %s', url, silent=self.silent)
126 self.gs_ctx.Copy(url, tempdir, debug_level=logging.DEBUG)
127 ref.SetDefault(local_path, lock=True)
129 def _GetMetadata(self, version):
130 """Return metadata (in the form of a dict) for a given version."""
132 version_base = self._GetVersionGSBase(version)
133 with self.misc_cache.Lookup(
134 self._GetCacheKeyForComponent(version, constants.METADATA_JSON)) as ref:
135 if ref.Exists(lock=True):
136 raw_json = osutils.ReadFile(ref.path)
138 metadata_path = os.path.join(version_base, constants.METADATA_JSON)
139 partial_metadata_path = os.path.join(version_base,
140 constants.PARTIAL_METADATA_JSON)
142 raw_json = self.gs_ctx.Cat(metadata_path,
143 debug_level=logging.DEBUG)
144 except gs.GSNoSuchKey:
145 logging.info('Could not read %s, falling back to %s',
146 metadata_path, partial_metadata_path)
147 raw_json = self.gs_ctx.Cat(partial_metadata_path,
148 debug_level=logging.DEBUG)
150 ref.AssignText(raw_json)
152 return json.loads(raw_json)
154 def _GetChromeLKGM(self, chrome_src_dir):
155 """Get ChromeOS LKGM checked into the Chrome tree.
158 Version number in format '3929.0.0'.
160 version = osutils.ReadFile(os.path.join(
161 chrome_src_dir, constants.PATH_TO_CHROME_LKGM))
164 def _GetRepoCheckoutVersion(self, repo_root):
165 """Get the version specified in chromeos_version.sh.
168 Version number in format '3929.0.0'.
170 chromeos_version_sh = os.path.join(repo_root, constants.VERSION_FILE)
171 sourced_env = osutils.SourceEnvironment(
172 chromeos_version_sh, ['CHROMEOS_VERSION_STRING'],
173 env={'CHROMEOS_OFFICIAL': '1'})
174 return sourced_env['CHROMEOS_VERSION_STRING']
176 def _GetNewestFullVersion(self, version=None):
177 """Gets the full version number of the latest build for the given |version|.
180 version: The version number or branch to look at. By default, look at
181 builds on the current branch.
184 Version number in the format 'R30-3929.0.0'.
187 version = git.GetChromiteTrackingBranch()
188 version_file = '%s/LATEST-%s' % (self.gs_base, version)
190 full_version = self.gs_ctx.Cat(version_file)
191 assert full_version.startswith('R')
193 except gs.GSNoSuchKey:
196 def _GetNewestManifestVersion(self):
197 """Gets the latest uploaded SDK version.
200 Version number in the format '3929.0.0'.
202 full_version = self._GetNewestFullVersion()
203 return None if full_version is None else full_version.split('-')[1]
205 def GetDefaultVersion(self):
206 """Get the default SDK version to use.
208 If we are in an existing SDK shell, the default version will just be
209 the current version. Otherwise, we will try to calculate the
210 appropriate version to use based on the checkout.
212 if os.environ.get(self.SDK_BOARD_ENV) == self.board:
213 sdk_version = os.environ.get(self.SDK_VERSION_ENV)
214 if sdk_version is not None:
217 with self.misc_cache.Lookup((self.board, 'latest')) as ref:
218 if ref.Exists(lock=True):
219 version = osutils.ReadFile(ref.path).strip()
220 # Deal with the old version format.
221 if version.startswith('R'):
222 version = version.split('-')[1]
227 def _SetDefaultVersion(self, version):
228 """Set the new default version."""
229 with self.misc_cache.Lookup((self.board, 'latest')) as ref:
230 ref.AssignText(version)
232 def UpdateDefaultVersion(self):
233 """Update the version that we default to using.
236 A tuple of the form (version, updated), where |version| is the
237 version number in the format '3929.0.0', and |updated| indicates
238 whether the version was indeed updated.
240 checkout_dir = self.chrome_src if self.chrome_src else os.getcwd()
241 checkout = commandline.DetermineCheckout(checkout_dir)
242 current = self.GetDefaultVersion() or '0'
243 if checkout.chrome_src_dir:
244 target = self._GetChromeLKGM(checkout.chrome_src_dir)
245 elif checkout.type == commandline.CHECKOUT_TYPE_REPO:
246 target = self._GetRepoCheckoutVersion(checkout.root)
247 if target != current:
248 lv_cls = distutils.version.LooseVersion
249 if lv_cls(target) > lv_cls(current):
250 # Hit the network for the newest uploaded version for the branch.
251 newest = self._GetNewestManifestVersion()
252 # The SDK for the version of the checkout has not been uploaded yet,
253 # so fall back to the latest uploaded SDK.
254 if newest is not None and lv_cls(target) > lv_cls(newest):
257 target = self._GetNewestManifestVersion()
260 raise MissingSDK(self.board)
262 self._SetDefaultVersion(target)
263 return target, target != current
265 def GetFullVersion(self, version):
266 """Add the release branch to a ChromeOS platform version.
269 version: A ChromeOS platform number of the form XXXX.XX.XX, i.e.,
273 The version with release branch prepended. I.e., R28-3918.0.0.
275 assert not version.startswith('R')
277 with self.misc_cache.Lookup(('full-version', version)) as ref:
278 if ref.Exists(lock=True):
279 return osutils.ReadFile(ref.path).strip()
281 # Find out the newest version from the LATEST (or LATEST-%s) file.
282 full_version = self._GetNewestFullVersion(version=version)
284 if full_version is None:
285 raise MissingSDK(self.board, version)
287 ref.AssignText(full_version)
290 def _GetVersionGSBase(self, version):
291 """The base path of the SDK for a particular version."""
292 if self.sdk_path is not None:
295 full_version = self.GetFullVersion(version)
296 return os.path.join(self.gs_base, full_version)
298 def _GetCacheKeyForComponent(self, version, component):
299 """Builds the cache key tuple for an SDK component."""
300 version_section = version
301 if self.sdk_path is not None:
302 version_section = self.sdk_path.replace('/', '__').replace(':', '__')
303 return (self.board, version_section, component)
305 @contextlib.contextmanager
306 def Prepare(self, components, version=None, target_tc=None,
308 """Ensures the components of an SDK exist and are read-locked.
310 For a given SDK version, pulls down missing components, and provides a
311 context where the components are read-locked, which prevents the cache from
312 deleting them during its purge operations.
314 If both target_tc and toolchain_url arguments are provided, then this
315 does not download metadata.json for the given version. Otherwise, this
316 function requires metadata.json for the given version to exist.
319 gs_ctx: GSContext object.
320 components: A list of specific components(tarballs) to prepare.
321 version: The version to prepare. If not set, uses the version returned by
322 GetDefaultVersion(). If there is no default version set (this is the
323 first time we are being executed), then we update the default version.
324 target_tc: Target toolchain name to use, e.g. x86_64-cros-linux-gnu
325 toolchain_url: Format pattern for path to fetch toolchain from,
326 e.g. 2014/04/%(target)s-2014.04.23.220740.tar.xz
329 An SDKFetcher.SDKContext namedtuple object. The attributes of the
331 version: The version that was prepared.
332 target_tc: Target toolchain name.
333 key_map: Dictionary that contains CacheReference objects for the SDK
334 artifacts, indexed by cache key.
336 if version is None and self.sdk_path is None:
337 version = self.GetDefaultVersion()
339 version, _ = self.UpdateDefaultVersion()
340 components = list(components)
345 if not target_tc or not toolchain_url:
346 metadata = self._GetMetadata(version)
347 target_tc = target_tc or metadata['toolchain-tuple'][0]
348 toolchain_url = toolchain_url or metadata['toolchain-url']
350 # Fetch toolchains from separate location.
351 if self.TARGET_TOOLCHAIN_KEY in components:
352 fetch_urls[self.TARGET_TOOLCHAIN_KEY] = os.path.join(
353 self.toolchain_path, toolchain_url % {'target': target_tc})
354 components.remove(self.TARGET_TOOLCHAIN_KEY)
356 version_base = self._GetVersionGSBase(version)
357 fetch_urls.update((t, os.path.join(version_base, t)) for t in components)
359 for key, url in fetch_urls.iteritems():
360 cache_key = self._GetCacheKeyForComponent(version, key)
361 ref = self.tarball_cache.Lookup(cache_key)
364 if not ref.Exists(lock=True):
365 # TODO(rcui): Parallelize this. Requires acquiring locks *before*
366 # generating worker processes; therefore the functionality needs to
367 # be moved into the DiskCache class itself -
368 # i.e.,DiskCache.ParallelSetDefault().
369 self._UpdateTarball(url, ref)
371 ctx_version = version
372 if self.sdk_path is not None:
373 ctx_version = CUSTOM_VERSION
374 yield self.SDKContext(ctx_version, target_tc, key_map)
376 # TODO(rcui): Move to using cros_build_lib.ContextManagerStack()
377 cros_build_lib.SafeRun([ref.Release for ref in key_map.itervalues()])
380 class GomaError(Exception):
381 """Indicates error with setting up Goma."""
384 @cros.CommandDecorator(COMMAND_NAME)
385 class ChromeSDKCommand(cros.CrosCommand):
386 """Set up an environment for building Chrome on Chrome OS.
388 Pulls down SDK components for building and testing Chrome for Chrome OS,
389 sets up the environment for building Chrome, and runs a command in the
390 environment, starting a bash session if no command is specified.
392 The bash session environment is set up by a user-configurable rc file located
393 at ~/.chromite/chrome_sdk.bashrc.
396 # Note, this URL is not accessible outside of corp.
397 _GOMA_URL = ('https://clients5.google.com/cxx-compiler-service/'
398 'download/goma_ctl.py')
400 _CLANG_DIR = 'third_party/llvm-build/Release+Asserts/bin'
413 SDK_GOMA_PORT_ENV = 'SDK_GOMA_PORT'
414 SDK_GOMA_DIR_ENV = 'SDK_GOMA_DIR'
416 GOMACC_PORT_CMD = ['./gomacc', 'port']
417 FETCH_GOMA_CMD = ['wget', _GOMA_URL]
419 # Override base class property to enable stats upload.
422 # Override base class property to use cache related commandline options.
423 use_caching_options = True
426 def upload_stats_timeout(self):
427 # Give a longer timeout for interactive SDK shell invocations, since the
428 # user will not notice a longer wait because it's happening in the
431 return super(ChromeSDKCommand, self).upload_stats_timeout
433 return stats.StatsUploader.UPLOAD_TIMEOUT
436 def ValidateVersion(version):
437 if version.startswith('R') or len(version.split('.')) != 3:
438 raise argparse.ArgumentTypeError(
439 '--version should be in the format 3912.0.0')
443 def AddParser(cls, parser):
444 super(ChromeSDKCommand, cls).AddParser(parser)
446 '--board', required=True, help='The board SDK to use.')
448 '--bashrc', type=osutils.ExpandPath,
449 default=constants.CHROME_SDK_BASHRC,
450 help='A bashrc file used to set up the SDK shell environment. '
451 'Defaults to %s.' % constants.CHROME_SDK_BASHRC)
453 '--chroot', type=osutils.ExpandPath,
454 help='Path to a ChromeOS chroot to use. If set, '
455 '<chroot>/build/<board> will be used as the sysroot that Chrome '
456 'is built against. The version shown in the SDK shell prompt '
457 'will then have an asterisk prepended to it.')
459 '--chrome-src', type=osutils.ExpandPath,
460 help='Specifies the location of a Chrome src/ directory. Required if '
461 'running with --clang if not running from a Chrome checkout.')
463 '--clang', action='store_true', default=False,
464 help='Sets up the environment for building with clang.')
466 '--cwd', type=osutils.ExpandPath,
467 help='Specifies a directory to switch to after setting up the SDK '
468 'shell. Defaults to the current directory.')
470 '--internal', action='store_true', default=False,
471 help='Sets up SDK for building official (internal) Chrome '
472 'Chrome, rather than Chromium.')
474 '--sdk-path', type='local_or_gs_path',
475 help='Provides a path, whether a local directory or a gs:// path, to '
476 'pull SDK components from.')
478 '--toolchain-path', type='local_or_gs_path',
479 help='Provides a path, whether a local directory or a gs:// path, to '
480 'pull toolchain components from.')
482 '--nogoma', action='store_false', default=True, dest='goma',
483 help="Disables Goma in the shell by removing it from the PATH.")
485 '--version', default=None, type=cls.ValidateVersion,
486 help="Specify version of SDK to use, in the format '3912.0.0'. "
487 "Defaults to determining version based on the type of checkout "
488 "(Chrome or ChromeOS) you are executing from.")
490 'cmd', nargs='*', default=None,
491 help='The command to execute in the SDK environment. Defaults to '
492 'starting a bash shell.')
494 parser.add_option_to_group(
495 parser.caching_group, '--clear-sdk-cache', action='store_true',
497 help='Removes everything in the SDK cache before starting.')
499 group = parser.add_option_group('Metadata Overrides (Advanced)',
500 description='Provide all of these overrides in order to remove '
501 'dependencies on metadata.json existence.')
502 parser.add_option_to_group(
503 group, '--target-tc', action='store', default=None,
504 help='Override target toolchain name, e.g. x86_64-cros-linux-gnu')
505 parser.add_option_to_group(
506 group, '--toolchain-url', action='store', default=None,
507 help='Override toolchain url format pattern, e.g. '
508 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
510 def __init__(self, options):
511 cros.CrosCommand.__init__(self, options)
512 self.board = options.board
515 # Initialized later based on options passed in.
519 def _CreatePS1(board, version, chroot=None):
520 """Returns PS1 string that sets commandline and xterm window caption.
522 If a chroot path is set, then indicate we are using the sysroot from there
523 instead of the stock sysroot by prepending an asterisk to the version.
526 board: The SDK board.
527 version: The SDK version.
528 chroot: The path to the chroot, if set.
530 custom = '*' if chroot else ''
531 sdk_version = '(sdk %s %s%s)' % (board, custom, version)
532 label = '\\u@\\h: \\w'
533 window_caption = "\\[\\e]0;%(sdk_version)s %(label)s \\a\\]"
534 command_line = "%(sdk_version)s \\[\\e[1;33m\\]%(label)s \\$ \\[\\e[m\\]"
535 ps1 = window_caption + command_line
536 return (ps1 % {'sdk_version': sdk_version,
539 def _FixGoldPath(self, var_contents, toolchain_path):
540 """Point to the gold linker in the toolchain tarball.
542 Accepts an already set environment variable in the form of '<cmd>
543 -B<gold_path>', and overrides the gold_path to the correct path in the
544 extracted toolchain tarball.
547 var_contents: The contents of the environment variable.
548 toolchain_path: Path to the extracted toolchain tarball contents.
551 Environment string that has correct gold path.
553 cmd, _, gold_path = var_contents.partition(' -B')
554 gold_path = os.path.join(toolchain_path, gold_path.lstrip('/'))
555 return '%s -B%s' % (cmd, gold_path)
557 def _SetupTCEnvironment(self, sdk_ctx, options, env):
558 """Sets up toolchain-related environment variables."""
559 target_tc_path = sdk_ctx.key_map[self.sdk.TARGET_TOOLCHAIN_KEY].path
560 tc_bin_path = os.path.join(target_tc_path, 'bin')
561 env['PATH'] = '%s:%s' % (tc_bin_path, os.environ['PATH'])
563 for var in ('CXX', 'CC', 'LD'):
564 env[var] = self._FixGoldPath(env[var], target_tc_path)
566 clang_path = os.path.join(options.chrome_src, self._CLANG_DIR)
568 # Tell clang where to find the gcc headers and libraries.
569 flags = ['--gcc-toolchain=' + os.path.join(target_tc_path, 'usr'),
570 '--target=' + sdk_ctx.target_tc]
571 # TODO: It'd be nicer to inject these flags via some gyp variable.
572 # Note: It's important they're only passed to target targets, not host
573 # targets. They are intentionally added only to CC and not CC_host.
574 clang_bin = os.path.join(clang_path, 'clang')
575 env['CC'] = ' '.join([clang_bin] + flags + [env['CC'].split()[-1]])
576 clangxx_bin = os.path.join(clang_path, 'clang++')
577 env['CXX'] = ' '.join([clangxx_bin] + flags + [env['CXX'].split()[-1]])
579 # The host compiler intentionally doesn't use the libstdc++ from sdk_ctx,
580 # so that host binaries link against the system libstdc++ and can run
581 # without a special rpath.
582 env['CC_host'] = os.path.join(clang_path, 'clang')
583 env['CXX_host'] = os.path.join(clang_path, 'clang++')
585 def _SetupEnvironment(self, board, sdk_ctx, options, goma_dir=None,
587 """Sets environment variables to export to the SDK shell."""
589 sysroot = os.path.join(options.chroot, 'build', board)
590 if not os.path.isdir(sysroot) and not options.cmd:
591 logging.warning("Because --chroot is set, expected a sysroot to be at "
592 "%s, but couldn't find one.", sysroot)
594 sysroot = sdk_ctx.key_map[constants.CHROME_SYSROOT_TAR].path
596 environment = os.path.join(sdk_ctx.key_map[constants.CHROME_ENV_TAR].path,
598 env = osutils.SourceEnvironment(environment, self.EBUILD_ENV)
599 self._SetupTCEnvironment(sdk_ctx, options, env)
601 # Add managed components to the PATH.
602 env['PATH'] = '%s:%s' % (constants.CHROMITE_BIN_DIR, env['PATH'])
603 env['PATH'] = '%s:%s' % (os.path.dirname(self.sdk.gs_ctx.gsutil_bin),
606 # Export internally referenced variables.
607 os.environ[self.sdk.SDK_BOARD_ENV] = board
608 if self.options.sdk_path:
609 os.environ[self.sdk.SDK_PATH_ENV] = self.options.sdk_path
610 os.environ[self.sdk.SDK_VERSION_ENV] = sdk_ctx.version
612 # Export the board/version info in a more accessible way, so developers can
613 # reference them in their chrome_sdk.bashrc files, as well as within the
615 for var in [self.sdk.SDK_VERSION_ENV, self.sdk.SDK_BOARD_ENV]:
616 env[var.lstrip('%')] = os.environ[var]
618 # Export Goma information.
620 env[self.SDK_GOMA_DIR_ENV] = goma_dir
621 env[self.SDK_GOMA_PORT_ENV] = goma_port
623 # SYSROOT is necessary for Goma and the sysroot wrapper.
624 env['SYSROOT'] = sysroot
625 gyp_dict = chrome_util.ProcessGypDefines(env['GYP_DEFINES'])
626 gyp_dict['sysroot'] = sysroot
627 gyp_dict.pop('order_text_section', None)
628 gyp_dict['host_clang'] = 1
630 gyp_dict['clang'] = 1
632 gyp_dict['branding'] = 'Chrome'
633 gyp_dict['buildtype'] = 'Official'
635 gyp_dict.pop('branding', None)
636 gyp_dict.pop('buildtype', None)
637 gyp_dict.pop('internal_gles2_conform_tests', None)
639 # Enable goma if requested.
641 gyp_dict['use_goma'] = 1
642 gyp_dict['gomadir'] = goma_dir
645 # TODO(thakis): Remove once https://b/issue?id=16876457 is fixed.
646 gyp_dict['use_goma'] = 0
648 env['GYP_DEFINES'] = chrome_util.DictToGypDefines(gyp_dict)
650 # PS1 sets the command line prompt and xterm window caption.
651 full_version = sdk_ctx.version
652 if full_version != CUSTOM_VERSION:
653 full_version = self.sdk.GetFullVersion(sdk_ctx.version)
654 env['PS1'] = self._CreatePS1(self.board, full_version,
655 chroot=options.chroot)
657 out_dir = 'out_%s' % self.board
658 env['builddir_name'] = out_dir
659 env['GYP_GENERATOR_FLAGS'] = 'output_dir=%s' % out_dir
660 env['GYP_CROSSCOMPILE'] = '1'
664 def _VerifyGoma(user_rc):
665 """Verify that the user has no goma installations set up in user_rc.
667 If the user does have a goma installation set up, verify that it's for
671 user_rc: User-supplied rc file.
673 user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
674 goma_ctl = osutils.Which('goma_ctl.py', user_env.get('PATH'))
675 if goma_ctl is not None:
677 '%s is adding Goma to the PATH. Using that Goma instead of the '
678 'managed Goma install.', user_rc)
681 def _VerifyChromiteBin(user_rc):
682 """Verify that the user has not set a chromite bin/ dir in user_rc.
685 user_rc: User-supplied rc file.
687 user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
688 chromite_bin = osutils.Which('parallel_emerge', user_env.get('PATH'))
689 if chromite_bin is not None:
691 '%s is adding chromite/bin to the PATH. Remove it from the PATH to '
692 'use the the default Chromite.', user_rc)
694 @contextlib.contextmanager
695 def _GetRCFile(self, env, user_rc):
696 """Returns path to dynamically created bashrc file.
698 The bashrc file sets the environment variables contained in |env|, as well
699 as sources the user-editable chrome_sdk.bashrc file in the user's home
700 directory. That rc file is created if it doesn't already exist.
703 env: A dictionary of environment variables that will be set by the rc
705 user_rc: User-supplied rc file.
707 if not os.path.exists(user_rc):
708 osutils.Touch(user_rc, makedirs=True)
710 self._VerifyGoma(user_rc)
711 self._VerifyChromiteBin(user_rc)
713 # We need a temporary rc file to 'wrap' the user configuration file,
714 # because running with '--rcfile' causes bash to ignore bash special
715 # variables passed through subprocess.Popen, such as PS1. So we set them
718 # Having a wrapper rc file will also allow us to inject bash functions into
719 # the environment, not just variables.
720 with osutils.TempDir() as tempdir:
721 # Only source the user's ~/.bashrc if running in interactive mode.
723 '[[ -e ~/.bashrc && $- == *i* ]] && . ~/.bashrc\n',
726 for key, value in env.iteritems():
727 contents.append("export %s='%s'\n" % (key, value))
728 contents.append('. "%s"\n' % user_rc)
730 rc_file = os.path.join(tempdir, 'rcfile')
731 osutils.WriteFile(rc_file, contents)
734 def _GomaPort(self, goma_dir):
735 """Returns current active Goma port."""
736 port = cros_build_lib.RunCommand(
737 self.GOMACC_PORT_CMD, cwd=goma_dir, debug_level=logging.DEBUG,
738 error_code_ok=True, capture_output=True).output.strip()
741 def _FetchGoma(self):
742 """Fetch, install, and start Goma, using cached version if it exists.
745 A tuple (dir, port) containing the path to the cached goma/ dir and the
748 common_path = os.path.join(self.options.cache_dir, constants.COMMON_CACHE)
749 common_cache = cache.DiskCache(common_path)
751 ref = common_cache.Lookup(('goma', '2'))
753 Log('Installing Goma.', silent=self.silent)
754 with osutils.TempDir() as tempdir:
755 goma_dir = os.path.join(tempdir, 'goma')
757 result = cros_build_lib.DebugRunCommand(
758 self.FETCH_GOMA_CMD, cwd=goma_dir, error_code_ok=True)
759 if result.returncode:
760 raise GomaError('Failed to fetch Goma')
761 # Update to latest version of goma. We choose the outside-chroot version
762 # ('goobuntu') over the chroot version ('chromeos') by supplying
763 # input='1' to the following prompt:
765 # What is your platform?
766 # 1. Goobuntu 2. Precise (32bit) 3. Lucid (32bit) 4. Debian
767 # 5. Chrome OS 6. MacOS ? -->
768 cros_build_lib.DebugRunCommand(
769 ['python', 'goma_ctl.py', 'update'], cwd=goma_dir, input='1\n')
770 ref.SetDefault(goma_dir)
773 Log('Starting Goma.', silent=self.silent)
774 cros_build_lib.DebugRunCommand(
775 ['python', 'goma_ctl.py', 'ensure_start'], cwd=goma_dir)
776 port = self._GomaPort(goma_dir)
777 Log('Goma is started on port %s', port, silent=self.silent)
779 raise GomaError('No Goma port detected')
781 return goma_dir, port
784 """Perform the command."""
785 if os.environ.get(SDKFetcher.SDK_VERSION_ENV) is not None:
786 cros_build_lib.Die('Already in an SDK shell.')
788 if not self.options.chrome_src:
789 checkout = commandline.DetermineCheckout(os.getcwd())
790 self.options.chrome_src = checkout.chrome_src_dir
792 checkout = commandline.DetermineCheckout(self.options.chrome_src)
793 if not checkout.chrome_src_dir:
794 cros_build_lib.Die('Chrome checkout not found at %s',
795 self.options.chrome_src)
796 self.options.chrome_src = checkout.chrome_src_dir
798 if self.options.clang and not self.options.chrome_src:
799 cros_build_lib.Die('--clang requires --chrome-src to be set.')
801 if self.options.version and self.options.sdk_path:
802 cros_build_lib.Die('Cannot specify both --version and --sdk-path.')
804 self.silent = bool(self.options.cmd)
805 # Lazy initialize because SDKFetcher creates a GSContext() object in its
806 # constructor, which may block on user input.
807 self.sdk = SDKFetcher(self.options.cache_dir, self.options.board,
808 clear_cache=self.options.clear_sdk_cache,
809 chrome_src=self.options.chrome_src,
810 sdk_path=self.options.sdk_path,
811 toolchain_path=self.options.toolchain_path,
814 prepare_version = self.options.version
815 if not prepare_version and not self.options.sdk_path:
816 prepare_version, _ = self.sdk.UpdateDefaultVersion()
818 components = [self.sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR]
819 if not self.options.chroot:
820 components.append(constants.CHROME_SYSROOT_TAR)
824 if self.options.goma:
826 goma_dir, goma_port = self._FetchGoma()
827 except GomaError as e:
828 logging.error('Goma: %s. Bypass by running with --nogoma.', e)
830 with self.sdk.Prepare(components, version=prepare_version,
831 target_tc=self.options.target_tc,
832 toolchain_url=self.options.toolchain_url) as ctx:
833 env = self._SetupEnvironment(self.options.board, ctx, self.options,
834 goma_dir=goma_dir, goma_port=goma_port)
835 with self._GetRCFile(env, self.options.bashrc) as rcfile:
836 bash_cmd = ['/bin/bash']
839 if not self.options.cmd:
840 bash_cmd.extend(['--rcfile', rcfile, '-i'])
842 # The '"$@"' expands out to the properly quoted positional args
843 # coming after the '--'.
844 bash_cmd.extend(['-c', '"$@"', '--'])
845 bash_cmd.extend(self.options.cmd)
846 # When run in noninteractive mode, bash sources the rc file set in
847 # BASH_ENV, and ignores the --rcfile flag.
848 extra_env = {'BASH_ENV': rcfile}
850 # Bash behaves differently when it detects that it's being launched by
851 # sshd - it ignores the BASH_ENV variable. So prevent ssh-related
852 # environment variables from being passed through.
853 os.environ.pop('SSH_CLIENT', None)
854 os.environ.pop('SSH_CONNECTION', None)
855 os.environ.pop('SSH_TTY', None)
857 cmd_result = cros_build_lib.RunCommand(
858 bash_cmd, print_cmd=False, debug_level=logging.CRITICAL,
859 error_code_ok=True, extra_env=extra_env, cwd=self.options.cwd)
861 return cmd_result.returncode