• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
2# reserved. Use of this source code is governed by a BSD-style license that
3# can be found in the LICENSE file.
4
5from __future__ import absolute_import
6from __future__ import print_function
7from datetime import datetime
8import json
9from io import open
10from optparse import OptionParser
11import os
12import re
13import shlex
14import shutil
15import subprocess
16import sys
17import tempfile
18import zipfile
19
20is_python2 = sys.version_info.major == 2
21
22if is_python2:
23  from urllib import FancyURLopener
24  from urllib2 import urlopen
25else:
26  from urllib.request import FancyURLopener, urlopen
27
28##
29# Default URLs.
30##
31
32depot_tools_url = 'https://chromium.googlesource.com/chromium/tools/depot_tools.git'
33depot_tools_archive_url = 'https://storage.googleapis.com/chrome-infra/depot_tools.zip'
34
35cef_git_url = 'https://bitbucket.org/chromiumembedded/cef.git'
36
37chromium_channel_json_url = 'https://omahaproxy.appspot.com/all.json'
38
39##
40# Global system variables.
41##
42
43# Script directory.
44script_dir = os.path.dirname(__file__)
45
46##
47# Helper functions.
48##
49
50
51def msg(message):
52  """ Output a message. """
53  sys.stdout.write('--> ' + message + "\n")
54
55
56def run(command_line, working_dir, depot_tools_dir=None, output_file=None):
57  """ Runs the specified command. """
58  # add depot_tools to the path
59  env = os.environ
60  if not depot_tools_dir is None:
61    env['PATH'] = depot_tools_dir + os.pathsep + env['PATH']
62
63  sys.stdout.write('-------- Running "'+command_line+'" in "'+\
64                   working_dir+'"...'+"\n")
65  if not options.dryrun:
66    args = shlex.split(command_line.replace('\\', '\\\\'))
67
68    if not output_file:
69      return subprocess.check_call(
70          args, cwd=working_dir, env=env, shell=(sys.platform == 'win32'))
71    try:
72      msg('Writing %s' % output_file)
73      with open(output_file, 'w', encoding='utf-8') as fp:
74        return subprocess.check_call(
75            args,
76            cwd=working_dir,
77            env=env,
78            shell=(sys.platform == 'win32'),
79            stderr=subprocess.STDOUT,
80            stdout=fp)
81    except subprocess.CalledProcessError:
82      msg('ERROR Run failed. See %s for output.' % output_file)
83      raise
84
85
86def create_directory(path):
87  """ Creates a directory if it doesn't already exist. """
88  if not os.path.exists(path):
89    msg("Creating directory %s" % (path))
90    if not options.dryrun:
91      os.makedirs(path)
92
93
94def delete_directory(path):
95  """ Removes an existing directory. """
96  if os.path.exists(path):
97    msg("Removing directory %s" % (path))
98    if not options.dryrun:
99      shutil.rmtree(path, onerror=onerror)
100
101
102def copy_directory(source, target, allow_overwrite=False):
103  """ Copies a directory from source to target. """
104  if not options.dryrun and os.path.exists(target):
105    if not allow_overwrite:
106      raise Exception("Directory %s already exists" % (target))
107    remove_directory(target)
108  if os.path.exists(source):
109    msg("Copying directory %s to %s" % (source, target))
110    if not options.dryrun:
111      shutil.copytree(source, target)
112
113
114def move_directory(source, target, allow_overwrite=False):
115  """ Copies a directory from source to target. """
116  if not options.dryrun and os.path.exists(target):
117    if not allow_overwrite:
118      raise Exception("Directory %s already exists" % (target))
119    remove_directory(target)
120  if os.path.exists(source):
121    msg("Moving directory %s to %s" % (source, target))
122    if not options.dryrun:
123      shutil.move(source, target)
124
125
126def is_git_checkout(path):
127  """ Returns true if the path represents a git checkout. """
128  return os.path.exists(os.path.join(path, '.git'))
129
130
131def exec_cmd(cmd, path):
132  """ Execute the specified command and return the result. """
133  out = ''
134  err = ''
135  sys.stdout.write("-------- Running \"%s\" in \"%s\"...\n" % (cmd, path))
136  parts = cmd.split()
137  try:
138    process = subprocess.Popen(
139        parts,
140        cwd=path,
141        stdout=subprocess.PIPE,
142        stderr=subprocess.PIPE,
143        shell=(sys.platform == 'win32'))
144    out, err = process.communicate()
145  except IOError as e:
146    (errno, strerror) = e.args
147    raise
148  except:
149    raise
150  return {'out': out.decode('utf-8'), 'err': err.decode('utf-8')}
151
152
153def get_git_hash(path, branch):
154  """ Returns the git hash for the specified branch/tag/hash. """
155  cmd = "%s rev-parse %s" % (git_exe, branch)
156  result = exec_cmd(cmd, path)
157  if result['out'] != '':
158    return result['out'].strip()
159  return 'Unknown'
160
161
162def get_git_date(path, branch):
163  """ Returns the date for the specified branch/tag/hash. """
164  cmd = "%s show -s --format=%%ct %s" % (git_exe, branch)
165  result = exec_cmd(cmd, path)
166  if result['out'] != '':
167    return datetime.utcfromtimestamp(
168        int(result['out'].strip())).strftime('%Y-%m-%d %H:%M:%S UTC')
169  return 'Unknown'
170
171
172def get_git_url(path):
173  """ Returns the origin url for the specified path. """
174  cmd = "%s config --get remote.origin.url" % (git_exe)
175  result = exec_cmd(cmd, path)
176  if result['out'] != '':
177    return result['out'].strip()
178  return 'Unknown'
179
180
181def download_and_extract(src, target):
182  """ Extracts the contents of src, which may be a URL or local file, to the
183      target directory. """
184  temporary = False
185
186  if src[:4] == 'http':
187    # Attempt to download a URL.
188    opener = FancyURLopener({})
189    response = opener.open(src)
190
191    temporary = True
192    handle, archive_path = tempfile.mkstemp(suffix='.zip')
193    os.write(handle, response.read())
194    os.close(handle)
195  elif os.path.exists(src):
196    # Use a local file.
197    archive_path = src
198  else:
199    raise Exception('Path type is unsupported or does not exist: ' + src)
200
201  if not zipfile.is_zipfile(archive_path):
202    raise Exception('Not a valid zip archive: ' + src)
203
204  # Attempt to extract the archive file.
205  try:
206    os.makedirs(target)
207    zf = zipfile.ZipFile(archive_path, 'r')
208    zf.extractall(target)
209  except:
210    shutil.rmtree(target, onerror=onerror)
211    raise
212  zf.close()
213
214  # Delete the archive file if temporary.
215  if temporary and os.path.exists(archive_path):
216    os.remove(archive_path)
217
218
219def read_file(path):
220  """ Read a file. """
221  if os.path.exists(path):
222    with open(path, 'r', encoding='utf-8') as fp:
223      return fp.read()
224  else:
225    raise Exception("Path does not exist: %s" % (path))
226
227
228def write_fp(fp, data):
229  if is_python2:
230    fp.write(data.decode('utf-8'))
231  else:
232    fp.write(data)
233
234
235def write_file(path, data):
236  """ Write a file. """
237  msg('Writing %s' % path)
238  if not options.dryrun:
239    with open(path, 'w', encoding='utf-8') as fp:
240      write_fp(fp, data)
241
242
243def read_config_file(path):
244  """ Read a configuration file. """
245  # Parse the contents.
246  return eval(read_file(path), {'__builtins__': None}, None)
247
248
249def write_config_file(path, contents):
250  """ Write a configuration file. """
251  data = "{\n"
252  for key in sorted(contents.keys()):
253    data += "  '%s': '%s',\n" % (key, contents[key])
254  data += "}\n"
255  write_file(path, data)
256
257
258def read_branch_config_file(path):
259  """ Read the CEF branch from the specified path. """
260  config_file = os.path.join(path, 'cef.branch')
261  if os.path.isfile(config_file):
262    contents = read_config_file(config_file)
263    if 'branch' in contents:
264      return contents['branch']
265  return ''
266
267
268def write_branch_config_file(path, branch):
269  """ Write the CEF branch to the specified path. """
270  config_file = os.path.join(path, 'cef.branch')
271  if not os.path.isfile(config_file):
272    write_config_file(config_file, {'branch': branch})
273
274
275def apply_patch(name):
276  patch_file = os.path.join(cef_dir, 'patch', 'patches', name)
277  if os.path.exists(patch_file + ".patch"):
278    # Attempt to apply the patch file.
279    patch_tool = os.path.join(cef_dir, 'tools', 'patcher.py')
280    run('%s %s --patch-file "%s" --patch-dir "%s"' %
281        (python_exe, patch_tool, patch_file,
282         chromium_src_dir), chromium_src_dir, depot_tools_dir)
283
284
285def apply_deps_patch():
286  """ Patch the Chromium DEPS file before `gclient sync` if necessary. """
287  deps_path = os.path.join(chromium_src_dir, deps_file)
288  if os.path.isfile(deps_path):
289    msg("Chromium DEPS file: %s" % (deps_path))
290    apply_patch(deps_file)
291  else:
292    raise Exception("Path does not exist: %s" % (deps_path))
293
294
295def apply_runhooks_patch():
296  """ Patch the Chromium runhooks files before `gclient runhooks` if necessary. """
297  apply_patch('runhooks')
298
299
300def run_patch_updater(args='', output_file=None):
301  """ Run the patch updater script. """
302  tool = os.path.join(cef_src_dir, 'tools', 'patch_updater.py')
303  if len(args) > 0:
304    args = ' ' + args
305  run('%s %s%s' % (python_exe, tool, args), cef_src_dir, depot_tools_dir,
306      output_file)
307
308
309def onerror(func, path, exc_info):
310  """
311  Error handler for ``shutil.rmtree``.
312
313  If the error is due to an access error (read only file)
314  it attempts to add write permission and then retries.
315
316  If the error is for another reason it re-raises the error.
317
318  Usage : ``shutil.rmtree(path, onerror=onerror)``
319  """
320  import stat
321  if not os.access(path, os.W_OK):
322    # Is the error an access error ?
323    os.chmod(path, stat.S_IWUSR)
324    func(path)
325  else:
326    raise
327
328
329def read_json_url(url):
330  """ Read a JSON URL. """
331  msg('Downloading %s' % url)
332  return json.loads(urlopen(url).read())
333
334
335g_channel_data = None
336
337
338def get_chromium_channel_data(os, channel, param=None):
339  """ Returns all data for the specified Chromium channel. """
340  global g_channel_data
341
342  if g_channel_data is None:
343    g_channel_data = read_json_url(chromium_channel_json_url)
344    assert len(g_channel_data) > 0, 'Failed to load Chromium channel data'
345
346  for oses in g_channel_data:
347    if oses['os'] == os:
348      for version in oses['versions']:
349        if version['channel'] == channel:
350          assert version['os'] == os
351          assert version['channel'] == channel
352          if param is None:
353            return version
354          else:
355            assert param in version, 'Missing parameter %s for Chromium channel %s %s' % (
356                param, os, channel)
357            return version[param]
358      raise Exception("Invalid Chromium channel value: %s" % channel)
359  raise Exception("Invalid Chromium os value: %s" % os)
360
361
362def get_chromium_channel_commit(os, channel):
363  """ Returns the current branch commit for the specified Chromium channel. """
364  return get_chromium_channel_data(os, channel, 'branch_commit')
365
366
367def get_chromium_channel_version(os, channel):
368  """ Returns the current version for the specified Chromium channel. """
369  return get_chromium_channel_data(os, channel, 'current_version')
370
371
372def get_chromium_master_position(commit):
373  """ Returns the closest master position for the specified Chromium commit. """
374  # Using -2 because a "Publish DEPS" commit which does not have a master
375  # position may be first.
376  cmd = "%s log -2 %s" % (git_exe, commit)
377  result = exec_cmd(cmd, chromium_src_dir)
378  if result['out'] != '':
379    match = re.search(r'refs/heads/master@{#([\d]+)}', result['out'])
380    assert match != None, 'Failed to find position'
381    return int(match.groups()[0])
382  return None
383
384
385def get_chromium_master_commit(position):
386  """ Returns the master commit for the specified Chromium commit position. """
387  cmd = '%s log -1 --grep=refs/heads/master@{#%s} origin/master' % (
388      git_exe, str(position))
389  result = exec_cmd(cmd, chromium_src_dir)
390  if result['out'] != '':
391    match = re.search(r'^commit ([a-f0-9]+)', result['out'])
392    assert match != None, 'Failed to find commit'
393    return match.groups()[0]
394  return None
395
396
397def get_chromium_versions(commit):
398  """ Returns the list of Chromium versions that contain the specified commit.
399      Versions are listed oldest to newest. """
400  cmd = '%s tag --contains %s' % (git_exe, commit)
401  result = exec_cmd(cmd, chromium_src_dir)
402  if result['out'] != '':
403    return [line.strip() for line in result['out'].strip().split('\n')]
404  return None
405
406
407def get_build_compat_versions():
408  """ Returns the compatible Chromium and (optionally) depot_tools versions
409      specified by the CEF checkout. """
410  compat_path = os.path.join(cef_dir, 'CHROMIUM_BUILD_COMPATIBILITY.txt')
411  msg("Reading %s" % compat_path)
412  config = read_config_file(compat_path)
413
414  if not 'chromium_checkout' in config:
415    raise Exception("Missing chromium_checkout value in %s" % (compat_path))
416  return config
417
418
419def get_chromium_target_version(os='win', channel='canary', target_distance=0):
420  """ Returns the target Chromium version based on a heuristic. """
421  # The current compatible version from CEF.
422  compat_version = chromium_compat_version
423  compat_commit = get_git_hash(chromium_src_dir, compat_version)
424  if compat_version == compat_commit:
425    versions = get_chromium_versions(compat_commit)
426    if len(versions) > 0:
427      compat_version = 'refs/tags/' + versions[0]
428      # Closest version may not align with the compat position, so adjust the
429      # commit to match.
430      compat_commit = get_git_hash(chromium_src_dir, compat_version)
431  compat_position = get_chromium_master_position(compat_commit)
432  compat_date = get_git_date(chromium_src_dir, compat_commit)
433
434  # The most recent channel version from the Chromium website.
435  channel_version = 'refs/tags/' + get_chromium_channel_version(os, channel)
436  channel_commit = get_chromium_channel_commit(os, channel)
437  channel_position = get_chromium_master_position(channel_commit)
438  channel_date = get_git_date(chromium_src_dir, channel_commit)
439
440  if compat_position >= channel_position:
441    # Already compatible with the channel version or newer.
442    target_version = compat_version
443    target_commit = compat_commit
444    target_position = compat_position
445    target_date = compat_date
446  elif target_distance <= 0 or compat_position + target_distance >= channel_position:
447    # Channel version is within the target distance.
448    target_version = channel_version
449    target_commit = channel_commit
450    target_position = channel_position
451    target_date = channel_date
452  else:
453    # Find an intermediary version that's within the target distance.
454    target_position = compat_position + target_distance
455    target_commit = get_chromium_master_commit(target_position)
456    versions = get_chromium_versions(target_commit)
457    if len(versions) > 0:
458      target_version = 'refs/tags/' + versions[0]
459      # Closest version may not align with the target position, so adjust the
460      # commit and position to match.
461      target_commit = get_git_hash(chromium_src_dir, target_version)
462      target_position = get_chromium_master_position(target_commit)
463    else:
464      target_version = target_commit
465    target_date = get_git_date(chromium_src_dir, target_commit)
466
467  msg("")
468  msg("Computed Chromium update for %s %s at distance %d" % (os, channel,
469                                                             target_distance))
470  msg("Compat:  %s %s %s (#%d)" % (compat_date, compat_version, compat_commit,
471                                   compat_position))
472  msg("Target:  %s %s %s (#%d)" % (target_date, target_version, target_commit,
473                                   target_position))
474  msg("Channel: %s %s %s (#%d)" % (channel_date, channel_version,
475                                   channel_commit, channel_position))
476  msg("")
477
478  return target_version
479
480
481def get_build_directory_name(is_debug):
482  build_dir = ('Debug' if is_debug else 'Release') + '_'
483
484  # CEF uses a consistent directory naming scheme for GN via
485  # GetAllPlatformConfigs in tools/gn_args.py.
486  if options.x64build:
487    build_dir += 'GN_x64'
488  elif options.armbuild:
489    build_dir += 'GN_arm'
490  elif options.arm64build:
491    build_dir += 'GN_arm64'
492  else:
493    build_dir += 'GN_x86'
494  return build_dir
495
496
497def read_update_file():
498  update_path = os.path.join(cef_src_dir, 'CHROMIUM_UPDATE.txt')
499  if not os.path.exists(update_path):
500    msg("Missing file: %s" % update_path)
501    return None
502
503  msg("Reading %s" % update_path)
504  return read_config_file(update_path)
505
506
507def log_chromium_changes():
508  """ Evaluate the Chromium checkout for changes. """
509  config = read_update_file()
510  if config is None:
511    msg("Skipping Chromium changes log.")
512    return
513
514  if 'files' in config:
515    out_file = os.path.join(download_dir, 'chromium_update_changes.diff')
516    if os.path.exists(out_file):
517      os.remove(out_file)
518
519    old_commit = get_chromium_master_commit(
520        get_chromium_master_position(chromium_compat_version))
521    new_commit = get_chromium_master_commit(
522        get_chromium_master_position(chromium_checkout))
523
524    cmd = '%s diff --relative --no-prefix %s..%s -- %s' % (
525        git_exe, old_commit, new_commit, ' '.join(config['files']))
526    result = exec_cmd(cmd, chromium_src_dir)
527    if result['out'] != '':
528      write_file(out_file, result['out'])
529
530
531def check_pattern_matches(output_file=None):
532  """ Evaluate the Chromium checkout for pattern matches. """
533  config = read_update_file()
534  if config is None:
535    msg("Skipping Chromium pattern matching.")
536    return
537
538  if 'patterns' in config:
539    if output_file is None:
540      fp = sys.stdout
541    else:
542      msg('Writing %s' % output_file)
543      fp = open(output_file, 'w', encoding='utf-8')
544
545    has_output = False
546    for entry in config['patterns']:
547      msg("Evaluating pattern: %s" % entry['pattern'])
548
549      # Read patterns from a file to avoid formatting problems.
550      pattern_handle, pattern_file = tempfile.mkstemp()
551      os.write(pattern_handle, entry['pattern'])
552      os.close(pattern_handle)
553
554      cmd = '%s grep -n -f %s' % (git_exe, pattern_file)
555      result = exec_cmd(cmd, chromium_src_dir)
556      os.remove(pattern_file)
557
558      if result['out'] != '':
559        write_msg = True
560        re_exclude = re.compile(
561            entry['exclude_matches']) if 'exclude_matches' in entry else None
562
563        for line in result['out'].split('\n'):
564          line = line.strip()
565          if len(line) == 0:
566            continue
567          skip = not re_exclude is None and re_exclude.match(line) != None
568          if not skip:
569            if write_msg:
570              if has_output:
571                write_fp(fp, '\n')
572              write_fp(fp,
573                       '!!!! WARNING: FOUND PATTERN: %s\n' % entry['pattern'])
574              if 'message' in entry:
575                write_fp(fp, entry['message'] + '\n')
576              write_fp(fp, '\n')
577              write_msg = False
578            write_fp(fp, line + '\n')
579            has_output = True
580
581    if not output_file is None:
582      if has_output:
583        msg('ERROR Matches found. See %s for output.' % out_file)
584      else:
585        write_fp(fp, 'Good news! No matches.\n')
586      fp.close()
587
588    if has_output:
589      # Don't continue when we know the build will be wrong.
590      sys.exit(1)
591
592
593##
594# Program entry point.
595##
596
597# Cannot be loaded as a module.
598if __name__ != "__main__":
599  sys.stderr.write('This file cannot be loaded as a module!')
600  sys.exit()
601
602# Parse command-line options.
603disc = """
604This utility implements automation for the download, update, build and
605distribution of CEF.
606"""
607
608parser = OptionParser(description=disc)
609
610# Setup options.
611parser.add_option(
612    '--download-dir',
613    dest='downloaddir',
614    metavar='DIR',
615    help='Download directory with no spaces [required].')
616parser.add_option(
617    '--depot-tools-dir',
618    dest='depottoolsdir',
619    metavar='DIR',
620    help='Download directory for depot_tools.',
621    default='')
622parser.add_option('--depot-tools-archive', dest='depottoolsarchive',
623                  help='Zip archive file that contains a single top-level '+\
624                       'depot_tools directory.', default='')
625parser.add_option('--branch', dest='branch',
626                  help='Branch of CEF to build (master, 3987, ...). This '+\
627                       'will be used to name the CEF download directory and '+\
628                       'to identify the correct URL if --url is not '+\
629                       'specified. The default value is master.',
630                  default='master')
631parser.add_option('--url', dest='url',
632                  help='CEF download URL. If not specified the default URL '+\
633                       'will be used.',
634                  default='')
635parser.add_option('--chromium-url', dest='chromiumurl',
636                  help='Chromium download URL. If not specified the default '+\
637                       'URL will be used.',
638                  default='')
639parser.add_option('--checkout', dest='checkout',
640                  help='Version of CEF to checkout. If not specified the '+\
641                       'most recent remote version of the branch will be used.',
642                  default='')
643parser.add_option('--chromium-checkout', dest='chromiumcheckout',
644                  help='Version of Chromium to checkout (Git '+\
645                       'branch/hash/tag). This overrides the value specified '+\
646                       'by CEF in CHROMIUM_BUILD_COMPATIBILITY.txt.',
647                  default='')
648parser.add_option('--chromium-channel', dest='chromiumchannel',
649                  help='Chromium channel to check out (canary, dev, beta or '+\
650                       'stable). This overrides the value specified by CEF '+\
651                       'in CHROMIUM_BUILD_COMPATIBILITY.txt.',
652                  default='')
653parser.add_option('--chromium-channel-distance', dest='chromiumchanneldistance',
654                  help='The target number of commits to step in the '+\
655                       'channel, or 0 to use the newest channel version. '+\
656                       'Used in combination with --chromium-channel.',
657                  default='')
658
659# Miscellaneous options.
660parser.add_option(
661    '--force-config',
662    action='store_true',
663    dest='forceconfig',
664    default=False,
665    help='Force creation of a new gclient config file.')
666parser.add_option('--force-clean',
667                  action='store_true', dest='forceclean', default=False,
668                  help='Force a clean checkout of Chromium and CEF. This will'+\
669                       ' trigger a new update, build and distribution.')
670parser.add_option('--force-clean-deps',
671                  action='store_true', dest='forcecleandeps', default=False,
672                  help='Force a clean checkout of Chromium dependencies. Used'+\
673                       ' in combination with --force-clean.')
674parser.add_option(
675    '--dry-run',
676    action='store_true',
677    dest='dryrun',
678    default=False,
679    help="Output commands without executing them.")
680parser.add_option('--dry-run-platform', dest='dryrunplatform', default=None,
681                  help='Simulate a dry run on the specified platform '+\
682                       '(windows, mac, linux). Must be used in combination'+\
683                       ' with the --dry-run flag.')
684
685# Update-related options.
686parser.add_option('--force-update',
687                  action='store_true', dest='forceupdate', default=False,
688                  help='Force a Chromium and CEF update. This will trigger a '+\
689                       'new build and distribution.')
690parser.add_option('--no-update',
691                  action='store_true', dest='noupdate', default=False,
692                  help='Do not update Chromium or CEF. Pass --force-build or '+\
693                       '--force-distrib if you desire a new build or '+\
694                       'distribution.')
695parser.add_option('--no-cef-update',
696                  action='store_true', dest='nocefupdate', default=False,
697                  help='Do not update CEF. Pass --force-build or '+\
698                       '--force-distrib if you desire a new build or '+\
699                       'distribution.')
700parser.add_option('--force-cef-update',
701                  action='store_true', dest='forcecefupdate', default=False,
702                  help='Force a CEF update. This will cause local changes in '+\
703                       'the CEF checkout to be discarded and patch files to '+\
704                       'be reapplied.')
705parser.add_option(
706    '--no-chromium-update',
707    action='store_true',
708    dest='nochromiumupdate',
709    default=False,
710    help='Do not update Chromium.')
711parser.add_option(
712    '--no-depot-tools-update',
713    action='store_true',
714    dest='nodepottoolsupdate',
715    default=False,
716    help='Do not update depot_tools.')
717parser.add_option('--fast-update',
718                  action='store_true', dest='fastupdate', default=False,
719                  help='Update existing Chromium/CEF checkouts for fast incremental '+\
720                       'builds by attempting to minimize the number of modified files. '+\
721                       'The update will fail if there are unstaged CEF changes or if '+\
722                       'Chromium changes are not included in a patch file.')
723parser.add_option(
724    '--force-patch-update',
725    action='store_true',
726    dest='forcepatchupdate',
727    default=False,
728    help='Force update of patch files.')
729parser.add_option(
730    '--resave',
731    action='store_true',
732    dest='resave',
733    default=False,
734    help='Resave patch files.')
735parser.add_option(
736    '--log-chromium-changes',
737    action='store_true',
738    dest='logchromiumchanges',
739    default=False,
740    help='Create a log of the Chromium changes.')
741
742# Build-related options.
743parser.add_option('--force-build',
744                  action='store_true', dest='forcebuild', default=False,
745                  help='Force CEF debug and release builds. This builds '+\
746                       '[build-target] on all platforms and chrome_sandbox '+\
747                       'on Linux.')
748parser.add_option(
749    '--no-build',
750    action='store_true',
751    dest='nobuild',
752    default=False,
753    help='Do not build CEF.')
754parser.add_option(
755    '--build-target',
756    dest='buildtarget',
757    default='cefclient',
758    help='Target name(s) to build (defaults to "cefclient").')
759parser.add_option(
760    '--build-tests',
761    action='store_true',
762    dest='buildtests',
763    default=False,
764    help='Also build the test target specified via --test-target.')
765parser.add_option(
766    '--no-debug-build',
767    action='store_true',
768    dest='nodebugbuild',
769    default=False,
770    help="Don't perform the CEF debug build.")
771parser.add_option(
772    '--no-release-build',
773    action='store_true',
774    dest='noreleasebuild',
775    default=False,
776    help="Don't perform the CEF release build.")
777parser.add_option(
778    '--verbose-build',
779    action='store_true',
780    dest='verbosebuild',
781    default=False,
782    help='Show all command lines while building.')
783parser.add_option(
784    '--build-failure-limit',
785    dest='buildfailurelimit',
786    default=1,
787    type="int",
788    help='Keep going until N jobs fail.')
789parser.add_option('--build-log-file',
790                  action='store_true', dest='buildlogfile', default=False,
791                  help='Write build logs to file. The file will be named '+\
792                       '"build-[branch]-[debug|release].log" in the download '+\
793                       'directory.')
794parser.add_option(
795    '--x64-build',
796    action='store_true',
797    dest='x64build',
798    default=False,
799    help='Create a 64-bit build.')
800parser.add_option(
801    '--arm-build',
802    action='store_true',
803    dest='armbuild',
804    default=False,
805    help='Create an ARM build.')
806parser.add_option(
807    '--arm64-build',
808    action='store_true',
809    dest='arm64build',
810    default=False,
811    help='Create an ARM64 build.')
812
813# Test-related options.
814parser.add_option(
815    '--run-tests',
816    action='store_true',
817    dest='runtests',
818    default=False,
819    help='Run the ceftests target.')
820parser.add_option(
821    '--no-debug-tests',
822    action='store_true',
823    dest='nodebugtests',
824    default=False,
825    help="Don't run debug build tests.")
826parser.add_option(
827    '--no-release-tests',
828    action='store_true',
829    dest='noreleasetests',
830    default=False,
831    help="Don't run release build tests.")
832parser.add_option(
833    '--test-target',
834    dest='testtarget',
835    default='ceftests',
836    help='Test target name to build (defaults to "ceftests").')
837parser.add_option(
838    '--test-prefix',
839    dest='testprefix',
840    default='',
841    help='Prefix for running the test executable (e.g. `xvfb-run` on Linux).')
842parser.add_option(
843    '--test-args',
844    dest='testargs',
845    default='',
846    help='Arguments that will be passed to the test executable.')
847
848# Distribution-related options.
849parser.add_option(
850    '--force-distrib',
851    action='store_true',
852    dest='forcedistrib',
853    default=False,
854    help='Force creation of a CEF binary distribution.')
855parser.add_option(
856    '--no-distrib',
857    action='store_true',
858    dest='nodistrib',
859    default=False,
860    help="Don't create a CEF binary distribution.")
861parser.add_option(
862    '--minimal-distrib',
863    action='store_true',
864    dest='minimaldistrib',
865    default=False,
866    help='Create a minimal CEF binary distribution.')
867parser.add_option(
868    '--minimal-distrib-only',
869    action='store_true',
870    dest='minimaldistribonly',
871    default=False,
872    help='Create a minimal CEF binary distribution only.')
873parser.add_option(
874    '--client-distrib',
875    action='store_true',
876    dest='clientdistrib',
877    default=False,
878    help='Create a client CEF binary distribution.')
879parser.add_option(
880    '--client-distrib-only',
881    action='store_true',
882    dest='clientdistribonly',
883    default=False,
884    help='Create a client CEF binary distribution only.')
885parser.add_option(
886    '--sandbox-distrib',
887    action='store_true',
888    dest='sandboxdistrib',
889    default=False,
890    help='Create a cef_sandbox static library distribution.')
891parser.add_option(
892    '--sandbox-distrib-only',
893    action='store_true',
894    dest='sandboxdistribonly',
895    default=False,
896    help='Create a cef_sandbox static library distribution only.')
897parser.add_option(
898    '--no-distrib-docs',
899    action='store_true',
900    dest='nodistribdocs',
901    default=False,
902    help="Don't create CEF documentation.")
903parser.add_option(
904    '--no-distrib-archive',
905    action='store_true',
906    dest='nodistribarchive',
907    default=False,
908    help="Don't create archives for output directories.")
909parser.add_option(
910    '--clean-artifacts',
911    action='store_true',
912    dest='cleanartifacts',
913    default=False,
914    help='Clean the artifacts output directory.')
915parser.add_option(
916    '--distrib-subdir',
917    dest='distribsubdir',
918    default='',
919    help='CEF distrib dir name, child of chromium/src/cef/binary_distrib')
920parser.add_option(
921    '--distrib-subdir-suffix',
922    dest='distribsubdirsuffix',
923    default='',
924    help='CEF distrib dir name suffix, child of chromium/src/cef/binary_distrib'
925)
926
927(options, args) = parser.parse_args()
928
929if options.downloaddir is None:
930  print("The --download-dir option is required.")
931  parser.print_help(sys.stderr)
932  sys.exit()
933
934# Opt into component-specific flags for later use.
935if options.noupdate:
936  options.nocefupdate = True
937  options.nochromiumupdate = True
938  options.nodepottoolsupdate = True
939
940if options.runtests:
941  options.buildtests = True
942
943if (options.nochromiumupdate and options.forceupdate) or \
944   (options.nocefupdate and options.forceupdate) or \
945   (options.nobuild and options.forcebuild) or \
946   (options.nodistrib and options.forcedistrib) or \
947   ((options.forceclean or options.forcecleandeps) and options.fastupdate) or \
948   (options.chromiumcheckout and options.chromiumchannel):
949  print("Invalid combination of options.")
950  parser.print_help(sys.stderr)
951  sys.exit()
952
953if (options.noreleasebuild and \
954     (options.minimaldistrib or options.minimaldistribonly or \
955      options.clientdistrib or options.clientdistribonly)) or \
956   (options.minimaldistribonly + options.clientdistribonly + options.sandboxdistribonly > 1):
957  print('Invalid combination of options.')
958  parser.print_help(sys.stderr)
959  sys.exit()
960
961if options.x64build + options.armbuild + options.arm64build > 1:
962  print('Invalid combination of options.')
963  parser.print_help(sys.stderr)
964  sys.exit()
965
966if (options.buildtests or options.runtests) and len(options.testtarget) == 0:
967  print("A test target must be specified via --test-target.")
968  parser.print_help(sys.stderr)
969  sys.exit()
970
971# Operating system.
972if options.dryrun and options.dryrunplatform is not None:
973  platform = options.dryrunplatform
974  if not platform in ['windows', 'mac', 'linux']:
975    print('Invalid dry-run-platform value: %s' % (platform))
976    sys.exit()
977elif sys.platform == 'win32':
978  platform = 'windows'
979elif sys.platform == 'darwin':
980  platform = 'mac'
981elif sys.platform.startswith('linux'):
982  platform = 'linux'
983else:
984  print('Unknown operating system platform')
985  sys.exit()
986
987if options.clientdistrib or options.clientdistribonly:
988  if platform == 'linux' or (platform == 'windows' and options.arm64build):
989    client_app = 'cefsimple'
990  else:
991    client_app = 'cefclient'
992  if options.buildtarget.find(client_app) == -1:
993    print('A client distribution cannot be generated if --build-target ' +
994          'excludes %s.' % client_app)
995    parser.print_help(sys.stderr)
996    sys.exit()
997
998# CEF branch.
999cef_branch = options.branch
1000
1001branch_is_master = (cef_branch == 'master' or cef_branch == 'trunk')
1002if not branch_is_master:
1003  # Verify that the branch value is numeric.
1004  if not cef_branch.isdigit():
1005    print('Invalid branch value: %s' % cef_branch)
1006    sys.exit()
1007
1008  # Verify the minimum supported branch number.
1009  if int(cef_branch) < 3071:
1010    print('The requested branch (%s) is too old to build using this tool. ' +
1011          'The minimum supported branch is 3071.' % cef_branch)
1012    sys.exit()
1013
1014# True if the requested branch is 3538 or newer.
1015branch_is_3538_or_newer = (branch_is_master or int(cef_branch) >= 3538)
1016
1017# True if the requested branch is 3945 or newer.
1018branch_is_3945_or_newer = (branch_is_master or int(cef_branch) >= 3945)
1019
1020# Enable Python 3 usage in Chromium for branches 3945 and newer.
1021if branch_is_3945_or_newer and not is_python2 and \
1022    not 'GCLIENT_PY3' in os.environ.keys():
1023  os.environ['GCLIENT_PY3'] = '1'
1024
1025if not branch_is_3945_or_newer and \
1026  (not is_python2 or bool(int(os.environ.get('GCLIENT_PY3', '0')))):
1027  print('Python 3 is not supported with branch 3904 and older ' +
1028        '(set GCLIENT_PY3=0 and run with Python 2 executable).')
1029  sys.exit()
1030
1031if options.armbuild:
1032  if platform != 'linux':
1033    print('The ARM build option is only supported on Linux.')
1034    sys.exit()
1035
1036deps_file = 'DEPS'
1037
1038if platform == 'mac' and not (options.x64build or options.arm64build):
1039  print('32-bit MacOS builds are not supported. ' +
1040        'Add --x64-build or --arm64-build flag to generate a 64-bit build.')
1041  sys.exit()
1042
1043# Platforms that build a cef_sandbox library.
1044sandbox_lib_platforms = ['windows']
1045if branch_is_3538_or_newer:
1046  sandbox_lib_platforms.append('mac')
1047
1048if not platform in sandbox_lib_platforms and (options.sandboxdistrib or
1049                                              options.sandboxdistribonly):
1050  print('The sandbox distribution is not supported on this platform.')
1051  sys.exit()
1052
1053# Options that force the sources to change.
1054force_change = options.forceclean or options.forceupdate
1055
1056# Options that cause local changes to be discarded.
1057discard_local_changes = force_change or options.forcecefupdate
1058
1059if options.resave and (options.forcepatchupdate or discard_local_changes):
1060  print('--resave cannot be combined with options that modify or discard ' +
1061        'patches.')
1062  parser.print_help(sys.stderr)
1063  sys.exit()
1064
1065if platform == 'windows':
1066  # Avoid errors when the "vs_toolchain.py update" Chromium hook runs.
1067  os.environ['DEPOT_TOOLS_WIN_TOOLCHAIN'] = '0'
1068
1069download_dir = os.path.abspath(options.downloaddir)
1070chromium_dir = os.path.join(download_dir, 'chromium')
1071chromium_src_dir = os.path.join(chromium_dir, 'src')
1072out_src_dir = os.path.join(chromium_src_dir, 'out')
1073cef_src_dir = os.path.join(chromium_src_dir, 'cef')
1074
1075if options.fastupdate and os.path.exists(cef_src_dir):
1076  cef_dir = cef_src_dir
1077else:
1078  cef_dir = os.path.join(download_dir, 'cef')
1079
1080##
1081# Manage the download directory.
1082##
1083
1084# Create the download directory if necessary.
1085create_directory(download_dir)
1086
1087msg("Download Directory: %s" % (download_dir))
1088
1089##
1090# Manage the depot_tools directory.
1091##
1092
1093# Check if the depot_tools directory exists.
1094if options.depottoolsdir != '':
1095  depot_tools_dir = os.path.abspath(options.depottoolsdir)
1096else:
1097  depot_tools_dir = os.path.join(download_dir, 'depot_tools')
1098
1099msg("Depot Tools Directory: %s" % (depot_tools_dir))
1100
1101if not os.path.exists(depot_tools_dir):
1102  if platform == 'windows' and options.depottoolsarchive == '':
1103    # On Windows download depot_tools as an archive file since we can't assume
1104    # that git is already installed.
1105    options.depottoolsarchive = depot_tools_archive_url
1106
1107  if options.depottoolsarchive != '':
1108    # Extract depot_tools from an archive file.
1109    msg('Extracting %s to %s.' % \
1110        (options.depottoolsarchive, depot_tools_dir))
1111    if not options.dryrun:
1112      download_and_extract(options.depottoolsarchive, depot_tools_dir)
1113  else:
1114    # On Linux and OS X check out depot_tools using Git.
1115    run('git clone ' + depot_tools_url + ' ' + depot_tools_dir, download_dir)
1116
1117if not options.nodepottoolsupdate:
1118  # Update depot_tools.
1119  # On Windows this will download required python and git binaries.
1120  msg('Updating depot_tools')
1121  if platform == 'windows':
1122    run('update_depot_tools.bat', depot_tools_dir, depot_tools_dir)
1123  else:
1124    run('update_depot_tools', depot_tools_dir, depot_tools_dir)
1125
1126# Determine the executables to use.
1127if platform == 'windows':
1128  # Force use of the version bundled with depot_tools.
1129  git_exe = os.path.join(depot_tools_dir, 'git.bat')
1130  python_bat = 'python.bat' if is_python2 else 'python3.bat'
1131  python_exe = os.path.join(depot_tools_dir, python_bat)
1132  if options.dryrun and not os.path.exists(git_exe):
1133    sys.stdout.write("WARNING: --dry-run assumes that depot_tools" \
1134                     " is already in your PATH. If it isn't\nplease" \
1135                     " specify a --depot-tools-dir value.\n")
1136    git_exe = 'git.bat'
1137    python_exe = python_bat
1138else:
1139  git_exe = 'git'
1140  python_exe = sys.executable
1141
1142##
1143# Manage the cef directory.
1144##
1145
1146# Delete the existing CEF directory if requested.
1147if options.forceclean and os.path.exists(cef_dir):
1148  delete_directory(cef_dir)
1149
1150# Determine the type of CEF checkout to use.
1151if os.path.exists(cef_dir) and not is_git_checkout(cef_dir):
1152  raise Exception("Not a valid CEF Git checkout: %s" % (cef_dir))
1153
1154# Determine the CEF download URL to use.
1155cef_url = options.url.strip()
1156if cef_url == '':
1157  cef_url = cef_git_url
1158
1159# Verify that the requested CEF URL matches the existing checkout.
1160if not options.nocefupdate and os.path.exists(cef_dir):
1161  cef_existing_url = get_git_url(cef_dir)
1162  if cef_url != cef_existing_url:
1163    raise Exception(
1164        'Requested CEF checkout URL %s does not match existing URL %s' %
1165        (cef_url, cef_existing_url))
1166
1167msg("CEF Branch: %s" % (cef_branch))
1168msg("CEF URL: %s" % (cef_url))
1169msg("CEF Source Directory: %s" % (cef_dir))
1170
1171# Determine the CEF Git branch to use.
1172if options.checkout == '':
1173  # Target the most recent branch commit from the remote repo.
1174  if branch_is_master:
1175    cef_checkout = 'origin/master'
1176  else:
1177    cef_checkout = 'origin/' + cef_branch
1178else:
1179  cef_checkout = options.checkout
1180
1181# Create the CEF checkout if necessary.
1182if not options.nocefupdate and not os.path.exists(cef_dir):
1183  cef_checkout_new = True
1184  run('%s clone %s %s' % (git_exe, cef_url, cef_dir), download_dir,
1185      depot_tools_dir)
1186else:
1187  cef_checkout_new = False
1188
1189# Determine if the CEF checkout needs to change.
1190if not options.nocefupdate and os.path.exists(cef_dir):
1191  cef_current_hash = get_git_hash(cef_dir, 'HEAD')
1192
1193  if not cef_checkout_new:
1194    # Fetch updated sources.
1195    run('%s fetch' % (git_exe), cef_dir, depot_tools_dir)
1196
1197  cef_desired_hash = get_git_hash(cef_dir, cef_checkout)
1198  cef_checkout_changed = cef_checkout_new or force_change or \
1199                         options.forcecefupdate or \
1200                         cef_current_hash != cef_desired_hash
1201
1202  msg("CEF Current Checkout: %s" % (cef_current_hash))
1203  msg("CEF Desired Checkout: %s (%s)" % (cef_desired_hash, cef_checkout))
1204
1205  if cef_checkout_changed:
1206    if cef_dir == cef_src_dir:
1207      # Running in fast update mode. Backup and revert the patched files before
1208      # changing the CEF checkout.
1209      run_patch_updater("--backup --revert")
1210
1211    # Update the CEF checkout.
1212    run('%s checkout %s%s' %
1213      (git_exe, '--force ' if discard_local_changes else '', cef_checkout), \
1214      cef_dir, depot_tools_dir)
1215else:
1216  cef_checkout_changed = False
1217
1218build_compat_versions = get_build_compat_versions()
1219
1220if not options.nodepottoolsupdate and \
1221    'depot_tools_checkout' in build_compat_versions:
1222  # Update the depot_tools checkout.
1223  depot_tools_compat_version = build_compat_versions['depot_tools_checkout']
1224  run('%s checkout %s%s' %
1225      (git_exe, '--force ' if discard_local_changes else '', depot_tools_compat_version), \
1226      depot_tools_dir, depot_tools_dir)
1227
1228# Disable further depot_tools updates.
1229os.environ['DEPOT_TOOLS_UPDATE'] = '0'
1230
1231##
1232# Manage the out directory.
1233##
1234
1235out_dir = os.path.join(download_dir, 'out_' + cef_branch)
1236
1237# Delete the existing out directory if requested.
1238if options.forceclean and os.path.exists(out_dir):
1239  delete_directory(out_dir)
1240
1241msg("CEF Output Directory: %s" % (out_dir))
1242
1243##
1244# Manage the chromium directory.
1245##
1246
1247# Create the chromium directory if necessary.
1248create_directory(chromium_dir)
1249
1250if options.chromiumurl != '':
1251  chromium_url = options.chromiumurl
1252else:
1253  chromium_url = 'https://chromium.googlesource.com/chromium/src.git'
1254
1255# Create gclient configuration file.
1256gclient_file = os.path.join(chromium_dir, '.gclient')
1257if not os.path.exists(gclient_file) or options.forceconfig:
1258  # Exclude unnecessary directories. Intentionally written without newlines.
1259  gclient_spec = \
1260      "solutions = [{"+\
1261        "'managed': False,"+\
1262        "'name': 'src', "+\
1263        "'url': '" + chromium_url + "', "+\
1264        "'custom_deps': {"+\
1265          "'build': None, "+\
1266          "'build/scripts/command_wrapper/bin': None, "+\
1267          "'build/scripts/gsd_generate_index': None, "+\
1268          "'build/scripts/private/data/reliability': None, "+\
1269          "'build/scripts/tools/deps2git': None, "+\
1270          "'build/third_party/lighttpd': None, "+\
1271          "'commit-queue': None, "+\
1272          "'depot_tools': None, "+\
1273          "'src/chrome_frame/tools/test/reference_build/chrome': None, "+\
1274          "'src/chrome/tools/test/reference_build/chrome_linux': None, "+\
1275          "'src/chrome/tools/test/reference_build/chrome_mac': None, "+\
1276          "'src/chrome/tools/test/reference_build/chrome_win': None, "+\
1277        "}, "+\
1278        "'deps_file': '" + deps_file + "', "+\
1279        "'safesync_url': ''"+\
1280      "}]"
1281
1282  msg('Writing %s' % gclient_file)
1283  if not options.dryrun:
1284    with open(gclient_file, 'w', encoding='utf-8') as fp:
1285      write_fp(fp, gclient_spec)
1286
1287# Initial Chromium checkout.
1288if not options.nochromiumupdate and not os.path.exists(chromium_src_dir):
1289  chromium_checkout_new = True
1290  run("gclient sync --nohooks --with_branch_heads --jobs 16", \
1291      chromium_dir, depot_tools_dir)
1292else:
1293  chromium_checkout_new = False
1294
1295# Verify the Chromium checkout.
1296if not options.dryrun and not is_git_checkout(chromium_src_dir):
1297  raise Exception('Not a valid git checkout: %s' % (chromium_src_dir))
1298
1299if os.path.exists(chromium_src_dir):
1300  msg("Chromium URL: %s" % (get_git_url(chromium_src_dir)))
1301
1302# Fetch Chromium changes so that we can perform the necessary calculations using
1303# local history.
1304if not options.nochromiumupdate and os.path.exists(chromium_src_dir):
1305  # Fetch updated sources.
1306  run("%s fetch" % (git_exe), chromium_src_dir, depot_tools_dir)
1307  # Also fetch tags, which are required for release branch builds.
1308  run("%s fetch --tags" % (git_exe), chromium_src_dir, depot_tools_dir)
1309
1310# Determine the Chromium checkout options required by CEF.
1311chromium_compat_version = build_compat_versions['chromium_checkout']
1312if len(options.chromiumcheckout) > 0:
1313  chromium_checkout = options.chromiumcheckout
1314elif len(options.chromiumchannel) > 0:
1315  target_distance = int(options.chromiumchanneldistance
1316                       ) if len(options.chromiumchanneldistance) > 0 else 0
1317  chromium_checkout = get_chromium_target_version(
1318      channel=options.chromiumchannel, target_distance=target_distance)
1319else:
1320  chromium_checkout = chromium_compat_version
1321
1322# Determine if the Chromium checkout needs to change.
1323if not options.nochromiumupdate and os.path.exists(chromium_src_dir):
1324  chromium_current_hash = get_git_hash(chromium_src_dir, 'HEAD')
1325  chromium_desired_hash = get_git_hash(chromium_src_dir, chromium_checkout)
1326  chromium_checkout_changed = chromium_checkout_new or force_change or \
1327                              chromium_current_hash != chromium_desired_hash
1328
1329  msg("Chromium Current Checkout: %s" % (chromium_current_hash))
1330  msg("Chromium Desired Checkout: %s (%s)" % \
1331      (chromium_desired_hash, chromium_checkout))
1332else:
1333  chromium_checkout_changed = options.dryrun
1334
1335if cef_checkout_changed:
1336  if cef_dir != cef_src_dir and os.path.exists(cef_src_dir):
1337    # Delete the existing src/cef directory. It will be re-copied from the
1338    # download directory later.
1339    delete_directory(cef_src_dir)
1340elif chromium_checkout_changed and cef_dir == cef_src_dir:
1341  # Running in fast update mode. Backup and revert the patched files before
1342  # changing the Chromium checkout.
1343  run_patch_updater("--backup --revert")
1344
1345# Delete the existing src/out directory if requested.
1346if options.forceclean and os.path.exists(out_src_dir):
1347  delete_directory(out_src_dir)
1348
1349# Move the existing src/out directory to the correct location in the download
1350# directory. It will be moved back from the download directory later.
1351if os.path.exists(out_src_dir):
1352  old_branch = read_branch_config_file(out_src_dir)
1353  if old_branch != '' and (chromium_checkout_changed or
1354                           old_branch != cef_branch):
1355    old_out_dir = os.path.join(download_dir, 'out_' + old_branch)
1356    move_directory(out_src_dir, old_out_dir)
1357
1358# Update the Chromium checkout.
1359if chromium_checkout_changed:
1360  if not chromium_checkout_new and not options.fastupdate:
1361    if options.forceclean and options.forcecleandeps:
1362      # Remove all local changes including third-party git checkouts managed by
1363      # gclient.
1364      run("%s clean -dffx" % (git_exe), chromium_src_dir, depot_tools_dir)
1365    else:
1366      # Revert all changes in the Chromium checkout.
1367      run("gclient revert --nohooks", chromium_dir, depot_tools_dir)
1368
1369  # Checkout the requested branch.
1370  run("%s checkout %s%s" % \
1371    (git_exe, '--force ' if discard_local_changes else '', chromium_checkout), \
1372    chromium_src_dir, depot_tools_dir)
1373
1374  # Patch the Chromium DEPS file if necessary.
1375  apply_deps_patch()
1376
1377  # Update third-party dependencies including branch/tag information.
1378  run("gclient sync %s--nohooks --with_branch_heads --jobs 16" % \
1379      ('--reset ' if discard_local_changes else ''), chromium_dir, depot_tools_dir)
1380
1381  # Patch the Chromium runhooks scripts if necessary.
1382  apply_runhooks_patch()
1383
1384  # Runs hooks for files that have been modified in the local working copy.
1385  run("gclient runhooks --jobs 16", chromium_dir, depot_tools_dir)
1386
1387  # Delete the src/out directory created by `gclient sync`.
1388  delete_directory(out_src_dir)
1389
1390if cef_dir == cef_src_dir:
1391  # Running in fast update mode.
1392  if cef_checkout_changed or chromium_checkout_changed:
1393    # Check and restore the patched files.
1394    run_patch_updater("--reapply --restore")
1395elif os.path.exists(cef_dir) and not os.path.exists(cef_src_dir):
1396  # Restore the src/cef directory.
1397  copy_directory(cef_dir, cef_src_dir)
1398
1399# Restore the src/out directory.
1400out_src_dir_exists = os.path.exists(out_src_dir)
1401if os.path.exists(out_dir) and not out_src_dir_exists:
1402  move_directory(out_dir, out_src_dir)
1403  out_src_dir_exists = True
1404elif not out_src_dir_exists:
1405  create_directory(out_src_dir)
1406
1407# Write the config file for identifying the branch.
1408write_branch_config_file(out_src_dir, cef_branch)
1409
1410if options.logchromiumchanges and chromium_checkout != chromium_compat_version:
1411  log_chromium_changes()
1412
1413if options.forcepatchupdate or ((chromium_checkout_new or not options.fastupdate) and \
1414                                chromium_checkout_changed and \
1415                                chromium_checkout != chromium_compat_version):
1416  # Not using the known-compatible Chromium version. Try to update patch files.
1417  if options.logchromiumchanges:
1418    out_file = os.path.join(download_dir, 'chromium_update_patches.txt')
1419    if os.path.exists(out_file):
1420      os.remove(out_file)
1421  else:
1422    out_file = None
1423  run_patch_updater(output_file=out_file)
1424elif options.resave:
1425  # Resave patch files.
1426  run_patch_updater("--resave")
1427
1428if chromium_checkout != chromium_compat_version:
1429  if options.logchromiumchanges:
1430    out_file = os.path.join(download_dir, 'chromium_update_patterns.txt')
1431    if os.path.exists(out_file):
1432      os.remove(out_file)
1433  else:
1434    out_file = None
1435  check_pattern_matches(output_file=out_file)
1436
1437##
1438# Build CEF.
1439##
1440
1441if not options.nobuild and (chromium_checkout_changed or \
1442                            cef_checkout_changed or options.forcebuild or \
1443                            not out_src_dir_exists):
1444  # Building should also force a distribution.
1445  options.forcedistrib = True
1446
1447  # Make sure the GN configuration exists.
1448  if not options.dryrun and \
1449    not os.path.exists(os.path.join(cef_src_dir, 'BUILD.gn')):
1450    raise Exception('GN configuration does not exist.')
1451
1452  # Print all build-related environment variables including any that were set
1453  # previously.
1454  for key in os.environ.keys():
1455    if key.startswith('CEF_') or key.startswith('GCLIENT_') or \
1456       key.startswith('GN_') or key.startswith('GYP_') or \
1457       key.startswith('DEPOT_TOOLS_'):
1458      msg('%s=%s' % (key, os.environ[key]))
1459
1460  # Generate project files.
1461  tool = os.path.join(cef_src_dir, 'tools', 'gclient_hook.py')
1462  run('%s %s' % (python_exe, tool), cef_src_dir, depot_tools_dir)
1463
1464  # Build using Ninja.
1465  command = 'ninja '
1466  if options.verbosebuild:
1467    command += '-v '
1468  if options.buildfailurelimit != 1:
1469    command += '-k %d ' % options.buildfailurelimit
1470  command += '-C '
1471  target = ' ' + options.buildtarget
1472  if options.buildtests:
1473    target += ' ' + options.testtarget
1474  if platform == 'linux':
1475    target += ' chrome_sandbox'
1476
1477  # Make a CEF Debug build.
1478  if not options.nodebugbuild:
1479    build_path = os.path.join('out', get_build_directory_name(True))
1480    args_path = os.path.join(chromium_src_dir, build_path, 'args.gn')
1481    msg(args_path + ' contents:\n' + read_file(args_path))
1482
1483    run(command + build_path + target, chromium_src_dir, depot_tools_dir,
1484        os.path.join(download_dir, 'build-%s-debug.log' % (cef_branch)) \
1485          if options.buildlogfile else None)
1486
1487    if platform in sandbox_lib_platforms:
1488      # Make the separate cef_sandbox build when GN is_official_build=true.
1489      build_path += '_sandbox'
1490      if os.path.exists(os.path.join(chromium_src_dir, build_path)):
1491        args_path = os.path.join(chromium_src_dir, build_path, 'args.gn')
1492        msg(args_path + ' contents:\n' + read_file(args_path))
1493
1494        run(command + build_path + ' cef_sandbox', chromium_src_dir, depot_tools_dir,
1495            os.path.join(download_dir, 'build-%s-debug-sandbox.log' % (cef_branch)) \
1496              if options.buildlogfile else None)
1497
1498  # Make a CEF Release build.
1499  if not options.noreleasebuild:
1500    build_path = os.path.join('out', get_build_directory_name(False))
1501    args_path = os.path.join(chromium_src_dir, build_path, 'args.gn')
1502    msg(args_path + ' contents:\n' + read_file(args_path))
1503
1504    run(command + build_path + target, chromium_src_dir, depot_tools_dir,
1505        os.path.join(download_dir, 'build-%s-release.log' % (cef_branch)) \
1506          if options.buildlogfile else None)
1507
1508    if platform in sandbox_lib_platforms:
1509      # Make the separate cef_sandbox build when GN is_official_build=true.
1510      build_path += '_sandbox'
1511      if os.path.exists(os.path.join(chromium_src_dir, build_path)):
1512        args_path = os.path.join(chromium_src_dir, build_path, 'args.gn')
1513        msg(args_path + ' contents:\n' + read_file(args_path))
1514
1515        run(command + build_path + ' cef_sandbox', chromium_src_dir, depot_tools_dir,
1516            os.path.join(download_dir, 'build-%s-release-sandbox.log' % (cef_branch)) \
1517              if options.buildlogfile else None)
1518
1519elif not options.nobuild:
1520  msg('Not building. The source hashes have not changed and ' +
1521      'the output folder "%s" already exists' % (out_src_dir))
1522
1523##
1524# Run CEF tests.
1525##
1526
1527if options.runtests:
1528  if platform == 'windows':
1529    test_exe = '%s.exe' % options.testtarget
1530  elif platform == 'mac':
1531    test_exe = '%s.app/Contents/MacOS/%s' % (options.testtarget,
1532                                             options.testtarget)
1533  elif platform == 'linux':
1534    test_exe = options.testtarget
1535
1536  test_prefix = options.testprefix
1537  if len(test_prefix) > 0:
1538    test_prefix += ' '
1539
1540  test_args = options.testargs
1541  if len(test_args) > 0:
1542    test_args = ' ' + test_args
1543
1544  if not options.nodebugtests:
1545    build_path = os.path.join(out_src_dir, get_build_directory_name(True))
1546    test_path = os.path.join(build_path, test_exe)
1547    if os.path.exists(test_path):
1548      run(test_prefix + test_path + test_args, build_path, depot_tools_dir)
1549    else:
1550      msg('Not running debug tests. Missing executable: %s' % test_path)
1551
1552  if not options.noreleasetests:
1553    build_path = os.path.join(out_src_dir, get_build_directory_name(False))
1554    test_path = os.path.join(build_path, test_exe)
1555    if os.path.exists(test_path):
1556      run(test_prefix + test_path + test_args, build_path, depot_tools_dir)
1557    else:
1558      msg('Not running release tests. Missing executable: %s' % test_path)
1559
1560##
1561# Create the CEF binary distribution.
1562##
1563
1564if not options.nodistrib and (chromium_checkout_changed or \
1565                              cef_checkout_changed or options.forcedistrib):
1566  if not options.forceclean and options.cleanartifacts:
1567    # Clean the artifacts output directory.
1568    artifacts_path = os.path.join(cef_src_dir, 'binary_distrib')
1569    delete_directory(artifacts_path)
1570
1571  # Determine the requested distribution types.
1572  distrib_types = []
1573  if options.minimaldistribonly:
1574    distrib_types.append('minimal')
1575  elif options.clientdistribonly:
1576    distrib_types.append('client')
1577  elif options.sandboxdistribonly:
1578    distrib_types.append('sandbox')
1579  else:
1580    distrib_types.append('standard')
1581    if options.minimaldistrib:
1582      distrib_types.append('minimal')
1583    if options.clientdistrib:
1584      distrib_types.append('client')
1585    if options.sandboxdistrib:
1586      distrib_types.append('sandbox')
1587
1588  cef_tools_dir = os.path.join(cef_src_dir, 'tools')
1589
1590  # Create the requested distribution types.
1591  first_type = True
1592  for type in distrib_types:
1593    path = '%s make_distrib.py --output-dir=../binary_distrib/' % python_exe
1594
1595    if options.nodebugbuild or options.noreleasebuild or type != 'standard':
1596      path += ' --allow-partial'
1597    path = path + ' --ninja-build'
1598    if options.x64build:
1599      path += ' --x64-build'
1600    elif options.armbuild:
1601      path += ' --arm-build'
1602    elif options.arm64build:
1603      path += ' --arm64-build'
1604
1605    if type == 'minimal':
1606      path += ' --minimal'
1607    elif type == 'client':
1608      path += ' --client'
1609    elif type == 'sandbox':
1610      path += ' --sandbox'
1611
1612    if first_type:
1613      if options.nodistribdocs:
1614        path += ' --no-docs'
1615      if options.nodistribarchive:
1616        path += ' --no-archive'
1617      first_type = False
1618    else:
1619      # Don't create the symbol archives or documentation more than once.
1620      path += ' --no-symbols --no-docs'
1621
1622    # Override the subdirectory name of binary_distrib if the caller requested.
1623    if options.distribsubdir != '':
1624      path += ' --distrib-subdir=' + options.distribsubdir
1625    if options.distribsubdirsuffix != '':
1626      path += ' --distrib-subdir-suffix=' + options.distribsubdirsuffix
1627
1628    # Create the distribution.
1629    run(path, cef_tools_dir, depot_tools_dir)
1630