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>