wip source download
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 3 Apr 2017 13:28:51 +0000 (14:28 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 3 Apr 2017 13:28:51 +0000 (14:28 +0100)
hippotatlib/ownsource.py [new file with mode: 0644]
t.py [new file with mode: 0755]

diff --git a/hippotatlib/ownsource.py b/hippotatlib/ownsource.py
new file mode 100644 (file)
index 0000000..be7bdcd
--- /dev/null
@@ -0,0 +1,144 @@
+# 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()
diff --git a/t.py b/t.py
new file mode 100755 (executable)
index 0000000..8c6e152
--- /dev/null
+++ b/t.py
@@ -0,0 +1,6 @@
+#!/usr/bin/python3
+
+from hippotatlib.ownsource import SourceShipmentPreparer
+
+p = SourceShipmentPreparer('tmp')
+p.generate()