| #!/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 |
| |
| _SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) |
| _SOURCE_TREE_DIR = os.path.abspath(os.path.join(_SCRIPT_DIR, os.pardir, |
| os.pardir)) |
| |
| _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 = ['third_party/starboard', 'starboard',] |
| |
| # The list of files that must be present in a directory to allow it to be |
| # considered a valid port. |
| _PORT_FILES = [ |
| 'gyp_configuration.gypi', |
| 'gyp_configuration.py', |
| 'starboard_platform.gyp', |
| 'configuration_public.h', |
| 'atomic_public.h', |
| 'thread_types_public.h', |
| ] |
| |
| # Whether to emit warnings if it finds a directory that almost has a port. |
| # TODO: Enable when tree is clean. |
| _WARN_ON_ALMOST_PORTS = False |
| |
| # 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: |
| # Add directory to path to make sure that cygpath can be imported. |
| sys.path.insert(0, os.path.join(_SOURCE_TREE_DIR, 'lbshell', 'build', |
| 'pylib')) |
| sys.path.insert(0, os.path.join(_SOURCE_TREE_DIR, 'tools', 'gyp', 'pylib')) |
| importlib.import_module('gyp') |
| return sys.modules['gyp'] |
| |
| |
| def GetSourceTreeDir(): |
| return _SOURCE_TREE_DIR |
| |
| |
| 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'): |
| 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: |
| return build_id_file.read().replace('\n', '') |
| |
| 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, shell=True, 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 _GetClangVersion(clang_path): |
| """Return clang version string.""" |
| |
| output = subprocess.check_output([clang_path, '--version']).splitlines()[0] |
| return output |
| |
| |
| def CheckClangVersion(): |
| """Ensure the expected version of clang is in the path.""" |
| |
| # Run the clang update script to get the correct version of clang. |
| # Then check that clang is in the path. |
| source_tree_dir = GetSourceTreeDir() |
| update_script = os.path.join(source_tree_dir, 'tools', 'clang', 'scripts', |
| 'update.sh') |
| update_proc = subprocess.Popen(update_script) |
| rc = update_proc.wait() |
| if rc != 0: |
| raise RuntimeError('%s failed.' % update_script) |
| |
| # update.sh downloads clang to this path. |
| clang_bin_path = os.path.join(source_tree_dir, 'third_party', 'llvm-build', |
| 'Release+Asserts', 'bin') |
| clang_bin = os.path.join(clang_bin_path, 'clang') |
| |
| if not os.path.exists(clang_bin): |
| raise RuntimeError('Clang not found.') |
| |
| clang_in_path = Which('clang') |
| if not clang_in_path: |
| raise RuntimeError('clang not found in PATH. Add %s to your PATH' % |
| clang_bin_path) |
| |
| # The first clang in PATH may be goma's version, which is OK. |
| # It forwards to the real one. Just make sure the version strings |
| # are the same. |
| expected_version = _GetClangVersion(clang_bin) |
| detected_version = _GetClangVersion(clang_in_path) |
| if expected_version != detected_version: |
| raise RuntimeError('Invalid clang version found in PATH: (%s != %s)' % |
| (expected_version, detected_version)) |
| |
| |
| 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 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 GetStackSizeConstant(platform, constant_name): |
| """Gets a constant value from lb_shell_constants.h.""" |
| # TODO: This needs to be reimplemented if necessary. For now, |
| # it'll return the default value. |
| _, _ = platform, constant_name |
| return 0 |
| |
| |
| def _IsValidPortFilenameList(directory, filenames): |
| """Determines if |filenames| contains the required files for a valid port.""" |
| |
| missing = set(_PORT_FILES) - set(filenames) |
| if missing: |
| if len(missing) < (len(_PORT_FILES) / 2) and _WARN_ON_ALMOST_PORTS: |
| logging.warning('Directory %s contains several files needed for a port, ' |
| 'but not all of them. In particular, it is missing: %s', |
| directory, ', '.join(missing)) |
| return not missing |
| |
| |
| def _GetPortName(root, directory): |
| """Gets the name of a port found at |directory| off of |root|.""" |
| |
| assert directory.startswith(root) |
| start = len(root) + 1 # Remove the trailing slash from the root. |
| |
| assert start < len(directory) |
| |
| # Calculate the name based on relative path from search root to port. |
| return re.sub(r'[^a-zA-Z0-9_]', r'-', directory[start:]) |
| |
| |
| def _FindValidPorts(root, result): |
| """Adds name->path for all ports under |directory| to |result|.""" |
| for current_path, _, filenames in os.walk(root): |
| if _IsValidPortFilenameList(current_path, filenames): |
| if current_path == root: |
| logging.warning('Found port at search path root: %s', current_path) |
| port_name = _GetPortName(root, current_path) |
| if port_name in result: |
| logging.error('Found duplicate port name "%s" at "%s" and "%s"', |
| port_name, result[port_name], current_path) |
| result[port_name] = current_path |
| |
| return result |
| |
| |
| def _FindThirdPartyPlatforms(): |
| """Workhorse for GetThirdPartyPlatforms(). |
| |
| 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->absolute paths to the port directory. |
| """ |
| |
| result = {} |
| for path_element in _PORT_SEARCH_PATH: |
| root = os.path.join(_SOURCE_TREE_DIR, path_element) |
| if not os.path.isdir(root): |
| logging.warning('Port search path directory not found: %s', path_element) |
| continue |
| root = os.path.realpath(root) |
| _FindValidPorts(root, result) |
| |
| return result |
| |
| # Global cache of TPP so the filesystem walk is only done once, and the values |
| # are always consistent. |
| _THIRD_PARTY_PLATFORMS = None |
| |
| |
| def GetThirdPartyPlatforms(): |
| """Return valid platform definitions found by scanning the source tree.""" |
| global _THIRD_PARTY_PLATFORMS |
| if _THIRD_PARTY_PLATFORMS is None: |
| _THIRD_PARTY_PLATFORMS = _FindThirdPartyPlatforms() |
| return _THIRD_PARTY_PLATFORMS |