Annotation of embedaddon/rsync/packaging/pkglib.py, revision 1.1
1.1 ! misho 1: import os, sys, re, subprocess
! 2:
! 3: # This python3 library provides a few helpful routines that are
! 4: # used by the latest packaging scripts.
! 5:
! 6: default_encoding = 'utf-8'
! 7:
! 8: # Output the msg args to stderr. Accepts all the args that print() accepts.
! 9: def warn(*msg):
! 10: print(*msg, file=sys.stderr)
! 11:
! 12:
! 13: # Output the msg args to stderr and die with a non-zero return-code.
! 14: # Accepts all the args that print() accepts.
! 15: def die(*msg):
! 16: warn(*msg)
! 17: sys.exit(1)
! 18:
! 19:
! 20: # Set this to an encoding name or set it to None to avoid the default encoding idiom.
! 21: def set_default_encoding(enc):
! 22: default_encoding = enc
! 23:
! 24:
! 25: # Set shell=True if the cmd is a string; sets a default encoding unless raw=True was specified.
! 26: def _tweak_opts(cmd, opts, **maybe_set):
! 27: # This sets any maybe_set value that isn't already set AND creates a copy of opts for us.
! 28: opts = {**maybe_set, **opts}
! 29:
! 30: if type(cmd) == str:
! 31: opts = {'shell': True, **opts}
! 32:
! 33: want_raw = opts.pop('raw', False)
! 34: if default_encoding and not want_raw:
! 35: opts = {'encoding': default_encoding, **opts}
! 36:
! 37: capture = opts.pop('capture', None)
! 38: if capture:
! 39: if capture == 'stdout':
! 40: opts = {'stdout': subprocess.PIPE, **opts}
! 41: elif capture == 'stderr':
! 42: opts = {'stderr': subprocess.PIPE, **opts}
! 43: elif capture == 'output':
! 44: opts = {'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE, **opts}
! 45: elif capture == 'combined':
! 46: opts = {'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT, **opts}
! 47:
! 48: discard = opts.pop('discard', None)
! 49: if discard:
! 50: # We DO want to override any already set stdout|stderr values (unlike above).
! 51: if discard == 'stdout' or discard == 'output':
! 52: opts['stdout'] = subprocess.DEVNULL
! 53: if discard == 'stderr' or discard == 'output':
! 54: opts['stderr'] = subprocess.DEVNULL
! 55:
! 56: return opts
! 57:
! 58:
! 59: # This does a normal subprocess.run() with some auto-args added to make life easier.
! 60: def cmd_run(cmd, **opts):
! 61: return subprocess.run(cmd, **_tweak_opts(cmd, opts))
! 62:
! 63:
! 64: # Like cmd_run() with a default check=True specified.
! 65: def cmd_chk(cmd, **opts):
! 66: return subprocess.run(cmd, **_tweak_opts(cmd, opts, check=True))
! 67:
! 68:
! 69: # Capture stdout in a string and return the (output, return_code) tuple.
! 70: # Use capture='combined' opt to get both stdout and stderr together.
! 71: def cmd_txt_status(cmd, **opts):
! 72: input = opts.pop('input', None)
! 73: if input is not None:
! 74: opts['stdin'] = subprocess.PIPE
! 75: proc = subprocess.Popen(cmd, **_tweak_opts(cmd, opts, capture='stdout'))
! 76: out = proc.communicate(input=input)[0]
! 77: return (out, proc.returncode)
! 78:
! 79:
! 80: # Like cmd_txt_status() but just return the output.
! 81: def cmd_txt(cmd, **opts):
! 82: return cmd_txt_status(cmd, **opts)[0]
! 83:
! 84:
! 85: # Capture stdout in a string and return the output if the command has a 0 return code.
! 86: # Otherwise it throws an exception that indicates the return code and the output.
! 87: def cmd_txt_chk(cmd, **opts):
! 88: out, rc = cmd_txt_status(cmd, **opts)
! 89: if rc != 0:
! 90: cmd_err = f'Command "{cmd}" returned non-zero exit status "{rc}" and output:\n{out}'
! 91: raise Exception(cmd_err)
! 92: return out
! 93:
! 94:
! 95: # Starts a piped-output command of stdout (by default) and leaves it up to you to read
! 96: # the output and call communicate() on the returned object.
! 97: def cmd_pipe(cmd, **opts):
! 98: return subprocess.Popen(cmd, **_tweak_opts(cmd, opts, capture='stdout'))
! 99:
! 100:
! 101: # Runs a "git status" command and dies if the checkout is not clean (the
! 102: # arg fatal_unless_clean can be used to make that non-fatal. Returns a
! 103: # tuple of the current branch, the is_clean flag, and the status text.
! 104: def check_git_status(fatal_unless_clean=True, subdir='.'):
! 105: status_txt = cmd_txt_chk(f"cd '{subdir}' && git status")
! 106: is_clean = re.search(r'\nnothing to commit.+working (directory|tree) clean', status_txt) != None
! 107:
! 108: if not is_clean and fatal_unless_clean:
! 109: if subdir == '.':
! 110: subdir = ''
! 111: else:
! 112: subdir = f" *{subdir}*"
! 113: die(f"The{subdir} checkout is not clean:\n" + status_txt)
! 114:
! 115: m = re.match(r'^(?:# )?On branch (.+)\n', status_txt)
! 116: cur_branch = m[1] if m else None
! 117:
! 118: return (cur_branch, is_clean, status_txt)
! 119:
! 120:
! 121: # Calls check_git_status() on the current git checkout and (optionally) a subdir path's
! 122: # checkout. Use fatal_unless_clean to indicate if an unclean checkout is fatal or not.
! 123: # The master_branch arg indicates what branch we want both checkouts to be using, and
! 124: # if the branch is wrong the user is given the option of either switching to the right
! 125: # branch or aborting.
! 126: def check_git_state(master_branch, fatal_unless_clean=True, check_extra_dir=None):
! 127: cur_branch = check_git_status(fatal_unless_clean)[0]
! 128: branch = re.sub(r'^patch/([^/]+)/[^/]+$', r'\1', cur_branch) # change patch/BRANCH/PATCH_NAME into BRANCH
! 129: if branch != master_branch:
! 130: print(f"The checkout is not on the {master_branch} branch.")
! 131: if master_branch != 'master':
! 132: sys.exit(1)
! 133: ans = input(f"Do you want me to continue with --branch={branch}? [n] ")
! 134: if not ans or not re.match(r'^y', ans, flags=re.I):
! 135: sys.exit(1)
! 136: master_branch = branch
! 137:
! 138: if check_extra_dir and os.path.isdir(os.path.join(check_extra_dir, '.git')):
! 139: branch = check_git_status(fatal_unless_clean, check_extra_dir)[0]
! 140: if branch != master_branch:
! 141: print(f"The *{check_extra_dir}* checkout is on branch {branch}, not branch {master_branch}.")
! 142: ans = input(f"Do you want to change it to branch {master_branch}? [n] ")
! 143: if not ans or not re.match(r'^y', ans, flags=re.I):
! 144: sys.exit(1)
! 145: subdir.check_call(f"cd {check_extra_dir} && git checkout '{master_branch}'", shell=True)
! 146:
! 147: return (cur_branch, master_branch)
! 148:
! 149:
! 150: # Return the git hash of the most recent commit.
! 151: def latest_git_hash(branch):
! 152: out = cmd_txt_chk(['git', 'log', '-1', '--no-color', branch])
! 153: m = re.search(r'^commit (\S+)', out, flags=re.M)
! 154: if not m:
! 155: die(f"Unable to determine commit hash for master branch: {branch}")
! 156: return m[1]
! 157:
! 158:
! 159: # Return a set of all branch names that have the format "patch/BASE_BRANCH/NAME"
! 160: # for the given base_branch string. Just the NAME portion is put into the set.
! 161: def get_patch_branches(base_branch):
! 162: branches = set()
! 163: proc = cmd_pipe('git branch -l'.split())
! 164: for line in proc.stdout:
! 165: m = re.search(r' patch/([^/]+)/(.+)', line)
! 166: if m and m[1] == base_branch:
! 167: branches.add(m[2])
! 168: proc.communicate()
! 169: return branches
! 170:
! 171:
! 172: def mandate_gensend_hook():
! 173: hook = '.git/hooks/pre-push'
! 174: if not os.path.exists(hook):
! 175: print('Creating hook file:', hook)
! 176: cmd_chk(['./rsync', '-a', 'packaging/pre-push', hook])
! 177: else:
! 178: out, rc = cmd_txt_status(['fgrep', 'make gensend', hook], discard='output')
! 179: if rc:
! 180: die('Please add a "make gensend" into your', hook, 'script.')
! 181:
! 182:
! 183: # Snag the GENFILES values out of the Makefile.in file and return them as a list.
! 184: def get_gen_files(want_dir_plus_list=False):
! 185: cont_re = re.compile(r'\\\n')
! 186:
! 187: gen_files = [ ]
! 188:
! 189: auto_dir = os.path.join('auto-build-save', cmd_txt('git rev-parse --abbrev-ref HEAD').strip().replace('/', '%'))
! 190:
! 191: with open('Makefile.in', 'r', encoding='utf-8') as fh:
! 192: for line in fh:
! 193: if not gen_files:
! 194: chk = re.sub(r'^GENFILES=', '', line)
! 195: if line == chk:
! 196: continue
! 197: line = chk
! 198: m = re.search(r'\\$', line)
! 199: line = re.sub(r'^\s+|\s*\\\n?$|\s+$', '', line)
! 200: gen_files += line.split()
! 201: if not m:
! 202: break
! 203:
! 204: if want_dir_plus_list:
! 205: return (auto_dir, gen_files)
! 206:
! 207: return [ os.path.join(auto_dir, fn) for fn in gen_files ]
! 208:
! 209:
! 210: def get_rsync_version():
! 211: with open('version.h', 'r', encoding='utf-8') as fh:
! 212: txt = fh.read()
! 213: m = re.match(r'^#define\s+RSYNC_VERSION\s+"(\d.+?)"', txt)
! 214: if m:
! 215: return m[1]
! 216: die("Unable to find RSYNC_VERSION define in version.h")
! 217:
! 218:
! 219: def get_NEWS_version_info():
! 220: rel_re = re.compile(r'^\| \S{2} \w{3} \d{4}\s+\|\s+(?P<ver>\d+\.\d+\.\d+)\s+\|\s+(?P<pdate>\d{2} \w{3} \d{4})?\s+\|\s+(?P<pver>\d+)\s+\|')
! 221: last_version = last_protocol_version = None
! 222: pdate = { }
! 223:
! 224: with open('NEWS.md', 'r', encoding='utf-8') as fh:
! 225: for line in fh:
! 226: if not last_version: # Find the first non-dev|pre version with a release date.
! 227: m = re.search(r'rsync (\d+\.\d+\.\d+) .*\d\d\d\d', line)
! 228: if m:
! 229: last_version = m[1]
! 230: m = rel_re.match(line)
! 231: if m:
! 232: if m['pdate']:
! 233: pdate[m['ver']] = m['pdate']
! 234: if m['ver'] == last_version:
! 235: last_protocol_version = m['pver']
! 236:
! 237: if not last_protocol_version:
! 238: die(f"Unable to determine protocol_version for {last_version}.")
! 239:
! 240: return last_version, last_protocol_version, pdate
! 241:
! 242:
! 243: def get_protocol_versions():
! 244: protocol_version = subprotocol_version = None
! 245:
! 246: with open('rsync.h', 'r', encoding='utf-8') as fh:
! 247: for line in fh:
! 248: m = re.match(r'^#define\s+PROTOCOL_VERSION\s+(\d+)', line)
! 249: if m:
! 250: protocol_version = m[1]
! 251: continue
! 252: m = re.match(r'^#define\s+SUBPROTOCOL_VERSION\s+(\d+)', line)
! 253: if m:
! 254: subprotocol_version = m[1]
! 255: break
! 256:
! 257: if not protocol_version:
! 258: die("Unable to determine the current PROTOCOL_VERSION.")
! 259:
! 260: if not subprotocol_version:
! 261: die("Unable to determine the current SUBPROTOCOL_VERSION.")
! 262:
! 263: return protocol_version, subprotocol_version
! 264:
! 265: # vim: sw=4 et
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>