1#!/usr/bin/python 2# This Source Code Form is subject to the terms of the Mozilla Public 3# License, v. 2.0. If a copy of the MPL was not distributed with this 4# file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 6from __future__ import absolute_import, print_function 7 8HG_EXCLUSIONS = [".hg", ".hgignore", ".hgtags"] 9 10import os 11import sys 12import shutil 13import glob 14from optparse import OptionParser 15from subprocess import check_call 16 17topsrcdir = os.path.dirname(__file__) 18if topsrcdir == "": 19 topsrcdir = "." 20 21 22def check_call_noisy(cmd, *args, **kwargs): 23 print("Executing command:", cmd) 24 check_call(cmd, *args, **kwargs) 25 26 27def do_hg_pull(dir, repository, hg): 28 fulldir = os.path.join(topsrcdir, dir) 29 # clone if the dir doesn't exist, pull if it does 30 if not os.path.exists(fulldir): 31 check_call_noisy([hg, "clone", repository, fulldir]) 32 else: 33 cmd = [hg, "pull", "-u", "-R", fulldir] 34 if repository is not None: 35 cmd.append(repository) 36 check_call_noisy(cmd) 37 check_call( 38 [hg, "parent", "-R", fulldir, "--template=Updated to revision {node}.\n"] 39 ) 40 41 42def do_hg_replace(dir, repository, tag, exclusions, hg): 43 """ 44 Replace the contents of dir with the contents of repository, except for 45 files matching exclusions. 46 """ 47 fulldir = os.path.join(topsrcdir, dir) 48 if os.path.exists(fulldir): 49 shutil.rmtree(fulldir) 50 51 assert not os.path.exists(fulldir) 52 check_call_noisy([hg, "clone", "-u", tag, repository, fulldir]) 53 54 for thing in exclusions: 55 for excluded in glob.iglob(os.path.join(fulldir, thing)): 56 if os.path.isdir(excluded): 57 shutil.rmtree(excluded) 58 else: 59 os.remove(excluded) 60 61 62def toggle_trailing_blank_line(depname): 63 """If the trailing line is empty, then we'll delete it. 64 Otherwise we'll add a blank line.""" 65 lines = open(depname, "rb").readlines() 66 if not lines: 67 print("unexpected short file", file=sys.stderr) 68 return 69 70 if not lines[-1].strip(): 71 # trailing line is blank, removing it 72 open(depname, "wb").writelines(lines[:-1]) 73 else: 74 # adding blank line 75 open(depname, "ab").write(b"\n") 76 77 78def get_trailing_blank_line_state(depname): 79 lines = open(depname, "r").readlines() 80 if not lines: 81 print("unexpected short file", file=sys.stderr) 82 return "no blank line" 83 84 if not lines[-1].strip(): 85 return "has blank line" 86 return "no blank line" 87 88 89def update_nspr_or_nss(tag, depfile, destination, hgpath): 90 destination = destination.rstrip("/") 91 permanent_patch_dir = destination + "/patches" 92 temporary_patch_dir = destination + ".patches" 93 if os.path.exists(temporary_patch_dir): 94 print("please clean up leftover directory " + temporary_patch_dir) 95 sys.exit(2) 96 warn_if_patch_exists(permanent_patch_dir) 97 # protect patch directory from being removed by do_hg_replace 98 if os.path.exists(permanent_patch_dir): 99 shutil.move(permanent_patch_dir, temporary_patch_dir) 100 # now update the destination 101 print("reverting to HG version of %s to get its blank line state" % depfile) 102 check_call_noisy([options.hg, "revert", depfile]) 103 old_state = get_trailing_blank_line_state(depfile) 104 print("old state of %s is: %s" % (depfile, old_state)) 105 do_hg_replace(destination, hgpath, tag, HG_EXCLUSIONS, options.hg) 106 new_state = get_trailing_blank_line_state(depfile) 107 print("new state of %s is: %s" % (depfile, new_state)) 108 if old_state == new_state: 109 print("toggling blank line in: ", depfile) 110 toggle_trailing_blank_line(depfile) 111 tag_file = destination + "/TAG-INFO" 112 with open(tag_file, "w") as f: 113 f.write(tag) 114 # move patch directory back to a subdirectory 115 if os.path.exists(temporary_patch_dir): 116 shutil.move(temporary_patch_dir, permanent_patch_dir) 117 118 119def warn_if_patch_exists(path): 120 # If the given patch directory exists and contains at least one file, 121 # then print warning and wait for the user to acknowledge. 122 if os.path.isdir(path) and os.listdir(path): 123 print("========================================") 124 print("WARNING: At least one patch file exists") 125 print("in directory: " + path) 126 print("You must manually re-apply all patches") 127 print("after this script has completed!") 128 print("========================================") 129 input("Press Enter to continue...") 130 return 131 132 133o = OptionParser(usage="client.py [options] update_nspr tagname | update_nss tagname") 134o.add_option( 135 "--skip-mozilla", 136 dest="skip_mozilla", 137 action="store_true", 138 default=False, 139 help="Obsolete", 140) 141 142o.add_option( 143 "--hg", 144 dest="hg", 145 default=os.environ.get("HG", "hg"), 146 help="The location of the hg binary", 147) 148o.add_option( 149 "--repo", dest="repo", help="the repo to update from (default: upstream repo)" 150) 151 152try: 153 options, args = o.parse_args() 154 action = args[0] 155except IndexError: 156 o.print_help() 157 sys.exit(2) 158 159if action in ("checkout", "co"): 160 print("Warning: client.py checkout is obsolete.", file=sys.stderr) 161 pass 162elif action in ("update_nspr"): 163 (tag,) = args[1:] 164 depfile = "nsprpub/config/prdepend.h" 165 if not options.repo: 166 options.repo = "https://hg.mozilla.org/projects/nspr" 167 update_nspr_or_nss(tag, depfile, "nsprpub", options.repo) 168elif action in ("update_nss"): 169 (tag,) = args[1:] 170 depfile = "security/nss/coreconf/coreconf.dep" 171 if not options.repo: 172 options.repo = "https://hg.mozilla.org/projects/nss" 173 update_nspr_or_nss(tag, depfile, "security/nss", options.repo) 174else: 175 o.print_help() 176 sys.exit(2) 177