# Copyright 2017 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Functions for dealing with determining --tool-prefix.""" import abc import distutils.spawn import logging import os _STATUS_DETECTED = 1 _STATUS_VERIFIED = 2 SRC_ROOT = os.environ.get('CHECKOUT_SOURCE_ROOT', os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir))) _SAMPLE_TOOL_SUFFIX = 'readelf' class _PathFinder(object): def __init__(self, name, value): self._status = _STATUS_DETECTED if value is not None else 0 self._name = name self._value = value @abc.abstractmethod def Detect(self): pass @abc.abstractmethod def Verify(self): pass def Tentative(self): if self._status < _STATUS_DETECTED: self._value = self.Detect() logging.debug('Detected --%s=%s', self._name, self._value) self._status = _STATUS_DETECTED return self._value def Finalized(self): if self._status < _STATUS_VERIFIED: self.Tentative() self.Verify() logging.info('Using --%s=%s', self._name, self._value) self._status = _STATUS_VERIFIED return self._value class OutputDirectoryFinder(_PathFinder): def __init__(self, value=None, any_path_within_output_directory=None): super(OutputDirectoryFinder, self).__init__( name='output-directory', value=value) self._any_path_within_output_directory = any_path_within_output_directory def Detect(self): # Try and find build.ninja. abs_path = os.path.abspath(self._any_path_within_output_directory) while True: if os.path.exists(os.path.join(abs_path, 'build.ninja')): return os.path.relpath(abs_path) parent_dir = os.path.dirname(abs_path) if parent_dir == abs_path: break abs_path = abs_path = parent_dir # See if CWD=output directory. if os.path.exists('build.ninja'): return '.' return None def Verify(self): if not self._value or not os.path.isdir(self._value): raise Exception('Bad --%s. Path not found: %s' % (self._name, self._value)) class ToolPrefixFinder(_PathFinder): def __init__(self, value=None, output_directory_finder=None, linker_name=None): super(ToolPrefixFinder, self).__init__( name='tool-prefix', value=value) self._output_directory_finder = output_directory_finder self._linker_name = linker_name; def IsLld(self): return self._linker_name.startswith('lld') if self._linker_name else True def Detect(self): output_directory = self._output_directory_finder.Tentative() if output_directory: ret = None if self.IsLld(): ret = os.path.join(SRC_ROOT, 'third_party', 'llvm-build', 'Release+Asserts', 'bin', 'llvm-') else: # Auto-detect from build_vars.txt build_vars = _LoadBuildVars(output_directory) tool_prefix = build_vars.get('android_tool_prefix') if tool_prefix: ret = os.path.normpath(os.path.join(output_directory, tool_prefix)) # Maintain a trailing '/' if needed. if tool_prefix.endswith(os.path.sep): ret += os.path.sep if ret: # Check for output directories that have a stale build_vars.txt. if os.path.isfile(ret + _SAMPLE_TOOL_SUFFIX): return ret else: err_lines = ['tool-prefix not found: %s' % ret] if ret.endswith('llvm-'): err_lines.append('Probably need to run: ' 'tools/clang/scripts/download_objdump.py') raise Exception('\n'.join(err_lines)) from_path = distutils.spawn.find_executable(_SAMPLE_TOOL_SUFFIX) if from_path: return from_path[:-7] return None def Verify(self): if os.path.sep not in self._value: full_path = distutils.spawn.find_executable( self._value + _SAMPLE_TOOL_SUFFIX) else: full_path = self._value + _SAMPLE_TOOL_SUFFIX if not full_path or not os.path.isfile(full_path): raise Exception('Bad --%s. Path not found: %s' % (self._name, full_path)) def _LoadBuildVars(output_directory): build_vars_path = os.path.join(output_directory, 'build_vars.txt') if os.path.exists(build_vars_path): with open(build_vars_path) as f: return dict(l.rstrip().split('=', 1) for l in f if '=' in l) return dict() def FromSrcRootRelative(path): ret = os.path.relpath(os.path.join(SRC_ROOT, path)) # Need to maintain a trailing /. if path.endswith(os.path.sep): ret += os.path.sep return ret def ToSrcRootRelative(path): ret = os.path.relpath(path, SRC_ROOT) # Need to maintain a trailing /. if path.endswith(os.path.sep): ret += os.path.sep return ret def GetCppFiltPath(tool_prefix): if tool_prefix[-5:] == 'llvm-': return tool_prefix + 'cxxfilt' return tool_prefix + 'c++filt' def GetNmPath(tool_prefix): return tool_prefix + 'nm' def GetApkAnalyzerPath(output_directory): build_vars = _LoadBuildVars(output_directory) sdk_analyzer = os.path.normpath(os.path.join( output_directory, build_vars['android_sdk_root'], 'tools', 'bin', 'apkanalyzer')) if os.path.exists(sdk_analyzer): return sdk_analyzer # Older SDKs do not contain the tool, so fall back to the one we know exists. return os.path.join(SRC_ROOT, 'third_party', 'android_tools', 'sdk', 'tools', 'bin', 'apkanalyzer') def GetObjDumpPath(tool_prefix): return tool_prefix + 'objdump' def GetReadElfPath(tool_prefix): # Work-around for llvm-readobj bug where 'File: ...' info is not printed: # https://bugs.llvm.org/show_bug.cgi?id=35351 if tool_prefix[-5:] == 'llvm-': return 'readelf' return tool_prefix + 'readelf' def GetBcAnalyzerPath(tool_prefix): if tool_prefix[-5:] != 'llvm-': raise ValueError('BC analyzer is only supported in LLVM.') return tool_prefix + 'bcanalyzer'