tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

clobber.py (4513B)


      1 #!/usr/bin/env python3
      2 # Copyright 2015 The Chromium Authors
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """This script provides methods for clobbering build directories."""
      7 
      8 import argparse
      9 import os
     10 import shutil
     11 import subprocess
     12 import sys
     13 
     14 
     15 def extract_gn_build_commands(build_ninja_file):
     16  """Extracts from a build.ninja the commands to run GN.
     17 
     18  The commands to run GN are the gn rule and build.ninja build step at the
     19  top of the build.ninja file. We want to keep these when deleting GN builds
     20  since we want to preserve the command-line flags to GN.
     21 
     22  On error, returns the empty string."""
     23  result = ""
     24  with open(build_ninja_file, 'r') as f:
     25    # Reads until the first empty line after the "build build.ninja:" target.
     26    # We assume everything before it necessary as well (eg the
     27    # "ninja_required_version" line).
     28    found_build_dot_ninja_target = False
     29    for line in f.readlines():
     30      result += line
     31      if line.startswith('build build.ninja:'):
     32        found_build_dot_ninja_target = True
     33      if found_build_dot_ninja_target and line[0] == '\n':
     34        return result
     35  return ''  # We got to EOF and didn't find what we were looking for.
     36 
     37 
     38 def _rmtree(d):
     39  # For unknown reasons (anti-virus?) rmtree of Chromium build directories
     40  # often fails on Windows.
     41  if sys.platform.startswith('win'):
     42    subprocess.check_call(['rmdir', '/s', '/q', d], shell=True)
     43  else:
     44    shutil.rmtree(d)
     45 
     46 
     47 def _clean_dir(build_dir):
     48  # Remove files/sub directories individually instead of recreating the build
     49  # dir because it fails when the build dir is symlinked or mounted.
     50  for e in os.scandir(build_dir):
     51    if e.is_dir():
     52      _rmtree(e.path)
     53    else:
     54      os.remove(e.path)
     55 
     56 
     57 def delete_build_dir(build_dir):
     58  # GN writes a build.ninja.d file. Note that not all GN builds have args.gn.
     59  build_ninja_d_file = os.path.join(build_dir, 'build.ninja.d')
     60  if not os.path.exists(build_ninja_d_file):
     61    _clean_dir(build_dir)
     62    return
     63 
     64  # GN builds aren't automatically regenerated when you sync. To avoid
     65  # messing with the GN workflow, erase everything but the args file, and
     66  # write a dummy build.ninja file that will automatically rerun GN the next
     67  # time Ninja is run.
     68  build_ninja_file = os.path.join(build_dir, 'build.ninja')
     69  build_commands = extract_gn_build_commands(build_ninja_file)
     70 
     71  try:
     72    gn_args_file = os.path.join(build_dir, 'args.gn')
     73    with open(gn_args_file, 'r') as f:
     74      args_contents = f.read()
     75  except IOError:
     76    args_contents = ''
     77 
     78  exception_during_rm = None
     79  try:
     80    # _clean_dir() may fail, such as when chrome.exe is running,
     81    # and we still want to restore args.gn/build.ninja/build.ninja.d, so catch
     82    # the exception and rethrow it later.
     83    # We manually rm files inside the build dir rather than using "gn clean/gen"
     84    # since we may not have run all necessary DEPS hooks yet at this point.
     85    _clean_dir(build_dir)
     86  except Exception as e:
     87    exception_during_rm = e
     88 
     89  # Put back the args file (if any).
     90  if args_contents != '':
     91    with open(gn_args_file, 'w') as f:
     92      f.write(args_contents)
     93 
     94  # Write the build.ninja file sufficiently to regenerate itself.
     95  with open(os.path.join(build_dir, 'build.ninja'), 'w') as f:
     96    if build_commands != '':
     97      f.write(build_commands)
     98    else:
     99      # Couldn't parse the build.ninja file, write a default thing.
    100      f.write('''ninja_required_version = 1.7.2
    101 
    102 rule gn
    103  command = gn -q gen //out/%s/
    104  description = Regenerating ninja files
    105 
    106 build build.ninja: gn
    107  generator = 1
    108  depfile = build.ninja.d
    109 ''' % (os.path.split(build_dir)[1]))
    110 
    111  # Write a .d file for the build which references a nonexistant file. This
    112  # will make Ninja always mark the build as dirty.
    113  with open(build_ninja_d_file, 'w') as f:
    114    f.write('build.ninja: nonexistant_file.gn\n')
    115 
    116  if exception_during_rm:
    117    # Rethrow the exception we caught earlier.
    118    raise exception_during_rm
    119 
    120 
    121 def clobber(out_dir):
    122  """Clobber contents of build sub directories.
    123 
    124  Don't delete the directory itself: some checkouts have the build directory
    125  mounted."""
    126  for f in os.listdir(out_dir):
    127    path = os.path.join(out_dir, f)
    128    if os.path.isdir(path):
    129      delete_build_dir(path)
    130 
    131 
    132 def main():
    133  parser = argparse.ArgumentParser()
    134  parser.add_argument('out_dir', help='The output directory to clobber')
    135  args = parser.parse_args()
    136  clobber(args.out_dir)
    137  return 0
    138 
    139 
    140 if __name__ == '__main__':
    141  sys.exit(main())