• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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