+# Automatic source code provision (AGPL compliance)
+
+import sys
+import fnmatch
+
+class SourceShipmentPreparer():
+ def __init__(s, destdir):
+ # caller may modify, and should read after calling generate()
+ s.output_name = 'srcbomb.tar.gz'
+ # defaults, caller can modify after creation
+ s.src_filter = s.src_filter_glob
+ s.src_filter_globs = ['/usr/local/*', '!/usr*', '!/etc/*']
+ s.src_likeparent = s.src_likeparent_git
+ s.cwd = os.getcwd()
+ s.excludes = ['*~', '*.bak', '*.tmp', '#*#']
+ s.rune_shell = ['/bin/bash', '-ec']
+ s.rune_cpio = r''''
+ set -o pipefail
+ (
+ %s
+ # ^ by default, is find ... -print0
+ ) | (
+ cpio -Hustar -o --quiet -0 -R 1000:1000 || \
+ cpio -Hustar -o --quiet -0
+ )
+ '''
+ s.rune_portmanteau = r'''
+ outfile=$1; shift
+ rm -f "$outfile"
+ GZIP=-9 tar zcf "$outfile" "$@"'
+ '''
+ s.manifest_name='0000-MANIFEST.txt'
+ # private
+ s._destdir = destdir
+ s._outcounter = 1
+ s._manifest = []
+
+ def src_filter_glob(s, src): # default s.src_filter
+ for pat in s.src_filter_globs:
+ negate = pat.startswith('!')
+ if negate: pat = pat[1:]
+ if fnmatch.fnmatch(src, pat):
+ return not negate
+ return negate
+
+ def src_likeparent_git(s, src):
+ try:
+ stat(os.path.join(d, '.git/.'))
+ except FileNotFoundError:
+ return False
+ else:
+ return True
+
+ def src_parentfinder(s, src, infol): # callers may monkey-patch away
+ for deref in (False,True):
+ xinfo = []
+
+ search = src
+ if deref:
+ search = os.path.realpath(search)
+
+ def ascend():
+ xinfo.append(os.path.basename(search))
+ search = os.path.dirname(search)
+
+ try:
+ stab = lstat(search)
+ except FileNotFoundError:
+ return
+ if stat.S_ISREG(stab.st_mode):
+ ascend()
+
+ while not os.path.ismount(search):
+ if s.src_likeparent(search):
+ xinfo.reverse()
+ infol.append(os.path.join(*xinfo))
+ return search
+
+ ascend()
+
+ # no .git found anywhere
+ return d
+
+ def src_prenormaliser(s, d): # callers may monkey-patch away
+ return os.path.join(s.cwd, os.path.abspath(d))
+
+ def src_find_rune(s, d):
+ script = 'find -type f -perm +004'
+ for excl in s.excludes:
+ assert("'" not in excl)
+ script += r" \! -name '%s'" % excl
+ script += ' -print0'
+
+ def new_output_name(s, nametail, infol):
+ name = '%04d-%s' % (s._outcounter++, nametail)
+ s._manifest.append((name, infol.join(' '))
+ return name
+
+ def open_output_fh(s, name, mode):
+ return open(os.path.join(s._destdir, name), mode)
+
+ def new_output_fh(s, nametail, infol):
+ name = new_output_name(s, nametail, infol)
+ return open_output_fh(name, 'wb')
+
+ def mk_from_dir(s, d):
+ find_rune = s.src_find_rune(s, d)
+ total_rune = s.rune_cpio % find_rune
+ fh = new_output_fh('src.cpio')
+ subprocess.run(s.rune_shell + [total_rune],
+ cwd=s._destdir,
+ stdin=subprocess.DEVNULL,
+ stdout=fh,
+ restore_signals=True)
+ fh.close()
+
+ def mk_from_src(s, d, infol):
+ d = s.src_prenormaliser(d, infol)
+ if not s.src_filter(d): return
+ d = s.src_parentfinder(d, infol)
+ s.mk_from_dir(d, infol)
+
+ def mk_from_srcs(s, dirs=sys.path):
+ s.mk_from_src(sys.argv[0], ['argv[0]'])
+ for d in sys.path:
+ s.mk_from_src(d, ['sys.path'])
+
+ def mk_portmanteau(s):]
+ cmdl = s.rune_shell + [ s.rune_portmanteau, 'x',
+ s.output_name, s.manifest_name ]
+ mfh = open_output_fh(s.manifest_name,'w')
+ for (name, info) in s._manifest:
+ cmdl.append(name)
+ print('%s\t%s\n' % (name,info), file=mfh)
+ mfh.close()
+ subprocess.run(s.rune_shell + cmdl,
+ cmd=s._destdir,
+ stdin=subprocess.DEVNULL,
+ stdout=sys.stderr,
+ restore_signals=True)
+
+ def generate(s):
+ s.mk_from_srcdirs()
+ s.mk_portmanteau()