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.buildbot import cbuildbot_config
25 from chromite.buildbot 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', 'metadata', '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 raw_json = self.gs_ctx.Cat(
129 os.path.join(version_base, constants.METADATA_JSON),
130 debug_level=logging.DEBUG).output
131 ref.AssignText(raw_json)
133 return json.loads(raw_json)
135 def _GetChromeLKGM(self, chrome_src_dir):
136 """Get ChromeOS LKGM checked into the Chrome tree.
139 Version number in format '3929.0.0'.
141 version = osutils.ReadFile(os.path.join(
142 chrome_src_dir, constants.PATH_TO_CHROME_LKGM))
145 def _GetRepoCheckoutVersion(self, repo_root):
146 """Get the version specified in chromeos_version.sh.
149 Version number in format '3929.0.0'.
151 chromeos_version_sh = os.path.join(repo_root, constants.VERSION_FILE)
152 sourced_env = osutils.SourceEnvironment(
153 chromeos_version_sh, ['CHROMEOS_VERSION_STRING'],
154 env={'CHROMEOS_OFFICIAL': '1'})
155 return sourced_env['CHROMEOS_VERSION_STRING']
157 def _GetNewestFullVersion(self, version=None):
158 """Gets the full version number of the latest build for the given |version|.
161 version: The version number or branch to look at. By default, look at
162 builds on the current branch.
165 Version number in the format 'R30-3929.0.0'.
168 version = git.GetChromiteTrackingBranch()
169 version_file = '%s/LATEST-%s' % (self.gs_base, version)
171 full_version = self.gs_ctx.Cat(version_file).output
172 assert full_version.startswith('R')
174 except gs.GSNoSuchKey:
177 def _GetNewestManifestVersion(self):
178 """Gets the latest uploaded SDK version.
181 Version number in the format '3929.0.0'.
183 full_version = self._GetNewestFullVersion()
184 return None if full_version is None else full_version.split('-')[1]
186 def GetDefaultVersion(self):
187 """Get the default SDK version to use.
189 If we are in an existing SDK shell, the default version will just be
190 the current version. Otherwise, we will try to calculate the
191 appropriate version to use based on the checkout.
193 if os.environ.get(self.SDK_BOARD_ENV) == self.board:
194 sdk_version = os.environ.get(self.SDK_VERSION_ENV)
195 if sdk_version is not None:
198 with self.misc_cache.Lookup((self.board, 'latest')) as ref:
199 if ref.Exists(lock=True):
200 version = osutils.ReadFile(ref.path).strip()
201 # Deal with the old version format.
202 if version.startswith('R'):
203 version = version.split('-')[1]
208 def _SetDefaultVersion(self, version):
209 """Set the new default version."""
210 with self.misc_cache.Lookup((self.board, 'latest')) as ref:
211 ref.AssignText(version)
213 def UpdateDefaultVersion(self):
214 """Update the version that we default to using.
217 A tuple of the form (version, updated), where |version| is the
218 version number in the format '3929.0.0', and |updated| indicates
219 whether the version was indeed updated.
221 checkout_dir = self.chrome_src if self.chrome_src else os.getcwd()
222 checkout = commandline.DetermineCheckout(checkout_dir)
223 current = self.GetDefaultVersion() or '0'
224 if checkout.chrome_src_dir:
225 target = self._GetChromeLKGM(checkout.chrome_src_dir)
226 elif checkout.type == commandline.CHECKOUT_TYPE_REPO:
227 target = self._GetRepoCheckoutVersion(checkout.root)
228 if target != current:
229 lv_cls = distutils.version.LooseVersion
230 if lv_cls(target) > lv_cls(current):
231 # Hit the network for the newest uploaded version for the branch.
232 newest = self._GetNewestManifestVersion()
233 # The SDK for the version of the checkout has not been uploaded yet,
234 # so fall back to the latest uploaded SDK.
235 if newest is not None and lv_cls(target) > lv_cls(newest):
238 target = self._GetNewestManifestVersion()
241 raise MissingSDK(self.board)
243 self._SetDefaultVersion(target)
244 return target, target != current
246 def GetFullVersion(self, version):
247 """Add the release branch to a ChromeOS platform version.
250 version: A ChromeOS platform number of the form XXXX.XX.XX, i.e.,
254 The version with release branch prepended. I.e., R28-3918.0.0.
256 assert not version.startswith('R')
258 with self.misc_cache.Lookup(('full-version', version)) as ref:
259 if ref.Exists(lock=True):
260 return osutils.ReadFile(ref.path).strip()
262 # Find out the newest version from the LATEST (or LATEST-%s) file.
263 full_version = self._GetNewestFullVersion(version=version)
265 if full_version is None:
266 raise MissingSDK(self.board, version)
268 ref.AssignText(full_version)
271 def _GetVersionGSBase(self, version):
272 """The base path of the SDK for a particular version."""
273 if self.sdk_path is not None:
276 full_version = self.GetFullVersion(version)
277 return os.path.join(self.gs_base, full_version)
279 def _GetCacheKeyForComponent(self, version, component):
280 """Builds the cache key tuple for an SDK component."""
281 version_section = version
282 if self.sdk_path is not None:
283 version_section = self.sdk_path.replace('/', '__')
284 return (self.board, version_section, component)
286 @contextlib.contextmanager
287 def Prepare(self, components, version=None):
288 """Ensures the components of an SDK exist and are read-locked.
290 For a given SDK version, pulls down missing components, and provides a
291 context where the components are read-locked, which prevents the cache from
292 deleting them during its purge operations.
295 gs_ctx: GSContext object.
296 components: A list of specific components(tarballs) to prepare.
297 version: The version to prepare. If not set, uses the version returned by
298 GetDefaultVersion(). If there is no default version set (this is the
299 first time we are being executed), then we update the default version.
302 An SDKFetcher.SDKContext namedtuple object. The attributes of the
304 version: The version that was prepared.
305 metadata: A dictionary containing the build metadata for the SDK
307 key_map: Dictionary that contains CacheReference objects for the SDK
308 artifacts, indexed by cache key.
310 if version is None and self.sdk_path is None:
311 version = self.GetDefaultVersion()
313 version, _ = self.UpdateDefaultVersion()
314 components = list(components)
319 metadata = self._GetMetadata(version)
320 # Fetch toolchains from separate location.
321 if self.TARGET_TOOLCHAIN_KEY in components:
322 tc_tuple = metadata['toolchain-tuple'][0]
323 fetch_urls[self.TARGET_TOOLCHAIN_KEY] = os.path.join(
324 'gs://', constants.SDK_GS_BUCKET,
325 metadata['toolchain-url'] % {'target': tc_tuple})
326 components.remove(self.TARGET_TOOLCHAIN_KEY)
328 version_base = self._GetVersionGSBase(version)
329 fetch_urls.update((t, os.path.join(version_base, t)) for t in components)
331 for key, url in fetch_urls.iteritems():
332 cache_key = self._GetCacheKeyForComponent(version, key)
333 ref = self.tarball_cache.Lookup(cache_key)
336 if not ref.Exists(lock=True):
337 # TODO(rcui): Parallelize this. Requires acquiring locks *before*
338 # generating worker processes; therefore the functionality needs to
339 # be moved into the DiskCache class itself -
340 # i.e.,DiskCache.ParallelSetDefault().
341 self._UpdateTarball(url, ref)
343 ctx_version = version
344 if self.sdk_path is not None:
345 ctx_version = CUSTOM_VERSION
346 yield self.SDKContext(ctx_version, metadata, key_map)
348 # TODO(rcui): Move to using cros_build_lib.ContextManagerStack()
349 cros_build_lib.SafeRun([ref.Release for ref in key_map.itervalues()])
352 class GomaError(Exception):
353 """Indicates error with setting up Goma."""
356 class ClangError(Exception):
357 """Indicates error with setting up Clang."""
361 @cros.CommandDecorator(COMMAND_NAME)
362 class ChromeSDKCommand(cros.CrosCommand):
363 """Set up an environment for building Chrome on Chrome OS.
365 Pulls down SDK components for building and testing Chrome for Chrome OS,
366 sets up the environment for building Chrome, and runs a command in the
367 environment, starting a bash session if no command is specified.
369 The bash session environment is set up by a user-configurable rc file located
370 at ~/.chromite/chrome_sdk.bashrc.
373 # Note, this URL is not accessible outside of corp.
374 _GOMA_URL = ('https://clients5.google.com/cxx-compiler-service/'
375 'download/goma_ctl.py')
377 _CLANG_DIR = 'third_party/llvm-build/Release+Asserts/bin'
378 _CLANG_UPDATE_SH = 'tools/clang/scripts/update.sh'
391 SDK_GOMA_PORT_ENV = 'SDK_GOMA_PORT'
392 SDK_GOMA_DIR_ENV = 'SDK_GOMA_DIR'
394 GOMACC_PORT_CMD = ['./gomacc', 'port']
395 FETCH_GOMA_CMD = ['wget', _GOMA_URL]
397 # Override base class property to enable stats upload.
400 # Override base class property to use cache related commandline options.
401 use_caching_options = True
404 def upload_stats_timeout(self):
405 # Give a longer timeout for interactive SDK shell invocations, since the
406 # user will not notice a longer wait because it's happening in the
409 return super(ChromeSDKCommand, self).upload_stats_timeout
411 return stats.StatsUploader.UPLOAD_TIMEOUT
414 def ValidateVersion(version):
415 if version.startswith('R') or len(version.split('.')) != 3:
416 raise argparse.ArgumentTypeError(
417 '--version should be in the format 3912.0.0')
421 def AddParser(cls, parser):
422 def ExpandGSPath(path):
423 """Expand a path, possibly a gs:// URL."""
424 if path.startswith(gs.BASE_GS_URL):
426 return osutils.ExpandPath(path)
428 super(ChromeSDKCommand, cls).AddParser(parser)
430 '--board', required=True, help='The board SDK to use.')
432 '--bashrc', type=osutils.ExpandPath,
433 default=constants.CHROME_SDK_BASHRC,
434 help='A bashrc file used to set up the SDK shell environment. '
435 'Defaults to %s.' % constants.CHROME_SDK_BASHRC)
437 '--chroot', type=osutils.ExpandPath,
438 help='Path to a ChromeOS chroot to use. If set, '
439 '<chroot>/build/<board> will be used as the sysroot that Chrome '
440 'is built against. The version shown in the SDK shell prompt '
441 'will then have an asterisk prepended to it.')
443 '--chrome-src', type=osutils.ExpandPath,
444 help='Specifies the location of a Chrome src/ directory. Required if '
445 'running with --clang if not running from a Chrome checkout.')
447 '--clang', action='store_true', default=False,
448 help='Sets up the environment for building with clang. Due to a bug '
449 'with ninja, requires --make and possibly --chrome-src to be set.')
451 '--cwd', type=osutils.ExpandPath,
452 help='Specifies a directory to switch to after setting up the SDK '
453 'shell. Defaults to the current directory.')
455 '--internal', action='store_true', default=False,
456 help='Sets up SDK for building official (internal) Chrome '
457 'Chrome, rather than Chromium.')
459 '--sdk-path', type=ExpandGSPath,
460 help='Provides a path, whether a local directory or a gs:// path, to '
461 'pull SDK components from.')
463 '--make', action='store_true', default=False,
464 help='If set, gyp_chromium will generate Make files instead of Ninja '
465 'files. Note: Make files are spread out through the source tree, '
466 'and not concentrated in the out_<board> directory, so you can '
467 'only have one Make config running at a time.')
469 '--nogoma', action='store_false', default=True, dest='goma',
470 help="Disables Goma in the shell by removing it from the PATH.")
472 '--version', default=None, type=cls.ValidateVersion,
473 help="Specify version of SDK to use, in the format '3912.0.0'. "
474 "Defaults to determining version based on the type of checkout "
475 "(Chrome or ChromeOS) you are executing from.")
477 'cmd', nargs='*', default=None,
478 help='The command to execute in the SDK environment. Defaults to '
479 'starting a bash shell.')
481 parser.add_option_to_group(
482 parser.caching_group, '--clear-sdk-cache', action='store_true',
484 help='Removes everything in the SDK cache before starting.')
486 def __init__(self, options):
487 cros.CrosCommand.__init__(self, options)
488 self.board = options.board
491 # Initialized later based on options passed in.
495 def _CreatePS1(board, version, chroot=None):
496 """Returns PS1 string that sets commandline and xterm window caption.
498 If a chroot path is set, then indicate we are using the sysroot from there
499 instead of the stock sysroot by prepending an asterisk to the version.
502 board: The SDK board.
503 version: The SDK version.
504 chroot: The path to the chroot, if set.
506 custom = '*' if chroot else ''
507 sdk_version = '(sdk %s %s%s)' % (board, custom, version)
508 label = '\\u@\\h: \\w'
509 window_caption = "\\[\\e]0;%(sdk_version)s %(label)s \\a\\]"
510 command_line = "%(sdk_version)s \\[\\e[1;33m\\]%(label)s \\$ \\[\\e[m\\]"
511 ps1 = window_caption + command_line
512 return (ps1 % {'sdk_version': sdk_version,
515 def _FixGoldPath(self, var_contents, toolchain_path):
516 """Point to the gold linker in the toolchain tarball.
518 Accepts an already set environment variable in the form of '<cmd>
519 -B<gold_path>', and overrides the gold_path to the correct path in the
520 extracted toolchain tarball.
523 var_contents: The contents of the environment variable.
524 toolchain_path: Path to the extracted toolchain tarball contents.
527 Environment string that has correct gold path.
529 cmd, _, gold_path = var_contents.partition(' -B')
530 gold_path = os.path.join(toolchain_path, gold_path.lstrip('/'))
531 return '%s -B%s' % (cmd, gold_path)
533 def _SetupTCEnvironment(self, sdk_ctx, options, env):
534 """Sets up toolchain-related environment variables."""
535 target_tc = sdk_ctx.key_map[self.sdk.TARGET_TOOLCHAIN_KEY].path
536 tc_bin = os.path.join(target_tc, 'bin')
537 env['PATH'] = '%s:%s' % (tc_bin, os.environ['PATH'])
539 for var in ('CXX', 'CC', 'LD'):
540 env[var] = self._FixGoldPath(env[var], target_tc)
543 # clang++ requires C++ header paths to be explicitly specified.
544 # See discussion on crbug.com/86037.
545 tc_tuple = sdk_ctx.metadata['toolchain-tuple'][0]
546 gcc_path = os.path.join(tc_bin, '%s-gcc' % tc_tuple)
547 gcc_version = cros_build_lib.DebugRunCommand(
548 [gcc_path, '-dumpversion'], redirect_stdout=True).output.strip()
549 gcc_lib = 'usr/lib/gcc/%(tuple)s/%(ver)s/include/g++-v%(major_ver)s' % {
552 'major_ver': gcc_version[0],
554 tc_gcc_lib = os.path.join(target_tc, gcc_lib)
556 for p in ('', tc_tuple, 'backward'):
557 includes.append('-isystem %s' % os.path.join(tc_gcc_lib, p))
559 env['CXX'] = 'clang++ %s' % ' '.join(includes)
561 env['CXX_host'] = 'g++'
562 env['CC_host'] = 'gcc'
565 clang_path = os.path.join(options.chrome_src, self._CLANG_DIR)
566 env['PATH'] = '%s:%s' % (clang_path, env['PATH'])
568 def _SetupEnvironment(self, board, sdk_ctx, options, goma_dir=None,
570 """Sets environment variables to export to the SDK shell."""
572 sysroot = os.path.join(options.chroot, 'build', board)
573 if not os.path.isdir(sysroot) and not options.cmd:
574 logging.warning("Because --chroot is set, expected a sysroot to be at "
575 "%s, but couldn't find one.", sysroot)
577 sysroot = sdk_ctx.key_map[constants.CHROME_SYSROOT_TAR].path
579 environment = os.path.join(sdk_ctx.key_map[constants.CHROME_ENV_TAR].path,
581 env = osutils.SourceEnvironment(environment, self.EBUILD_ENV)
582 self._SetupTCEnvironment(sdk_ctx, options, env)
584 # Add managed components to the PATH.
585 env['PATH'] = '%s:%s' % (constants.CHROMITE_BIN_DIR, env['PATH'])
586 env['PATH'] = '%s:%s' % (os.path.dirname(self.sdk.gs_ctx.gsutil_bin),
589 # Export internally referenced variables.
590 os.environ[self.sdk.SDK_BOARD_ENV] = board
591 if self.options.sdk_path:
592 os.environ[self.sdk.SDK_PATH_ENV] = self.options.sdk_path
593 os.environ[self.sdk.SDK_VERSION_ENV] = sdk_ctx.version
595 # Export the board/version info in a more accessible way, so developers can
596 # reference them in their chrome_sdk.bashrc files, as well as within the
598 for var in [self.sdk.SDK_VERSION_ENV, self.sdk.SDK_BOARD_ENV]:
599 env[var.lstrip('%')] = os.environ[var]
601 # Export Goma information.
603 env[self.SDK_GOMA_DIR_ENV] = goma_dir
604 env[self.SDK_GOMA_PORT_ENV] = goma_port
606 # SYSROOT is necessary for Goma and the sysroot wrapper.
607 env['SYSROOT'] = sysroot
608 gyp_dict = chrome_util.ProcessGypDefines(env['GYP_DEFINES'])
609 gyp_dict['sysroot'] = sysroot
610 gyp_dict.pop('order_text_section', None)
612 gyp_dict['clang'] = 1
613 gyp_dict['werror'] = ''
614 gyp_dict['clang_use_chrome_plugins'] = 0
615 gyp_dict['linux_use_tcmalloc'] = 0
617 gyp_dict['branding'] = 'Chrome'
618 gyp_dict['buildtype'] = 'Official'
620 gyp_dict.pop('branding', None)
621 gyp_dict.pop('buildtype', None)
623 # Enable goma if requested.
625 gyp_dict['use_goma'] = 1
626 gyp_dict['gomadir'] = goma_dir
628 env['GYP_DEFINES'] = chrome_util.DictToGypDefines(gyp_dict)
630 # PS1 sets the command line prompt and xterm window caption.
631 full_version = sdk_ctx.version
632 if full_version != CUSTOM_VERSION:
633 full_version = self.sdk.GetFullVersion(sdk_ctx.version)
634 env['PS1'] = self._CreatePS1(self.board, full_version,
635 chroot=options.chroot)
637 out_dir = 'out_%s' % self.board
638 env['builddir_name'] = out_dir
639 env['GYP_GENERATORS'] = 'make' if options.make else 'ninja'
640 env['GYP_GENERATOR_FLAGS'] = 'output_dir=%s' % out_dir
644 def _VerifyGoma(user_rc):
645 """Verify that the user has no goma installations set up in user_rc.
647 If the user does have a goma installation set up, verify that it's for
651 user_rc: User-supplied rc file.
653 user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
654 goma_ctl = osutils.Which('goma_ctl.py', user_env.get('PATH'))
655 if goma_ctl is not None:
657 '%s is adding Goma to the PATH. Using that Goma instead of the '
658 'managed Goma install.', user_rc)
661 def _VerifyChromiteBin(user_rc):
662 """Verify that the user has not set a chromite bin/ dir in user_rc.
665 user_rc: User-supplied rc file.
667 user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
668 chromite_bin = osutils.Which('parallel_emerge', user_env.get('PATH'))
669 if chromite_bin is not None:
671 '%s is adding chromite/bin to the PATH. Remove it from the PATH to '
672 'use the the default Chromite.', user_rc)
675 def _VerifyClang(user_rc):
676 """Verify that the user has not set a clang bin/ dir in user_rc.
679 user_rc: User-supplied rc file.
681 user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
682 clang_bin = osutils.Which('clang', user_env.get('PATH'))
683 if clang_bin is not None:
684 clang_dir = os.path.dirname(clang_bin)
685 if not osutils.Which('goma_ctl.py', clang_dir):
687 '%s is adding Clang to the PATH. Because of this, Goma is being '
688 'bypassed. Remove it from the PATH to use Goma with the default '
691 @contextlib.contextmanager
692 def _GetRCFile(self, env, user_rc):
693 """Returns path to dynamically created bashrc file.
695 The bashrc file sets the environment variables contained in |env|, as well
696 as sources the user-editable chrome_sdk.bashrc file in the user's home
697 directory. That rc file is created if it doesn't already exist.
700 env: A dictionary of environment variables that will be set by the rc
702 user_rc: User-supplied rc file.
704 if not os.path.exists(user_rc):
705 osutils.Touch(user_rc, makedirs=True)
707 self._VerifyGoma(user_rc)
708 self._VerifyChromiteBin(user_rc)
709 if self.options.clang:
710 self._VerifyClang(user_rc)
712 # We need a temporary rc file to 'wrap' the user configuration file,
713 # because running with '--rcfile' causes bash to ignore bash special
714 # variables passed through subprocess.Popen, such as PS1. So we set them
717 # Having a wrapper rc file will also allow us to inject bash functions into
718 # the environment, not just variables.
719 with osutils.TempDir() as tempdir:
720 # Only source the user's ~/.bashrc if running in interactive mode.
722 '[[ -e ~/.bashrc && $- == *i* ]] && . ~/.bashrc\n',
725 for key, value in env.iteritems():
726 contents.append("export %s='%s'\n" % (key, value))
727 contents.append('. "%s"\n' % user_rc)
729 rc_file = os.path.join(tempdir, 'rcfile')
730 osutils.WriteFile(rc_file, contents)
733 def _GomaPort(self, goma_dir):
734 """Returns current active Goma port."""
735 port = cros_build_lib.RunCommand(
736 self.GOMACC_PORT_CMD, cwd=goma_dir, debug_level=logging.DEBUG,
737 error_code_ok=True, capture_output=True).output.strip()
740 def _FetchGoma(self):
741 """Fetch, install, and start Goma, using cached version if it exists.
744 A tuple (dir, port) containing the path to the cached goma/ dir and the
747 common_path = os.path.join(self.options.cache_dir, constants.COMMON_CACHE)
748 common_cache = cache.DiskCache(common_path)
750 ref = common_cache.Lookup(('goma', '2'))
752 Log('Installing Goma.', silent=self.silent)
753 with osutils.TempDir() as tempdir:
754 goma_dir = os.path.join(tempdir, 'goma')
756 result = cros_build_lib.DebugRunCommand(
757 self.FETCH_GOMA_CMD, cwd=goma_dir, error_code_ok=True)
758 if result.returncode:
759 raise GomaError('Failed to fetch Goma')
760 # Update to latest version of goma. We choose the outside-chroot version
761 # ('goobuntu') over the chroot version ('chromeos') by supplying
762 # input='1' to the following prompt:
764 # What is your platform?
765 # 1. Goobuntu 2. Precise (32bit) 3. Lucid (32bit) 4. Debian
766 # 5. Chrome OS 6. MacOS ? -->
767 cros_build_lib.DebugRunCommand(
768 ['python', 'goma_ctl.py', 'update'], cwd=goma_dir, input='1\n')
769 ref.SetDefault(goma_dir)
772 Log('Starting Goma.', silent=self.silent)
773 cros_build_lib.DebugRunCommand(
774 ['python', 'goma_ctl.py', 'ensure_start'], cwd=goma_dir)
775 port = self._GomaPort(goma_dir)
776 Log('Goma is started on port %s', port, silent=self.silent)
778 raise GomaError('No Goma port detected')
780 return goma_dir, port
782 def _SetupClang(self):
783 """Install clang if needed."""
784 clang_path = os.path.join(self.options.chrome_src, self._CLANG_DIR)
785 if not os.path.exists(clang_path):
787 update_sh = os.path.join(self.options.chrome_src, self._CLANG_UPDATE_SH)
788 if not os.path.isfile(update_sh):
789 raise ClangError('%s not found.' % update_sh)
790 results = cros_build_lib.DebugRunCommand(
791 [update_sh], cwd=self.options.chrome_src, error_code_ok=True)
792 if results.returncode:
793 raise ClangError('Clang update failed with error code %s' %
794 (results.returncode,))
795 if not os.path.exists(clang_path):
796 raise ClangError('%s not found.' % clang_path)
797 except ClangError as e:
798 logging.error('Encountered errors while installing/updating clang: %s',
802 """Perform the command."""
803 if os.environ.get(SDKFetcher.SDK_VERSION_ENV) is not None:
804 cros_build_lib.Die('Already in an SDK shell.')
806 if self.options.clang and not self.options.make:
807 cros_build_lib.Die('--clang requires --make to be set.')
809 if not self.options.chrome_src:
810 checkout = commandline.DetermineCheckout(os.getcwd())
811 self.options.chrome_src = checkout.chrome_src_dir
813 checkout = commandline.DetermineCheckout(self.options.chrome_src)
814 if not checkout.chrome_src_dir:
815 cros_build_lib.Die('Chrome checkout not found at %s',
816 self.options.chrome_src)
817 self.options.chrome_src = checkout.chrome_src_dir
819 if self.options.clang and not self.options.chrome_src:
820 cros_build_lib.Die('--clang requires --chrome-src to be set.')
822 if self.options.version and self.options.sdk_path:
823 cros_build_lib.Die('Cannot specify both --version and --sdk-path.')
825 self.silent = bool(self.options.cmd)
826 # Lazy initialize because SDKFetcher creates a GSContext() object in its
827 # constructor, which may block on user input.
828 self.sdk = SDKFetcher(self.options.cache_dir, self.options.board,
829 clear_cache=self.options.clear_sdk_cache,
830 chrome_src=self.options.chrome_src,
831 sdk_path=self.options.sdk_path,
834 prepare_version = self.options.version
835 if not prepare_version and not self.options.sdk_path:
836 prepare_version, _ = self.sdk.UpdateDefaultVersion()
838 components = [self.sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR]
839 if not self.options.chroot:
840 components.append(constants.CHROME_SYSROOT_TAR)
844 if self.options.goma:
846 goma_dir, goma_port = self._FetchGoma()
847 except GomaError as e:
848 logging.error('Goma: %s. Bypass by running with --nogoma.', e)
850 if self.options.clang:
853 with self.sdk.Prepare(components, version=prepare_version) as ctx:
854 env = self._SetupEnvironment(self.options.board, ctx, self.options,
855 goma_dir=goma_dir, goma_port=goma_port)
856 with self._GetRCFile(env, self.options.bashrc) as rcfile:
857 bash_cmd = ['/bin/bash']
860 if not self.options.cmd:
861 bash_cmd.extend(['--rcfile', rcfile, '-i'])
863 # The '"$@"' expands out to the properly quoted positional args
864 # coming after the '--'.
865 bash_cmd.extend(['-c', '"$@"', '--'])
866 bash_cmd.extend(self.options.cmd)
867 # When run in noninteractive mode, bash sources the rc file set in
868 # BASH_ENV, and ignores the --rcfile flag.
869 extra_env = {'BASH_ENV': rcfile}
871 # Bash behaves differently when it detects that it's being launched by
872 # sshd - it ignores the BASH_ENV variable. So prevent ssh-related
873 # environment variables from being passed through.
874 os.environ.pop('SSH_CLIENT', None)
875 os.environ.pop('SSH_CONNECTION', None)
876 os.environ.pop('SSH_TTY', None)
878 cmd_result = cros_build_lib.RunCommand(
879 bash_cmd, print_cmd=False, debug_level=logging.CRITICAL,
880 error_code_ok=True, extra_env=extra_env, cwd=self.options.cwd)
882 return cmd_result.returncode