| #!/usr/bin/python |
| # Copyright 2012 Google Inc. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Utilities for use by gyp_cobalt and other build tools.""" |
| |
| import importlib |
| import json |
| import logging |
| import os |
| import re |
| import subprocess |
| import sys |
| import urllib |
| import urllib2 |
| |
| import bootstrap_path # pylint: disable=unused-import |
| from cobalt.tools import paths |
| from starboard.tools import platform |
| |
| _CLANG_REVISION = '298539-1' |
| _CLANG_VERSION = '5.0.0' |
| |
| _SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) |
| |
| _VERSION_SERVER_URL = 'https://carbon-airlock-95823.appspot.com/build_version/generate' # pylint:disable=line-too-long |
| _XSSI_PREFIX = ")]}'\n" |
| |
| # The list of directories, relative to "src/", to search through for ports. |
| _PORT_SEARCH_PATH = [ |
| 'starboard', |
| os.path.join('starboard', 'port'), |
| os.path.join('third_party', 'starboard'), |
| ] |
| |
| # The path to the build.id file that preserves a build ID. |
| BUILD_ID_PATH = os.path.abspath(os.path.join(_SCRIPT_DIR, 'build.id')) |
| |
| |
| def GetGyp(): |
| if 'gyp' not in sys.modules: |
| sys.path.insert(0, |
| os.path.join(paths.REPOSITORY_ROOT, 'tools', 'gyp', |
| 'pylib')) |
| importlib.import_module('gyp') |
| return sys.modules['gyp'] |
| |
| |
| def GypDebugOptions(): |
| """Returns all valid GYP debug options.""" |
| debug_modes = [] |
| gyp = GetGyp() |
| for name in dir(gyp): |
| if name.startswith('DEBUG_'): |
| debug_modes.append(getattr(gyp, name)) |
| return debug_modes |
| |
| |
| def GetRevinfo(): |
| """Get absolute state of all git repos from gclient DEPS.""" |
| |
| try: |
| revinfo_cmd = ['gclient', 'revinfo', '-a'] |
| |
| if sys.platform.startswith('linux') or sys.platform == 'darwin': |
| use_shell = False |
| else: |
| # Windows needs shell to find gclient in the PATH. |
| use_shell = True |
| output = subprocess.check_output(revinfo_cmd, shell=use_shell) |
| revinfo = {} |
| lines = output.splitlines() |
| for line in lines: |
| repo, url = line.split(':', 1) |
| repo = repo.strip().replace('\\', '/') |
| url = url.strip() |
| revinfo[repo] = url |
| return revinfo |
| except subprocess.CalledProcessError as e: |
| logging.warning('Failed to get revision information: %s', e) |
| return {} |
| |
| |
| def GetBuildNumber(version_server=_VERSION_SERVER_URL): |
| """Send a request to the build version server for a build number.""" |
| |
| if os.path.isfile(BUILD_ID_PATH): |
| with open(BUILD_ID_PATH, 'r') as build_id_file: |
| build_number = int(build_id_file.read().replace('\n', '')) |
| logging.info('Retrieving build number from %s', BUILD_ID_PATH) |
| logging.info('Build Number: %d', build_number) |
| return build_number |
| |
| revinfo = GetRevinfo() |
| json_deps = json.dumps(revinfo) |
| username = os.environ.get('USERNAME', os.environ.get('USER')) |
| |
| post_data = {'deps': json_deps} |
| if username: |
| post_data['user'] = username |
| |
| logging.debug('Post data is %s', post_data) |
| request = urllib2.Request(version_server, data=urllib.urlencode(post_data)) |
| # TODO: retry on timeout. |
| try: |
| response = urllib2.urlopen(request) |
| except urllib2.HTTPError as e: |
| logging.warning('Failed to retrieve build number: %s', e) |
| return 0 |
| except urllib2.URLError as e: |
| logging.warning('Could not connect to %s: %s', version_server, e) |
| return 0 |
| |
| data = response.read() |
| if data.find(_XSSI_PREFIX) == 0: |
| data = data[len(_XSSI_PREFIX):] |
| results = json.loads(data) |
| build_number = results.get('build_number', 0) |
| logging.info('Build Number: %d', build_number) |
| return build_number |
| |
| |
| def Which(filename): |
| for path in os.environ['PATH'].split(os.pathsep): |
| full_name = os.path.join(path, filename) |
| if os.path.exists(full_name) and os.path.isfile(full_name): |
| return full_name |
| return None |
| |
| |
| def _EnsureGomaRunning(): |
| """Ensure goma is running.""" |
| |
| cmd_line = ['goma_ctl.py', 'ensure_start'] |
| try: |
| subprocess.check_output(cmd_line, stderr=subprocess.STDOUT) |
| return True |
| except subprocess.CalledProcessError as e: |
| logging.error('goma failed to start.\nCommand: %s\n%s', ' '.join(e.cmd), |
| e.output) |
| return False |
| |
| |
| def GetToolchainsDir(): |
| toolchains_dir = os.path.realpath( |
| os.getenv('COBALT_TOOLCHAINS_DIR', |
| os.path.join(os.environ.get('HOME'), 'cobalt-toolchains'))) |
| # Ensure the toolchains directory exists. |
| if not os.path.exists(toolchains_dir): |
| os.mkdir(toolchains_dir) |
| return toolchains_dir |
| |
| |
| def GetClangBasePath(): |
| return os.path.join(GetToolchainsDir(), |
| 'x86_64-linux-gnu-clang-chromium-' + _CLANG_REVISION) |
| |
| |
| def GetClangBinPath(): |
| return os.path.join('llvm-build', 'Release+Asserts', 'bin') |
| |
| |
| def EnsureClangAvailable(base_dir, bin_path): |
| """Ensure the expected version of clang is available.""" |
| |
| # Run the clang update script to get the correct version of clang. |
| # Then check that clang is in the path. |
| update_script = os.path.join(paths.REPOSITORY_ROOT, 'tools', 'clang', |
| 'scripts', 'update.py') |
| update_proc = subprocess.Popen([ |
| update_script, '--force-clang-revision', _CLANG_REVISION, |
| '--clang-version', _CLANG_VERSION, '--force-base-dir', base_dir |
| ]) |
| rc = update_proc.wait() |
| if rc != 0: |
| raise RuntimeError('%s failed.' % update_script) |
| |
| # update.sh downloads clang to this path. |
| clang_bin = os.path.join(base_dir, bin_path, 'clang') |
| |
| if not os.path.exists(clang_bin): |
| raise RuntimeError('Clang not found.') |
| |
| |
| def FindAndInitGoma(): |
| """Checks if goma is installed and makes sure that it's initialized. |
| |
| Returns: |
| True if goma was found and correctly initialized. |
| """ |
| |
| # GOMA is only supported on Linux. |
| if not sys.platform.startswith('linux'): |
| return False |
| |
| # See if we can find gomacc somewhere the path. |
| gomacc_path = Which('gomacc') |
| |
| if 'USE_GOMA' in os.environ: |
| use_goma = int(os.environ['USE_GOMA']) |
| if use_goma and not gomacc_path: |
| logging.critical('goma was requested but gomacc not found in PATH.') |
| sys.exit(1) |
| else: |
| use_goma = bool(gomacc_path) |
| |
| if use_goma: |
| use_goma = _EnsureGomaRunning() |
| return use_goma |
| |
| |
| def GetHostCompilerEnvironment(goma_supports_compiler=False): |
| """Return the host compiler toolchain environment.""" |
| |
| base_dir = GetClangBasePath() |
| bin_path = GetClangBinPath() |
| toolchain_bin_dir = os.path.join(base_dir, bin_path) |
| EnsureClangAvailable(base_dir, bin_path) |
| |
| cc_clang = os.path.join(toolchain_bin_dir, 'clang') |
| cxx_clang = os.path.join(toolchain_bin_dir, 'clang++') |
| host_clang_environment = { |
| 'CC_host': cc_clang, |
| 'CXX_host': cxx_clang, |
| 'LD_host': cxx_clang, |
| 'ARFLAGS_host': 'rcs', |
| 'ARTHINFLAGS_host': 'rcsT', |
| } |
| # Check if goma is installed. Initialize if needed and use if possible. |
| if goma_supports_compiler and FindAndInitGoma(): |
| logging.info('Using Goma') |
| host_clang_environment.update({ |
| 'CC_host': 'gomacc ' + cc_clang, |
| 'CXX_host': 'gomacc ' + cxx_clang, |
| }) |
| return host_clang_environment |
| |
| |
| def GetConstantValue(file_path, constant_name): |
| """Return an rvalue from a C++ header file. |
| |
| Search a C/C++ header file at |file_path| for an assignment to the |
| constant named |constant_name|. The rvalue for the assignment must be a |
| literal constant or an expression of literal constants. |
| |
| Args: |
| file_path: Header file to search. |
| constant_name: Name of C++ rvalue to evaluate. |
| |
| Returns: |
| constant_name as an evaluated expression. |
| """ |
| |
| search_re = re.compile(r'%s\s*=\s*([\d\+\-*/\s\(\)]*);' % constant_name) |
| with open(file_path, 'r') as f: |
| match = search_re.search(f.read()) |
| |
| if not match: |
| logging.critical('Could not query constant value. The expression ' |
| 'should only have numbers, operators, spaces, and ' |
| 'parens. Please check "%s" in %s.\n', constant_name, |
| file_path) |
| sys.exit(1) |
| |
| expression = match.group(1) |
| value = eval(expression) # pylint:disable=eval-used |
| return value |
| |
| |
| def _FindAllPlatforms(): |
| """Workhorse for GetAllPlatforms(). |
| |
| Search through directories listed in _PORT_SEARCH_PATH to find valid |
| ports, so that they can be added to the VALID_PLATFORMS list. This |
| allows gyp_cobalt to register new ports without needing to modify |
| code in src/cobalt/. |
| |
| Returns: |
| A dictionary of name->PlatformInfo. |
| |
| """ |
| |
| result = {} |
| search_path = [os.path.realpath(os.path.join(paths.REPOSITORY_ROOT, x)) |
| for x in _PORT_SEARCH_PATH] |
| |
| # Ignore search path directories inside other search path directories. |
| exclusion_set = set(search_path) |
| |
| for entry in search_path: |
| if not os.path.isdir(entry): |
| continue |
| for platform_info in platform.PlatformInfo.EnumeratePorts(entry, |
| exclusion_set): |
| if platform_info.port_name in result: |
| logging.error('Found duplicate port name "%s" at "%s" and "%s"', |
| platform_info.port_name, result[platform_info.port_name], |
| platform_info.path) |
| result[platform_info.port_name] = platform_info |
| |
| return result |
| |
| |
| # Global cache of TPP so the filesystem walk is only done once, and the values |
| # are always consistent. |
| _ALL_PLATFORMS = None |
| |
| |
| def GetAllPlatforms(): |
| """Return valid platform definitions found by scanning the source tree.""" |
| global _ALL_PLATFORMS |
| if _ALL_PLATFORMS is None: |
| _ALL_PLATFORMS = _FindAllPlatforms() |
| return _ALL_PLATFORMS |