1# Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright 2# 2012 Google Inc. All rights reserved. Use of this source code is governed by 3# a BSD-style license that can be found in the LICENSE file. 4 5# This script determines the contents of the per-configuration `args.gn` files 6# that are used to build CEF/Chromium with GN. See comments in CEF's top-level 7# BUILD.gn file for general GN usage instructions. 8# 9# This script performs the following tasks: 10# 11# - Defines CEF's default and required arg values in cases where they differ 12# from Chromium's. 13# - Accepts user-defined arg values via the GN_DEFINES environment variable. 14# - Verifies that user-defined arg values do not conflict with CEF's 15# requirements. 16# - Generates multiple configurations by combining user-defined arg values with 17# CEF's default and required values. 18# 19# Before adding a new arg value in this script determine the following: 20# 21# - Chromium's default value. Default values are defined in the declare_args() 22# sections of *.gni files. 23# - Chromium's value requirements. Check for assert()s related to the value in 24# Chromium code. 25# - Whether a particular value is optional or required for CEF. 26# - Under what conditions a particular value is required for CEF (platform, 27# build type, CPU architecture, etc). 28# 29# If CEF can use Chromium's default value and has no additional validation 30# requirements then do nothing. 31# 32# If CEF can use Chromium's default value but would like to enforce additional 33# validation requirements then go to 3B. 34# 35# If CEF cannot or should not use Chromium's default value then choose one of 36# the following: 37# 38# 1. If CEF requires a different value either globally or based on the platform: 39# - Add an assert() for the value in CEF's top-level BUILD.gn file. 40# - Add the required value in GetRequiredArgs(). 41# - Result: CEF's required value will be used. The user cannot override the 42# value via GN_DEFINES. 43# 44# 2. If CEF requires a different value based on the build type or CPU 45# architecture: 46# - Add an assert() for the value in CEF's top-level BUILD.gn file. 47# - Add the required value in GetConfigArgs(). 48# - Result: CEF's required value will be used. The user cannot override the 49# value via GN_DEFINES. 50# 51# 3. If CEF recommends (but does not require) a different value either globally 52# or based on the platform: 53# A. Set the default value: 54# - Add the recommended value in GetRecommendedDefaultArgs(). 55# - Result: CEF's recommended value will be used by default. The user can 56# override the value via GN_DEFINES. 57# 58# B. If CEF has additional validation requirements: 59# - Add the default Chromium value in GetChromiumDefaultArgs(). 60# - Perform validation in ValidateArgs(). 61# - Result: An AssertionError will be thrown if validation fails. 62 63from __future__ import absolute_import 64from __future__ import print_function 65import os 66import shlex 67import sys 68 69# The CEF directory is the parent directory of _this_ script. 70cef_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) 71# The src directory is the parent directory of the CEF directory. 72src_dir = os.path.abspath(os.path.join(cef_dir, os.pardir)) 73 74# Determine the platform. 75if sys.platform == 'win32': 76 platform = 'windows' 77elif sys.platform == 'darwin': 78 platform = 'mac' 79elif sys.platform.startswith('linux'): 80 platform = 'linux' 81else: 82 print('Unknown operating system platform') 83 sys.exit() 84 85 86def msg(msg): 87 print('NOTE: ' + msg) 88 89 90def NameValueListToDict(name_value_list): 91 """ 92 Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary 93 of the pairs. If a string is simply NAME, then the value in the dictionary 94 is set to True. If VALUE can be converted to a boolean or integer, it is. 95 """ 96 result = {} 97 for item in name_value_list: 98 tokens = item.split('=', 1) 99 if len(tokens) == 2: 100 token_value = tokens[1] 101 if token_value.lower() == 'true': 102 token_value = True 103 elif token_value.lower() == 'false': 104 token_value = False 105 else: 106 # If we can make it an int, use that, otherwise, use the string. 107 try: 108 token_value = int(token_value) 109 except ValueError: 110 pass 111 # Set the variable to the supplied value. 112 result[tokens[0]] = token_value 113 else: 114 # No value supplied, treat it as a boolean and set it. 115 result[tokens[0]] = True 116 return result 117 118 119def ShlexEnv(env_name): 120 """ 121 Split an environment variable using shell-like syntax. 122 """ 123 flags = os.environ.get(env_name, []) 124 if flags: 125 flags = shlex.split(flags) 126 return flags 127 128 129def MergeDicts(*dict_args): 130 """ 131 Given any number of dicts, shallow copy and merge into a new dict. 132 Precedence goes to key value pairs in latter dicts. 133 """ 134 result = {} 135 for dictionary in dict_args: 136 result.update(dictionary) 137 return result 138 139 140def GetValueString(val): 141 """ 142 Return the string representation of |val| expected by GN. 143 """ 144 if isinstance(val, bool): 145 if val: 146 return 'true' 147 else: 148 return 'false' 149 elif isinstance(val, int): 150 return val 151 else: 152 return '"%s"' % val 153 return val 154 155 156def GetChromiumDefaultArgs(): 157 """ 158 Return default GN args. These must match the Chromium defaults. 159 Only args that may be retrieved via GetArgValue() need to be specified here. 160 """ 161 # Search for these values in declare_args() sections of *.gni files to find 162 # the defaults. 163 164 defaults = { 165 'dcheck_always_on': False, 166 'is_asan': False, 167 'is_debug': True, 168 'is_official_build': False, 169 'target_cpu': 'x64', 170 } 171 172 if platform == 'linux': 173 defaults['use_sysroot'] = True 174 175 if platform == 'windows': 176 defaults['is_win_fastlink'] = False 177 defaults['visual_studio_path'] = '' 178 defaults['visual_studio_version'] = '' 179 defaults['visual_studio_runtime_dirs'] = '' 180 defaults['windows_sdk_path'] = '' 181 182 return defaults 183 184 185def GetArgValue(args, key): 186 """ 187 Return an existing GN arg value or the Chromium default. 188 """ 189 defaults = GetChromiumDefaultArgs() 190 assert key in defaults, "No default Chromium value specified for %s" % key 191 return args.get(key, defaults[key]) 192 193 194def GetRecommendedDefaultArgs(): 195 """ 196 Return recommended default GN args that differ from Chromium defaults. 197 """ 198 # Search for these values in declare_args() sections of *.gni files to find 199 # the defaults. 200 201 result = { 202 # Enable NaCL. Default is true. False is recommended for faster builds. 203 'enable_nacl': False, 204 205 # Disable component builds. Default depends on the platform. True results 206 # in faster local builds but False is required to create a CEF binary 207 # distribution. 208 'is_component_build': False, 209 210 # Specify the current PGO phase. Default is 0 (turned off) for normal 211 # builds and 2 (used during the optimization phase) for official Windows 212 # and macOS builds. Currently turned off for CEF because it requires 213 # additional setup and is not yet tested. See issue #2956. 214 'chrome_pgo_phase': 0, 215 216 # Disable support for background apps, which don't make sense with CEF. 217 # Default is enabled on desktop platforms. This feature was also causing 218 # strange shutdown crashes when using the Chrome runtime with a Debug 219 # component build on Windows. 220 'enable_background_mode': False, 221 222 # Disable support for resource allowlist generation. When enabled this 223 # introduces a Windows official build dependency on the 224 # "//chrome:chrome_dll" target, which will fail to build with CEF. 225 'enable_resource_allowlist_generation': False, 226 } 227 228 if platform != 'windows': 229 # Only allow non-component Debug builds on non-Windows platforms. These 230 # builds will fail on Windows due to linker issues (running out of memory, 231 # etc). See https://bitbucket.org/chromiumembedded/cef/issues/2679. 232 result['forbid_non_component_debug_builds'] = False 233 234 if platform == 'linux': 235 # Use a sysroot environment. Default is true. False is recommended for local 236 # builds. 237 # Run the following commands to download the sysroot environment: 238 # x86 build only: $ export GYP_DEFINES='target_arch=ia32' 239 # x86 or x64 build: $ gclient runhooks 240 result['use_sysroot'] = False 241 242 # Don't add the `-Wl,--fatal-warnings` linker flag when building on Ubuntu 243 # 14 (Trusty) host systems. It results in errors like the following: 244 # ld.lld: error: found local symbol '__bss_start' in global part of symbol 245 # table in file /usr/lib/x86_64-linux-gnu/libGL.so 246 # TODO(cef): Remove this flag once we require a newer host system. 247 result['fatal_linker_warnings'] = False 248 249 return result 250 251 252def GetGNEnvArgs(): 253 """ 254 Return GN args specified via the GN_DEFINES env variable. 255 """ 256 return NameValueListToDict(ShlexEnv('GN_DEFINES')) 257 258 259def GetRequiredArgs(): 260 """ 261 Return required GN args. Also enforced by assert() in //cef/BUILD.gn. 262 """ 263 result = { 264 # Set ENABLE_PRINTING=1 ENABLE_BASIC_PRINTING=1. 265 'enable_basic_printing': True, 266 # ENABLE_SERVICE_DISCOVERY=0 for print preview support 267 'enable_print_preview': True, 268 'optimize_webui': True, 269 # Enable support for Widevine CDM. 270 'enable_widevine': True, 271 272 # Don't use the chrome style plugin. 273 'clang_use_chrome_plugins': False, 274 } 275 276 if platform == 'linux': 277 # Don't generate Chromium installer packages. This avoids GN dependency 278 # errors with CEF (see issue #2301). 279 # Due to the way this variable is declared in chrome/installer/BUILD.gn it 280 # can't be enforced by assert(). 281 result['enable_linux_installer'] = False 282 283 return result 284 285 286def GetMergedArgs(build_args): 287 """ 288 Return merged GN args. 289 """ 290 dict = MergeDicts(GetRecommendedDefaultArgs(), GetGNEnvArgs(), build_args) 291 292 # Verify that the user is not trying to override required args. 293 required = GetRequiredArgs() 294 for key in required.keys(): 295 if key in dict: 296 assert dict[key] == required[key], \ 297 "%s=%s is required" % (key, GetValueString(required[key])) 298 299 return MergeDicts(dict, required) 300 301 302def ValidateArgs(args): 303 """ 304 Validate GN arg combinations that we know about. Also provide suggestions 305 where appropriate. 306 """ 307 dcheck_always_on = GetArgValue(args, 'dcheck_always_on') 308 is_asan = GetArgValue(args, 'is_asan') 309 is_debug = GetArgValue(args, 'is_debug') 310 is_official_build = GetArgValue(args, 'is_official_build') 311 target_cpu = GetArgValue(args, 'target_cpu') 312 313 if platform == 'linux': 314 use_sysroot = GetArgValue(args, 'use_sysroot') 315 316 if platform == 'windows': 317 is_win_fastlink = GetArgValue(args, 'is_win_fastlink') 318 visual_studio_path = GetArgValue(args, 'visual_studio_path') 319 visual_studio_version = GetArgValue(args, 'visual_studio_version') 320 visual_studio_runtime_dirs = GetArgValue(args, 'visual_studio_runtime_dirs') 321 windows_sdk_path = GetArgValue(args, 'windows_sdk_path') 322 323 # Target CPU architecture. 324 # - Windows supports "x86", "x64" and "arm64". 325 # - Mac supports "x64" and "arm64". 326 # - Linux supports only "x64" unless using a sysroot environment. 327 if platform == 'mac': 328 assert target_cpu in ('x64', 'arm64'), 'target_cpu must be "x64" or "arm64"' 329 elif platform == 'windows': 330 assert target_cpu in ('x86', 'x64', 331 'arm64'), 'target_cpu must be "x86", "x64" or "arm64"' 332 elif platform == 'linux': 333 assert target_cpu in ( 334 'x86', 'x64', 'arm', 335 'arm64'), 'target_cpu must be "x86", "x64", "arm" or "arm64"' 336 337 if platform == 'linux': 338 if target_cpu == 'x86': 339 assert use_sysroot, 'target_cpu="x86" requires use_sysroot=true' 340 elif target_cpu == 'arm': 341 assert use_sysroot, 'target_cpu="arm" requires use_sysroot=true' 342 elif target_cpu == 'arm64': 343 assert use_sysroot, 'target_cpu="arm64" requires use_sysroot=true' 344 345 # ASAN requires Release builds. 346 if is_asan: 347 assert not is_debug, "is_asan=true requires is_debug=false" 348 if not dcheck_always_on: 349 msg('is_asan=true recommends dcheck_always_on=true') 350 351 # Official build requires Release builds. 352 if is_official_build: 353 assert not is_debug, "is_official_build=true requires is_debug=false" 354 355 if platform == 'windows': 356 # Official builds should not use /DEBUG:FASTLINK. 357 if is_official_build: 358 assert not is_win_fastlink, "is_official_build=true precludes is_win_fastlink=true" 359 360 # Windows custom toolchain requirements. 361 # 362 # Required GN arguments: 363 # visual_studio_path="<path to VS root>" 364 # The directory that contains Visual Studio. For example, a subset of 365 # "C:\Program Files (x86)\Microsoft Visual Studio 14.0". 366 # visual_studio_version="<VS version>" 367 # The VS version. For example, "2015". 368 # visual_studio_runtime_dirs="<path to VS CRT>" 369 # The directory that contains the VS CRT. For example, the contents of 370 # "C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x64" plus 371 # "C:\Windows\System32\ucrtbased.dll" 372 # windows_sdk_path="<path to WinSDK>" 373 # The directory that contains the Win SDK. For example, a subset of 374 # "C:\Program Files (x86)\Windows Kits\10". 375 # 376 # Required environment variables: 377 # DEPOT_TOOLS_WIN_TOOLCHAIN=0 378 # GYP_MSVS_OVERRIDE_PATH=<path to VS root, must match visual_studio_path> 379 # GYP_MSVS_VERSION=<VS version, must match visual_studio_version> 380 # CEF_VCVARS=none 381 # 382 # Optional environment variables (required if vcvarsall.bat does not exist): 383 # INCLUDE=<VS include paths> 384 # LIB=<VS library paths> 385 # PATH=<VS executable paths> 386 # 387 # See comments in gclient_hook.py for environment variable usage. 388 # 389 if visual_studio_path != '': 390 assert visual_studio_version != '', 'visual_studio_path requires visual_studio_version' 391 assert visual_studio_runtime_dirs != '', 'visual_studio_path requires visual_studio_runtime_dirs' 392 assert windows_sdk_path != '', 'visual_studio_path requires windows_sdk_path' 393 394 assert os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '') == '0', \ 395 "visual_studio_path requires DEPOT_TOOLS_WIN_TOOLCHAIN=0 env variable" 396 397 msvs_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH', '') 398 assert msvs_path == visual_studio_path and os.path.exists(msvs_path), \ 399 "visual_studio_path requires matching GYP_MSVS_OVERRIDE_PATH env variable" 400 401 msvs_version = os.environ.get('GYP_MSVS_VERSION', '') 402 assert msvs_version == visual_studio_version, \ 403 "visual_studio_version requires matching GYP_MSVS_VERSION env variable" 404 405 assert os.environ.get('CEF_VCVARS', '') == 'none', \ 406 "visual_studio_path requires CEF_VCVARS=none env variable" 407 408 # If vcvarsall.bat exists then environment variables will be derived from 409 # there and any specified INCLUDE/LIB values will be ignored by Chromium 410 # (PATH is retained because it might contain required VS runtime 411 # libraries). If this file does not exist then the INCLUDE/LIB/PATH values 412 # are also required by Chromium. 413 vcvars_path = os.path.join(msvs_path, 'VC', 'vcvarsall.bat') 414 if not os.path.exists(vcvars_path): 415 vcvars_path = os.path.join(msvs_path, 'VC', 'Auxiliary', 'Build', 416 'vcvarsall.bat') 417 if os.path.exists(vcvars_path): 418 if 'INCLUDE' in os.environ: 419 del os.environ['INCLUDE'] 420 if 'LIB' in os.environ: 421 del os.environ['LIB'] 422 if 'LIBPATH' in os.environ: 423 del os.environ['LIBPATH'] 424 else: 425 assert 'INCLUDE' in os.environ \ 426 and 'LIB' in os.environ \ 427 and 'PATH' in os.environ, \ 428 "visual_studio_path requires INCLUDE, LIB and PATH env variables" 429 430 431def GetConfigArgs(args, is_debug, cpu): 432 """ 433 Return merged GN args for the configuration and validate. 434 """ 435 add_args = {} 436 437 # Cannot create is_official_build=true is_debug=true builds. 438 # This restriction is enforced in //build/config/BUILDCONFIG.gn. 439 # Instead, our "official Debug" build is a Release build with dchecks and 440 # symbols. Symbols will be generated by default for official builds; see the 441 # definition of 'symbol_level' in //build/config/compiler/compiler.gni. 442 if is_debug and GetArgValue(args, 'is_official_build'): 443 is_debug = False 444 add_args['dcheck_always_on'] = True 445 446 result = MergeDicts(args, add_args, { 447 'is_debug': is_debug, 448 'target_cpu': cpu, 449 }) 450 451 if platform == 'linux' and not cpu.startswith('arm'): 452 # Remove any arm-related values from non-arm configs. 453 for key in result.keys(): 454 if key.startswith('arm_'): 455 del result[key] 456 457 ValidateArgs(result) 458 return result 459 460 461def GetConfigArgsSandbox(platform, args, is_debug, cpu): 462 """ 463 Return merged GN args for the cef_sandbox configuration and validate. 464 """ 465 add_args = { 466 # Avoid libucrt.lib linker errors. 467 'use_allocator_shim': False, 468 469 # PartitionAlloc is selected as the default allocator in some cases. 470 # We can't use it because it requires use_allocator_shim=true. 471 'use_allocator': "none", 472 473 # Avoid /LTCG linker warnings and generate smaller lib files. 474 'is_official_build': False, 475 476 # Enable base target customizations necessary for distribution of the 477 # cef_sandbox static library. 478 'is_cef_sandbox_build': True, 479 } 480 481 if platform == 'windows': 482 # Avoid Debug build linker errors caused by custom libc++. 483 add_args['use_custom_libcxx'] = False 484 485 # Avoid dependency on //third_party/perfetto:libperfetto which fails to 486 # build with MSVC libc++. 487 add_args['enable_base_tracing'] = False 488 489 # Don't enable -Wmax-tokens in combination with MSVC libc++. 490 add_args['enable_wmax_tokens'] = False 491 492 # Allow non-component Debug builds for the sandbox. 493 add_args['forbid_non_component_debug_builds'] = False 494 495 result = MergeDicts(args, add_args, { 496 'is_debug': is_debug, 497 'target_cpu': cpu, 498 }) 499 500 ValidateArgs(result) 501 return result 502 503 504def LinuxSysrootExists(cpu): 505 """ 506 Returns true if the sysroot for the specified |cpu| architecture exists. 507 """ 508 # Directory that contains sysroots. 509 sysroot_root = os.path.join(src_dir, 'build', 'linux') 510 # CPU-specific sysroot directory names. 511 # Should match the values in build/config/sysroot.gni. 512 if cpu == 'x86': 513 sysroot_name = 'debian_sid_i386-sysroot' 514 elif cpu == 'x64': 515 sysroot_name = 'debian_sid_amd64-sysroot' 516 elif cpu == 'arm': 517 sysroot_name = 'debian_sid_arm-sysroot' 518 elif cpu == 'arm64': 519 sysroot_name = 'debian_sid_arm64-sysroot' 520 else: 521 raise Exception('Unrecognized sysroot CPU: %s' % cpu) 522 523 return os.path.isdir(os.path.join(sysroot_root, sysroot_name)) 524 525 526def GetAllPlatformConfigs(build_args): 527 """ 528 Return a map of directory name to GN args for the current platform. 529 """ 530 result = {} 531 532 # Merged args without validation. 533 args = GetMergedArgs(build_args) 534 535 create_debug = True 536 537 # Don't create debug directories for asan builds. 538 if GetArgValue(args, 'is_asan'): 539 create_debug = False 540 msg('Not generating Debug configuration due to is_asan=true') 541 542 supported_cpus = [] 543 544 if platform == 'linux': 545 use_sysroot = GetArgValue(args, 'use_sysroot') 546 if use_sysroot: 547 # Only generate configurations for sysroots that have been installed. 548 for cpu in ('x86', 'x64', 'arm', 'arm64'): 549 if LinuxSysrootExists(cpu): 550 supported_cpus.append(cpu) 551 else: 552 msg('Not generating %s configuration due to missing sysroot directory' 553 % cpu) 554 else: 555 supported_cpus = ['x64'] 556 elif platform == 'windows': 557 supported_cpus = ['x86', 'x64'] 558 if os.environ.get('CEF_ENABLE_ARM64', '') == '1': 559 supported_cpus.append('arm64') 560 elif platform == 'mac': 561 supported_cpus = ['x64'] 562 if os.environ.get('CEF_ENABLE_ARM64', '') == '1': 563 supported_cpus.append('arm64') 564 else: 565 raise Exception('Unsupported platform') 566 567 for cpu in supported_cpus: 568 if create_debug: 569 result['Debug_GN_' + cpu] = GetConfigArgs(args, True, cpu) 570 result['Release_GN_' + cpu] = GetConfigArgs(args, False, cpu) 571 572 if platform in ('windows', 'mac') and GetArgValue(args, 573 'is_official_build'): 574 # Build cef_sandbox.lib with a different configuration. 575 if create_debug: 576 result['Debug_GN_' + cpu + '_sandbox'] = GetConfigArgsSandbox( 577 platform, args, True, cpu) 578 result['Release_GN_' + cpu + '_sandbox'] = GetConfigArgsSandbox( 579 platform, args, False, cpu) 580 581 return result 582 583 584def GetConfigFileContents(args): 585 """ 586 Generate config file contents for the arguments. 587 """ 588 pairs = [] 589 for k in sorted(args.keys()): 590 pairs.append("%s=%s" % (k, GetValueString(args[k]))) 591 return "\n".join(pairs) 592 593 594# Program entry point. 595if __name__ == '__main__': 596 import sys 597 598 # Allow override of the platform via the command-line for testing. 599 if len(sys.argv) > 1: 600 platform = sys.argv[1] 601 if not platform in ('linux', 'mac', 'windows'): 602 sys.stderr.write('Usage: %s <platform>\n' % sys.argv[0]) 603 sys.exit() 604 605 print('Platform: %s' % platform) 606 607 # Dump the configuration based on platform and environment. 608 configs = GetAllPlatformConfigs({}) 609 for dir, config in configs.items(): 610 print('\n\nout/%s:\n' % dir) 611 print(GetConfigFileContents(config)) 612