예제 #1
0
  def testPath(self):
    """Check $PATH/path handling."""
    self.assertEqual(self.prog_path, osutils.Which('prog'))

    os.environ['PATH'] = ''
    self.assertEqual(None, osutils.Which('prog'))

    self.assertEqual(self.prog_path, osutils.Which('prog', path=self.tempdir))
예제 #2
0
    def _VerifyClang(user_rc):
        """Verify that the user has not set a clang bin/ dir in user_rc.

    Arguments:
      user_rc: User-supplied rc file.
    """
        user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
        clang_bin = osutils.Which('clang', user_env.get('PATH'))
        if clang_bin is not None:
            clang_dir = os.path.dirname(clang_bin)
            if not osutils.Which('goma_ctl.sh', clang_dir):
                logging.warning(
                    '%s is adding Clang to the PATH.  Because of this, Goma is being '
                    'bypassed.  Remove it from the PATH to use Goma with the default '
                    'Clang.', user_rc)
예제 #3
0
파일: gs.py 프로젝트: xiicloud/chromite
 def GetDefaultGSUtilBin(cls):
     if cls.DEFAULT_GSUTIL_BIN is None:
         gsutil_bin = cls.DEFAULT_GSUTIL_BUILDER_BIN
         if not os.path.exists(gsutil_bin):
             gsutil_bin = osutils.Which('gsutil')
         cls.DEFAULT_GSUTIL_BIN = gsutil_bin
     return cls.DEFAULT_GSUTIL_BIN
예제 #4
0
파일: gs.py 프로젝트: sjg20/chromite
    def GetDefaultGSUtilBin(cls, cache_dir=None):
        if cls.DEFAULT_GSUTIL_BIN is None:
            if cache_dir is None:
                cache_dir = path_util.GetCacheDir()
            if cache_dir is not None:
                common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
                tar_cache = cache.TarballCache(common_path)
                key = (cls.GSUTIL_TAR, )
                # The common cache will not be LRU, removing the need to hold a read
                # lock on the cached gsutil.
                ref = tar_cache.Lookup(key)
                ref.SetDefault(cls.GSUTIL_URL)
                cls.DEFAULT_GSUTIL_BIN = os.path.join(ref.path, 'gsutil',
                                                      'gsutil')
            else:
                # Check if the default gsutil path for builders exists. If
                # not, try locating gsutil. If none exists, simply use 'gsutil'.
                gsutil_bin = cls.DEFAULT_GSUTIL_BUILDER_BIN
                if not os.path.exists(gsutil_bin):
                    gsutil_bin = osutils.Which('gsutil')
                if gsutil_bin is None:
                    gsutil_bin = 'gsutil'
                cls.DEFAULT_GSUTIL_BIN = gsutil_bin

        return cls.DEFAULT_GSUTIL_BIN
예제 #5
0
    def _SetQemuPath(self):
        """Find a suitable Qemu executable."""
        qemu_exe = 'qemu-system-x86_64'
        qemu_exe_path = os.path.join('usr/bin', qemu_exe)

        # Check SDK cache.
        if not self.qemu_path:
            qemu_dir = self._CachePathForKey(
                cros_chrome_sdk.SDKFetcher.QEMU_BIN_KEY)
            if qemu_dir:
                qemu_path = os.path.join(qemu_dir, qemu_exe_path)
                if os.path.isfile(qemu_path):
                    self.qemu_path = qemu_path

        # Check chroot.
        if not self.qemu_path:
            qemu_path = os.path.join(constants.SOURCE_ROOT,
                                     constants.DEFAULT_CHROOT_DIR,
                                     qemu_exe_path)
            if os.path.isfile(qemu_path):
                self.qemu_path = qemu_path

        # Check system.
        if not self.qemu_path:
            self.qemu_path = osutils.Which(qemu_exe)

        if not self.qemu_path or not os.path.isfile(self.qemu_path):
            raise VMError('QEMU not found.')
        logging.debug('QEMU path: %s', self.qemu_path)
        self._CheckQemuMinVersion()
예제 #6
0
 def GetCrossGdb(self):
     """Find the appropriate cross-version of gdb for the board."""
     toolchains = toolchain.GetToolchainsForBoard(self.board)
     tc = toolchain.FilterToolchains(toolchains, 'default', True).keys()
     cross_gdb = tc[0] + '-gdb'
     if not osutils.Which(cross_gdb):
         raise GdbMissingDebuggerError('Cannot find %s; do you need to run '
                                       'setup_board?' % cross_gdb)
     return cross_gdb
예제 #7
0
    def _VerifyChromiteBin(user_rc):
        """Verify that the user has not set a chromite bin/ dir in user_rc.

    Args:
      user_rc: User-supplied rc file.
    """
        user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
        chromite_bin = osutils.Which('parallel_emerge', user_env.get('PATH'))
        if chromite_bin is not None:
            logging.warning(
                '%s is adding chromite/bin to the PATH.  Remove it from the PATH to '
                'use the the default Chromite.', user_rc)
예제 #8
0
 def testMode(self):
   """Check mode handling."""
   self.assertEqual(self.prog_path, osutils.Which('prog'))
   self.assertEqual(self.prog_path, osutils.Which('prog', mode=os.X_OK))
   self.assertEqual(self.prog_path, osutils.Which('prog', mode=os.R_OK))
   self.assertEqual(None, osutils.Which('text'))
   self.assertEqual(None, osutils.Which('text', mode=os.X_OK))
   self.assertEqual(self.text_path, osutils.Which('text', mode=os.F_OK))
예제 #9
0
    def _VerifyGoma(user_rc):
        """Verify that the user has no goma installations set up in user_rc.

    If the user does have a goma installation set up, verify that it's for
    ChromeOS.

    Args:
      user_rc: User-supplied rc file.
    """
        user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
        goma_ctl = osutils.Which('goma_ctl.py', user_env.get('PATH'))
        if goma_ctl is not None:
            logging.warning(
                '%s is adding Goma to the PATH.  Using that Goma instead of the '
                'managed Goma install.', user_rc)
예제 #10
0
    def _VerifyGoma(user_rc):
        """Verify that the user's goma installation is working.

    Arguments:
      user_rc: User-supplied rc file.
    """
        user_env = osutils.SourceEnvironment(user_rc, ['PATH'], env=True)
        goma_ctl = osutils.Which('goma_ctl.sh', user_env.get('PATH'))
        if goma_ctl is not None:
            manifest = os.path.join(os.path.dirname(goma_ctl), 'MANIFEST')
            platform_env = osutils.SourceEnvironment(manifest, ['PLATFORM'])
            platform = platform_env.get('PLATFORM')
            if platform is not None and platform != 'chromeos':
                cros_build_lib.Warning('Found %s version of Goma in PATH.',
                                       platform)
                cros_build_lib.Warning('Goma will not work')
예제 #11
0
def _SudoCommand():
    """Get the 'sudo' command, along with all needed environment variables."""

    # Pass in the ENVIRONMENT_WHITELIST variable so that scripts in the chroot
    # know what variables to pass through.
    cmd = ['sudo']
    for key in constants.CHROOT_ENVIRONMENT_WHITELIST:
        value = os.environ.get(key)
        if value is not None:
            cmd += ['%s=%s' % (key, value)]

    # Pass in the path to the depot_tools so that users can access them from
    # within the chroot.
    gclient = osutils.Which('gclient')
    if gclient is not None:
        cmd += ['DEPOT_TOOLS=%s' % os.path.realpath(os.path.dirname(gclient))]

    return cmd
예제 #12
0
    def __init__(self, options):
        """Constructor.

    Args:
      options: In addition to the flags required by the base class, need to
        specify:
        * chromium_dir: Optional. If specified, use the chromium repo the path
          points to. Otherwise, use base_dir/chromium/src.
        * build_dir: Optional. Store build result to it if specified. Default:
          base_dir/build.
        * archive_build: True to archive build.
        * reuse_build: True to reuse previous build.
    """
        super(SimpleChromeBuilder, self).__init__(options)
        self.reuse_build = options.reuse_build
        self.archive_build = options.archive_build

        if 'chromium_dir' in options and options.chromium_dir:
            self.chromium_dir = options.chromium_dir
            self.repo_dir = os.path.join(self.chromium_dir, 'src')
        else:
            self.chromium_dir = os.path.join(self.base_dir, CHROMIUM_DIR)
            self.repo_dir = os.path.join(self.base_dir, self.DEFAULT_REPO_DIR)

        if 'build_dir' in options and options.build_dir:
            self.archive_base = options.build_dir
        else:
            self.archive_base = os.path.join(self.base_dir, 'build')
        if self.archive_build:
            osutils.SafeMakedirs(self.archive_base)

        self.gclient = osutils.Which('gclient')
        if not self.gclient:
            self.gclient = os.path.join(constants.DEPOT_TOOLS_DIR, 'gclient')

        self.verbose = logging.getLogger().isEnabledFor(logging.DEBUG)
        self.chrome_sdk = commands.ChromeSDK(self.repo_dir,
                                             self.board,
                                             goma=True,
                                             debug_log=self.verbose)

        # log_output=True: Instead of emitting output to STDOUT, redirect output to
        # logging.info.
        self.log_output_args = {'log_output': True}
예제 #13
0
  def Start(self):
    """Start the VM."""

    self.Stop()

    logging.debug('Start VM')
    if not self.qemu_path:
      self.qemu_path = osutils.Which('qemu-system-x86_64')
    if not self.qemu_path:
      raise VMError('qemu not found.')
    logging.debug('qemu path=%s', self.qemu_path)

    if not self.image_path:
      self.image_path = os.environ.get('VM_IMAGE_PATH', '')
    logging.debug('vm image path=%s', self.image_path)
    if not self.image_path or not os.path.exists(self.image_path):
      raise VMError('VM image path %s does not exist.' % self.image_path)

    self._CleanupFiles(recreate=True)
    open(self.kvm_serial, 'w')
    for pipe in [self.kvm_pipe_in, self.kvm_pipe_out]:
      os.mkfifo(pipe, 0600)

    args = [self.qemu_path, '-m', '2G', '-smp', '4', '-vga', 'cirrus',
            '-daemonize',
            '-pidfile', self.pidfile,
            '-chardev', 'pipe,id=control_pipe,path=%s' % self.kvm_monitor,
            '-serial', 'file:%s' % self.kvm_serial,
            '-mon', 'chardev=control_pipe',
            '-net', 'nic,model=virtio',
            '-net', 'user,hostfwd=tcp::%d-:22' % self.ssh_port,
            '-drive', 'file=%s,index=0,media=disk,cache=unsafe'
            % self.image_path]
    if self.enable_kvm:
      args.append('-enable-kvm')
    if not self.display:
      args.extend(['-display', 'none'])
    logging.info(' '.join(args))
    logging.info('Pid file: %s', self.pidfile)
    if not self.dry_run:
      self._RunCommand(args)
예제 #14
0
    def _VerifyGoma(user_rc):
        """Verify that the user has no goma installations set up in user_rc.

    If the user does have a goma installation set up, verify that it's for
    ChromeOS.

    Arguments:
      user_rc: User-supplied rc file.
    """
        user_env = osutils.SourceEnvironment(user_rc, ['PATH'])
        goma_ctl = osutils.Which('goma_ctl.sh', user_env.get('PATH'))
        if goma_ctl is not None:
            logging.warning(
                '%s is adding Goma to the PATH.  Using that Goma instead of the '
                'managed Goma install.', user_rc)
            manifest = os.path.join(os.path.dirname(goma_ctl), 'MANIFEST')
            platform_env = osutils.SourceEnvironment(manifest, ['PLATFORM'])
            platform = platform_env.get('PLATFORM')
            if platform is not None and platform != 'chromeos':
                logging.warning('Found %s version of Goma in PATH.', platform)
                logging.warning('Goma will not work')
예제 #15
0
    def _SetQemuPath(self):
        """Find a suitable Qemu executable."""
        qemu_exe = 'qemu-system-x86_64'
        qemu_exe_path = os.path.join('usr/bin', qemu_exe)

        # Check SDK cache.
        if not self.qemu_path:
            qemu_dir = cros_chrome_sdk.SDKFetcher.GetCachePath(
                cros_chrome_sdk.SDKFetcher.QEMU_BIN_PATH, self.cache_dir,
                self.board)
            if qemu_dir:
                qemu_path = os.path.join(qemu_dir, qemu_exe_path)
                if os.path.isfile(qemu_path):
                    self.qemu_path = qemu_path

        # Check chroot.
        if not self.qemu_path:
            qemu_path = os.path.join(self.chroot_path, qemu_exe_path)
            if os.path.isfile(qemu_path):
                self.qemu_path = qemu_path

        # Check system.
        if not self.qemu_path:
            self.qemu_path = osutils.Which(qemu_exe)

        if not self.qemu_path or not os.path.isfile(self.qemu_path):
            raise VMError('QEMU not found.')

        if self.copy_on_write:
            if not self.qemu_img_path:
                # Look for qemu-img right next to qemu-system-x86_64.
                self.qemu_img_path = os.path.join(
                    os.path.dirname(self.qemu_path), 'qemu-img')
            if not os.path.isfile(self.qemu_img_path):
                raise VMError(
                    'qemu-img not found. (Needed to create qcow2 image).')

        logging.debug('QEMU path: %s', self.qemu_path)
        self._CheckQemuMinVersion()
예제 #16
0
def main(argv):
    parser = GetParser()
    options = parser.parse_args(argv)

    if options.gclient == '':
        parser.error('--gclient can not be an empty string!')
    gclient_path = options.gclient or osutils.Which('gclient')
    if not gclient_path:
        gclient_path = os.path.join(constants.DEPOT_TOOLS_DIR, 'gclient')

    try:
        if options.reset:
            # Revert any lingering local changes.
            gclient.Revert(gclient_path, options.chrome_root)

        SyncChrome(gclient_path, options)
    except cros_build_lib.RunCommandError:
        # If we have an error resetting, or syncing, we clobber, and fresh sync.
        logging.warning('Chrome checkout appears corrupt. Clobbering.')
        osutils.RmDir(options.chrome_root, ignore_missing=True, sudo=True)
        osutils.SafeMakedirsNonRoot(options.chrome_root)
        SyncChrome(gclient_path, options)

    return 0
예제 #17
0
def _ProxySimSetup(options):
  """Set up proxy simulator, and return only in the child environment.

  TODO: Ideally, this should support multiple concurrent invocations of
  cros_sdk --proxy-sim; currently, such invocations will conflict with each
  other due to the veth device names and IP addresses.  Either this code would
  need to generate fresh, unused names for all of these before forking, or it
  would need to support multiple concurrent cros_sdk invocations sharing one
  proxy and allowing it to exit when unused (without counting on any local
  service-management infrastructure on the host).
  """

  may_need_mpm = False
  apache_bin = osutils.Which('apache2')
  if apache_bin is None:
    apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
    if apache_bin is None:
      _ReportMissing(('apache2',))
  else:
    may_need_mpm = True

  # Module names and .so names included for ease of grepping.
  apache_modules = [('proxy_module', 'mod_proxy.so'),
                    ('proxy_connect_module', 'mod_proxy_connect.so'),
                    ('proxy_http_module', 'mod_proxy_http.so'),
                    ('proxy_ftp_module', 'mod_proxy_ftp.so')]

  # Find the apache module directory, and make sure it has the modules we need.
  module_dirs = {}
  for g in PROXY_APACHE_MODULE_GLOBS:
    for mod, so in apache_modules:
      for f in glob.glob(os.path.join(g, so)):
        module_dirs.setdefault(os.path.dirname(f), []).append(so)
  for apache_module_path, modules_found in module_dirs.iteritems():
    if len(modules_found) == len(apache_modules):
      break
  else:
    # Appease cros lint, which doesn't understand that this else block will not
    # fall through to the subsequent code which relies on apache_module_path.
    apache_module_path = None
    raise SystemExit(
        'Could not find apache module path containing all required modules: %s'
        % ', '.join(so for mod, so in apache_modules))

  def check_add_module(name):
    so = 'mod_%s.so' % name
    if os.access(os.path.join(apache_module_path, so), os.F_OK):
      mod = '%s_module' % name
      apache_modules.append((mod, so))
      return True
    return False

  check_add_module('authz_core')
  if may_need_mpm:
    for mpm in PROXY_APACHE_MPMS:
      if check_add_module('mpm_%s' % mpm):
        break

  veth_host = '%s-host' % PROXY_VETH_PREFIX
  veth_guest = '%s-guest' % PROXY_VETH_PREFIX

  # Set up locks to sync the net namespace setup.  We need the child to create
  # the net ns first, and then have the parent assign the guest end of the veth
  # interface to the child's new network namespace & bring up the proxy.  Only
  # then can the child move forward and rely on the network being up.
  ns_create_lock = locking.PipeLock()
  ns_setup_lock = locking.PipeLock()

  pid = os.fork()
  if not pid:
    # Create our new isolated net namespace.
    namespaces.Unshare(namespaces.CLONE_NEWNET)

    # Signal the parent the ns is ready to be configured.
    ns_create_lock.Post()
    del ns_create_lock

    # Wait for the parent to finish setting up the ns/proxy.
    ns_setup_lock.Wait()
    del ns_setup_lock

    # Set up child side of the network.
    commands = (
        ('ip', 'link', 'set', 'up', 'lo'),
        ('ip', 'address', 'add',
         '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
         'dev', veth_guest),
        ('ip', 'link', 'set', veth_guest, 'up'),
    )
    try:
      for cmd in commands:
        cros_build_lib.RunCommand(cmd, print_cmd=False)
    except cros_build_lib.RunCommandError:
      raise SystemExit('Running %r failed!' % (cmd,))

    proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
    for proto in ('http', 'https', 'ftp'):
      os.environ[proto + '_proxy'] = proxy_url
    for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
      os.environ.pop(v, None)
    return

  # Set up parent side of the network.
  uid = int(os.environ.get('SUDO_UID', '0'))
  gid = int(os.environ.get('SUDO_GID', '0'))
  if uid == 0 or gid == 0:
    for username in PROXY_APACHE_FALLBACK_USERS:
      try:
        pwnam = pwd.getpwnam(username)
        uid, gid = pwnam.pw_uid, pwnam.pw_gid
        break
      except KeyError:
        continue
    if uid == 0 or gid == 0:
      raise SystemExit('Could not find a non-root user to run Apache as')

  chroot_parent, chroot_base = os.path.split(options.chroot)
  pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
  log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)

  # Wait for the child to create the net ns.
  ns_create_lock.Wait()
  del ns_create_lock

  apache_directives = [
      'User #%u' % uid,
      'Group #%u' % gid,
      'PidFile %s' % pid_file,
      'ErrorLog %s' % log_file,
      'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
      'ServerName %s' % PROXY_HOST_IP,
      'ProxyRequests On',
      'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
  ] + [
      'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
      for (mod, so) in apache_modules
  ]
  commands = (
      ('ip', 'link', 'add', 'name', veth_host,
       'type', 'veth', 'peer', 'name', veth_guest),
      ('ip', 'address', 'add',
       '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
       'dev', veth_host),
      ('ip', 'link', 'set', veth_host, 'up'),
      ([apache_bin, '-f', '/dev/null'] +
       [arg for d in apache_directives for arg in ('-C', d)]),
      ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
  )
  cmd = None # Make cros lint happy.
  try:
    for cmd in commands:
      cros_build_lib.RunCommand(cmd, print_cmd=False)
  except cros_build_lib.RunCommandError:
    # Clean up existing interfaces, if any.
    cmd_cleanup = ('ip', 'link', 'del', veth_host)
    try:
      cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
    except cros_build_lib.RunCommandError:
      logging.error('running %r failed', cmd_cleanup)
    raise SystemExit('Running %r failed!' % (cmd,))

  # Signal the child that the net ns/proxy is fully configured now.
  ns_setup_lock.Post()
  del ns_setup_lock

  process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
예제 #18
0
    def VerifyAndFinishInitialization(self, device):
        """Verify files/processes exist and flags are correct."""
        if not self.board:
            if self.remote:
                self.board = cros_build_lib.GetBoard(device_board=device.board,
                                                     override_board=self.board)
            else:
                raise GdbCannotDetectBoardError(
                    'Cannot determine which board to use. '
                    'Please specify the with --board flag.')

        self.sysroot = cros_build_lib.GetSysroot(board=self.board)
        self.prompt = '(%s-gdb) ' % self.board
        self.inf_cmd = self.RemoveSysrootPrefix(self.inf_cmd)
        self.cross_gdb = self.GetCrossGdb()

        if self.remote:

            # If given remote process name, find pid & inf_cmd on remote device.
            if self.remote_process_name or self.pid:
                self._FindRemoteProcess(device)

            # Verify that sysroot is valid (exists).
            if not os.path.isdir(self.sysroot):
                raise GdbMissingSysrootError('Sysroot does not exist: %s' %
                                             self.sysroot)

        self.device = device
        sysroot_inf_cmd = ''
        if self.inf_cmd:
            sysroot_inf_cmd = os.path.join(self.sysroot,
                                           self.inf_cmd.lstrip('/'))

        # Verify that inf_cmd, if given, exists.
        if sysroot_inf_cmd and not os.path.exists(sysroot_inf_cmd):
            raise GdbMissingInferiorError('Cannot find file %s (in sysroot).' %
                                          sysroot_inf_cmd)

        # Check to see if inf_cmd is stripped, and if so, check to see if debug file
        # exists.  If not, tell user and give them the option of quitting & getting
        # the debug info.
        if sysroot_inf_cmd:
            stripped_info = cros_build_lib.RunCommand(
                ['file', sysroot_inf_cmd], capture_output=True).output
            if not ' not stripped' in stripped_info:
                debug_file = os.path.join(self.sysroot, 'usr/lib/debug',
                                          self.inf_cmd.lstrip('/'))
                debug_file += '.debug'
                if not os.path.exists(debug_file):
                    equery = 'equery-%s' % self.board
                    package = cros_build_lib.RunCommand(
                        [equery, '-q', 'b', self.inf_cmd],
                        capture_output=True).output
                    logging.info(
                        self._MISSING_DEBUG_INFO_MSG % {
                            'board': self.board,
                            'inf_cmd': self.inf_cmd,
                            'package': package,
                            'debug_file': debug_file
                        })
                    answer = cros_build_lib.BooleanPrompt()
                    if not answer:
                        raise GdbEarlyExitError(
                            'Exiting early, at user request.')

        # Set up qemu, if appropriate.
        qemu_arch = qemu.Qemu.DetectArch(self._GDB, self.sysroot)
        if qemu_arch is None:
            self.framework = 'ldso'
        else:
            self.framework = 'qemu'
            self.qemu = qemu.Qemu(self.sysroot, arch=qemu_arch)

        if self.remote:
            # Verify cgdb flag info.
            if self.cgdb:
                if osutils.Which('cgdb') is None:
                    raise GdbMissingDebuggerError(
                        'Cannot find cgdb.  Please install '
                        'cgdb first.')
예제 #19
0
def HasPixz():
    """Returns path to pixz if it's on PATH or None otherwise."""
    return osutils.Which('pixz')
예제 #20
0
 def _au_safe_sudo(self, cmd, **kwargs):
     """Run a command using sudo in a way that respects the util_path"""
     newcmd = osutils.Which(cmd[0], path=self._util_path)
     if newcmd:
         cmd = [newcmd] + cmd[1:]
     return cros_build_lib.SudoRunCommand(cmd, **kwargs)
예제 #21
0
 def testRoot(self):
   """Check root handling."""
   self.assertEqual(None, osutils.Which('prog', root='/.........'))
   self.assertEqual(self.prog_path, osutils.Which('prog', path='/',
                                                  root=self.tempdir))
예제 #22
0
def _ShellLintFile(path, output_format, debug, gentoo_format=False):
    """Returns result of running lint checks on |path|.

  Args:
    path: The path to the script on which to run the linter.
    output_format: The format of the output that the linter should emit. See
                   |SHLINT_OUTPUT_FORMAT_MAP|.
    debug: Whether to print out the linter command.
    gentoo_format: Whether to treat this file as an ebuild style script.

  Returns:
    A CommandResult object.
  """
    # TODO: Try using `checkbashisms`.
    syntax_check = _LinterRunCommand(['bash', '-n', path], debug)
    if syntax_check.returncode != 0:
        return syntax_check

    # Try using shellcheck if it exists, with a preference towards finding it
    # inside the chroot. This is OK as it is statically linked.
    shellcheck = (osutils.Which('shellcheck',
                                path='/usr/bin',
                                root=os.path.join(constants.SOURCE_ROOT,
                                                  'chroot'))
                  or osutils.Which('shellcheck'))

    if not shellcheck:
        logging.notice('Install shellcheck for additional shell linting.')
        return syntax_check

    # Instruct shellcheck to run itself from the shell script's dir. Note that
    # 'SCRIPTDIR' is a special string that shellcheck rewrites to the dirname of
    # the given path.
    extra_checks = [
        'avoid-nullary-conditions',  # SC2244
        'check-unassigned-uppercase',  # Include uppercase in SC2154
        'require-variable-braces',  # SC2250
    ]
    if not gentoo_format:
        extra_checks.append('quote-safe-variables')  # SC2248

    cmd = [
        shellcheck, '--source-path=SCRIPTDIR',
        '--enable=%s' % ','.join(extra_checks)
    ]
    if output_format != 'default':
        cmd.extend(SHLINT_OUTPUT_FORMAT_MAP[output_format])
    cmd.append('-x')
    if gentoo_format:
        # ebuilds don't explicitly export variables or contain a shebang.
        cmd.append('--exclude=SC2148')
        # ebuilds always use bash.
        cmd.append('--shell=bash')
    cmd.append(path)

    lint_result = _LinterRunCommand(cmd, debug)

    # During testing, we don't want to fail the linter for shellcheck errors,
    # so override the return code.
    if lint_result.returncode != 0:
        bug_url = (
            'https://bugs.chromium.org/p/chromium/issues/entry?' +
            urllib.parse.urlencode({
                'template':
                'Defect report from Developer',
                'summary':
                'Bad shellcheck warnings for %s' % os.path.basename(path),
                'components':
                'Infra>Client>ChromeOS>Build,',
                'cc':
                '[email protected],[email protected]',
                'comment':
                'Shellcheck output from file:\n%s\n\n<paste output here>\n\n'
                "What is wrong with shellcheck's findings?\n" % path,
            }))
        logging.warning(
            'Shellcheck found problems. These will eventually become '
            'errors.  If the shellcheck findings are not useful, '
            'please file a bug at:\n%s', bug_url)
        lint_result.returncode = 0
    return lint_result