304 lines
10 KiB
Python
Executable File
304 lines
10 KiB
Python
Executable File
#!/usr/bin/python3 -B
|
|
# Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
# source code may only be used and distributed under the Widevine Master
|
|
# License Agreement.
|
|
"""Generates build files and builds the CDM source release."""
|
|
|
|
# Lint as: python2, python3
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import math
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
# pylint: disable=C6204
|
|
# Absolute path to Widevine CDM project repository.
|
|
CDM_TOP_PATH = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
# If gyp has been installed locally in third_party, this will find it.
|
|
# Irrelevant if gyp has been installed globally.
|
|
sys.path.insert(1, os.path.join(CDM_TOP_PATH, 'third_party'))
|
|
import gyp
|
|
# pylint: enable=C6204
|
|
|
|
PLATFORMS_DIR_PATH = os.path.join(CDM_TOP_PATH, 'platforms')
|
|
OEMCRYPTO_FUZZTEST_DIR_PATH = os.path.join(
|
|
CDM_TOP_PATH, 'oemcrypto', 'test', 'fuzz_tests')
|
|
|
|
# Exit status for script if failure arises.
|
|
EXIT_FAILURE = 1
|
|
|
|
|
|
def LoadFields(path):
|
|
"""Loads variables from an external python script.
|
|
|
|
Attempts to load the specified python fields from the source file
|
|
specified at the given |path|.
|
|
|
|
Args:
|
|
path: The path (absolute to relative) to the source file containing
|
|
module to be loaded.
|
|
|
|
Returns:
|
|
The fields dictionary if it is successfully loaded. Otherwise
|
|
returns None
|
|
"""
|
|
fields = {}
|
|
with open(path) as f:
|
|
exec(f.read(), fields, fields) # pylint: disable=W0122
|
|
return fields
|
|
|
|
|
|
def IsNinjaInstalled():
|
|
"""Determine if ninja is installed."""
|
|
try:
|
|
if hasattr(subprocess, 'DEVNULL'):
|
|
# Provides better compatibility for Windows, not available in Python 2.x
|
|
dev_null = subprocess.DEVNULL
|
|
else:
|
|
dev_null = open(os.devnull, 'w')
|
|
subprocess.check_call(['ninja', '--version'], stdout=dev_null,
|
|
stderr=dev_null)
|
|
return True
|
|
except subprocess.CalledProcessError:
|
|
# Error code returned, probably not the ninja we're looking for.
|
|
return False
|
|
except OSError:
|
|
# No such command found.
|
|
return False
|
|
|
|
|
|
def PlatformExists(platform, print_details=False):
|
|
"""Determine if specified platform exists.
|
|
|
|
Args:
|
|
platform: Name of the platform (ex. "x86-64")
|
|
print_details: An optional flag to enable printing of the reason
|
|
the platform was determined to not exist. Does not print
|
|
anything if the platform is valid.
|
|
|
|
Returns:
|
|
True if the there exists configuration files for the specified |platform|,
|
|
False otherwise.
|
|
"""
|
|
target_path = os.path.join(PLATFORMS_DIR_PATH, platform)
|
|
platform_gypi_path = os.path.join(target_path, 'settings.gypi')
|
|
platform_environment_path = os.path.join(target_path, 'environment.py')
|
|
|
|
def vprint(msg):
|
|
if print_details:
|
|
print(msg, file=sys.stderr)
|
|
|
|
if not os.path.isdir(target_path):
|
|
vprint(' Target path does not exist: platform = {}'.format(platform))
|
|
return False
|
|
|
|
if not os.path.isfile(platform_gypi_path):
|
|
vprint(' Target platform is missing settings.gypi file: settings_path = {}'
|
|
.format(platform_gypi_path))
|
|
return False
|
|
if not os.path.isfile(platform_environment_path):
|
|
vprint(' Target platform is missing environment.py file: env_path = {}'
|
|
.format(platform_environment_path))
|
|
return False
|
|
return True
|
|
|
|
|
|
def RetrieveListOfPlatforms():
|
|
"""Retrieves a list of names of the support platform.
|
|
|
|
Returns:
|
|
A list of strings containing the name of the platform
|
|
"""
|
|
if not os.path.isdir(PLATFORMS_DIR_PATH):
|
|
print('Cannot find platforms directory: expected_path = {}'
|
|
.format(PLATFORMS_DIR_PATH), file=sys.stderr)
|
|
return []
|
|
return sorted(filter(PlatformExists, os.listdir(PLATFORMS_DIR_PATH)))
|
|
|
|
|
|
def VerboseSubprocess(args):
|
|
"""Print sub-process command and execute."""
|
|
print(' Running: ' + ' '.join(args))
|
|
return subprocess.call(args)
|
|
|
|
|
|
def RunMake(unused_output_path, build_config, job_limit, verbose):
|
|
"""Run Make as build system."""
|
|
os.environ['BUILDTYPE'] = build_config
|
|
|
|
if job_limit is not None:
|
|
if math.isinf(job_limit):
|
|
job_args = ['-j']
|
|
else:
|
|
job_args = ['-j', str(job_limit)]
|
|
else:
|
|
job_args = []
|
|
|
|
if verbose:
|
|
job_args.append('-v')
|
|
job_args.extend(['all', 'widevine_ce_cdm_shared'])
|
|
return VerboseSubprocess(['make', '-C', CDM_TOP_PATH] + job_args)
|
|
|
|
|
|
def RunNinja(output_path, build_config, job_limit, verbose):
|
|
"""Run Ninja as build system."""
|
|
build_path = os.path.join(output_path, build_config)
|
|
|
|
if job_limit is not None:
|
|
if math.isinf(job_limit):
|
|
print('Ninja cannot run an infinite number of jobs',
|
|
file=sys.stderr)
|
|
print('Running at most 1000 jobs')
|
|
job_limit = 1000
|
|
job_args = ['-j', str(job_limit)]
|
|
else:
|
|
job_args = []
|
|
|
|
if verbose:
|
|
job_args += ['-v']
|
|
job_args += ['all', 'widevine_ce_cdm_shared']
|
|
return VerboseSubprocess(['ninja', '-C', build_path] + job_args)
|
|
|
|
|
|
def GetBuilder(generator):
|
|
return {
|
|
'make': RunMake,
|
|
'ninja': RunNinja,
|
|
}.get(generator)
|
|
|
|
|
|
def ImportPlatform(platform, gyp_args, build_fuzz_tests):
|
|
"""Handles platform-specific setup for the named platform.
|
|
|
|
Computes platform-specific paths, sets gyp arguments for platform-specific
|
|
gypis and output paths, imports a platform-specific module, and exports
|
|
platform-specific environment variables.
|
|
|
|
Args:
|
|
platform: The name of the platform.
|
|
gyp_args: An array of gyp arguments to which this function will append.
|
|
build_fuzz_tests: True when we are building OEMCrypto fuzz tests.
|
|
|
|
Returns:
|
|
The path to the root of the build output.
|
|
"""
|
|
|
|
print(' Target Platform: ' + platform)
|
|
assert PlatformExists(platform)
|
|
|
|
target_path = os.path.join(PLATFORMS_DIR_PATH, platform)
|
|
platform_gypi_path = os.path.join(target_path, 'settings.gypi')
|
|
platform_environment_path = os.path.join(target_path, 'environment.py')
|
|
output_path = os.path.join(CDM_TOP_PATH, 'out', platform)
|
|
|
|
gyp_args.append('--include=' + platform_gypi_path)
|
|
if build_fuzz_tests:
|
|
fuzzer_settings_path = os.path.join(
|
|
OEMCRYPTO_FUZZTEST_DIR_PATH, 'platforms/x86-64')
|
|
fuzzer_settings_gypi_path = os.path.join(
|
|
fuzzer_settings_path, 'fuzzer_settings.gypi')
|
|
gyp_args.append('--include=' + fuzzer_settings_gypi_path)
|
|
gyp_args.append('-Goutput_dir=' + output_path)
|
|
|
|
platform_environment_path = os.path.join(target_path, 'environment.py')
|
|
target = LoadFields(platform_environment_path)
|
|
|
|
if 'export_variables' in target:
|
|
for variable, value in target['export_variables'].items():
|
|
if not os.environ.get(variable):
|
|
os.environ[variable] = value
|
|
print(' set {} to {}'.format(variable, value))
|
|
|
|
return output_path
|
|
|
|
|
|
def main(args):
|
|
if IsNinjaInstalled():
|
|
print('ninja is installed - use ninja for generator')
|
|
default_generator = 'ninja'
|
|
else:
|
|
print('ninja is not installed - use make for generator')
|
|
default_generator = 'make'
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('platform',
|
|
help=('The platform configuration to use (x86-64). '
|
|
'Should be one of the folder names inside '
|
|
'platforms/'),
|
|
choices=RetrieveListOfPlatforms())
|
|
parser.add_argument('-r', '--release',
|
|
dest='build_config', default='Debug',
|
|
action='store_const', const='Release',
|
|
help='Builds a release build (equivalent to -c Release)')
|
|
parser.add_argument('-c', '--config',
|
|
dest='build_config', default='Debug',
|
|
help='Select a build config (Debug, Release). '
|
|
'Defaults to Debug.')
|
|
parser.add_argument('-g', '--generator', default=default_generator,
|
|
help='Which build system to use (make, ninja, ...). '
|
|
'Defaults to ninja when available, make otherwise.')
|
|
parser.add_argument('-j', '--jobs', nargs='?', const=float('inf'), type=int,
|
|
help='When building, run up to this many jobs in '
|
|
'parallel. The default is the default for your chosen '
|
|
'generator. If this flag is specified without a value, '
|
|
'jobs will spawn without limit.')
|
|
parser.add_argument('-D', action='append', default=[],
|
|
help='Pass variable definitions to gyp.')
|
|
parser.add_argument('-e', '--extra_gyp', action='append', default=[],
|
|
help='External gyp file that are processed after the '
|
|
'standard gyp files. (Maybe be specified multiple times)')
|
|
parser.add_argument('-ft', '--fuzz_tests', default=False,
|
|
action='store_true',
|
|
help='Set this flag if you want to build fuzz tests.')
|
|
parser.add_argument('-v', '--verbose', action='store_true',
|
|
help='Print verbose output.')
|
|
|
|
options = parser.parse_args(args)
|
|
|
|
if not PlatformExists(options.platform, print_details=True):
|
|
platforms = RetrieveListOfPlatforms()
|
|
if platforms:
|
|
print(' Available platforms: ' + ', '.join(platforms), file=sys.stderr)
|
|
return EXIT_FAILURE
|
|
|
|
gyp_args = [
|
|
'--format=' + options.generator,
|
|
'--depth=' + CDM_TOP_PATH,
|
|
]
|
|
if options.fuzz_tests:
|
|
gyp_args.append(
|
|
os.path.join(OEMCRYPTO_FUZZTEST_DIR_PATH, 'oemcrypto_fuzztests.gyp'))
|
|
else:
|
|
gyp_args.append(os.path.join(CDM_TOP_PATH, 'cdm', 'cdm_unittests.gyp'))
|
|
for var in options.extra_gyp:
|
|
gyp_args.append(var)
|
|
|
|
for var in options.D:
|
|
gyp_args.append('-D' + var)
|
|
|
|
output_path = ImportPlatform(
|
|
options.platform, gyp_args, options.fuzz_tests)
|
|
|
|
print(' Running: {}'.format(' '.join(['gyp'] + gyp_args)))
|
|
retval = gyp.main(gyp_args)
|
|
if retval != 0:
|
|
return retval
|
|
|
|
# The gyp argument --build=xyz only works on newer versions of gyp and
|
|
# ignores the generator flag output_dir (as of 2014-05-28 with ninja).
|
|
# So instead of using --build, we simply invoke the build system ourselves.
|
|
builder = GetBuilder(options.generator)
|
|
if builder is None:
|
|
print(' Cannot automatically build with this generator', file=sys.stderr)
|
|
print(' Please start the build manually', file=sys.stderr)
|
|
return EXIT_FAILURE
|
|
return builder(
|
|
output_path, options.build_config, options.jobs, options.verbose)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|