wip ownsource, now getting there
[hippotat] / hippotatlib / ownsource.py
CommitLineData
4460af45
IJ
1# Automatic source code provision (AGPL compliance)
2
b635cd93 3import os
4460af45
IJ
4import sys
5import fnmatch
b635cd93
IJ
6import stat
7import subprocess
4460af45
IJ
8
9class SourceShipmentPreparer():
10 def __init__(s, destdir):
11 # caller may modify, and should read after calling generate()
12 s.output_name = 'srcbomb.tar.gz'
13 # defaults, caller can modify after creation
14 s.src_filter = s.src_filter_glob
15 s.src_filter_globs = ['/usr/local/*', '!/usr*', '!/etc/*']
16 s.src_likeparent = s.src_likeparent_git
17 s.cwd = os.getcwd()
b635cd93
IJ
18 s.find_rune_base = "find -type f -perm -004 \! -path '*/tmp/*'"
19 s.excludes = ['*~', '*.bak', '*.tmp', '#*#',
20 '[0-9][0-9][0-9][0-9]-src.cpio']
4460af45 21 s.rune_shell = ['/bin/bash', '-ec']
b635cd93
IJ
22 s.show_pathnames = True
23 s.rune_cpio = r'''
4460af45
IJ
24 set -o pipefail
25 (
26 %s
27 # ^ by default, is find ... -print0
28 ) | (
29 cpio -Hustar -o --quiet -0 -R 1000:1000 || \
30 cpio -Hustar -o --quiet -0
31 )
32 '''
33 s.rune_portmanteau = r'''
34 outfile=$1; shift
35 rm -f "$outfile"
36 GZIP=-9 tar zcf "$outfile" "$@"'
37 '''
38 s.manifest_name='0000-MANIFEST.txt'
39 # private
40 s._destdir = destdir
b635cd93 41 s._outcounter = 0
4460af45
IJ
42 s._manifest = []
43
44 def src_filter_glob(s, src): # default s.src_filter
45 for pat in s.src_filter_globs:
46 negate = pat.startswith('!')
47 if negate: pat = pat[1:]
48 if fnmatch.fnmatch(src, pat):
49 return not negate
50 return negate
51
52 def src_likeparent_git(s, src):
53 try:
b635cd93 54 os.stat(os.path.join(src, '.git/.'))
4460af45
IJ
55 except FileNotFoundError:
56 return False
57 else:
58 return True
59
60 def src_parentfinder(s, src, infol): # callers may monkey-patch away
61 for deref in (False,True):
62 xinfo = []
63
64 search = src
65 if deref:
66 search = os.path.realpath(search)
67
68 def ascend():
b635cd93 69 nonlocal search
4460af45
IJ
70 xinfo.append(os.path.basename(search))
71 search = os.path.dirname(search)
72
73 try:
b635cd93 74 stab = os.lstat(search)
4460af45
IJ
75 except FileNotFoundError:
76 return
77 if stat.S_ISREG(stab.st_mode):
78 ascend()
79
80 while not os.path.ismount(search):
81 if s.src_likeparent(search):
82 xinfo.reverse()
b635cd93 83 if len(xinfo): infol.append('want=' + os.path.join(*xinfo))
4460af45
IJ
84 return search
85
86 ascend()
87
88 # no .git found anywhere
b635cd93 89 return src
4460af45 90
b635cd93 91 def src_prenormaliser(s, d, infol): # callers may monkey-patch away
4460af45
IJ
92 return os.path.join(s.cwd, os.path.abspath(d))
93
94 def src_find_rune(s, d):
b635cd93
IJ
95 script = s.find_rune_base
96 for excl in s.excludes + [s.output_name, s.manifest_name]:
4460af45
IJ
97 assert("'" not in excl)
98 script += r" \! -name '%s'" % excl
99 script += ' -print0'
b635cd93 100 return script
4460af45
IJ
101
102 def new_output_name(s, nametail, infol):
b635cd93
IJ
103 s._outcounter += 1
104 name = '%04d-%s' % (s._outcounter, nametail)
105 s._manifest.append((name, ' '.join(infol)))
4460af45
IJ
106 return name
107
b635cd93
IJ
108 def new_output_fh(s, nametail, infol):
109 name = s.new_output_name(nametail, infol)
110 return s.open_output_fh(name, 'wb')
111
4460af45
IJ
112 def open_output_fh(s, name, mode):
113 return open(os.path.join(s._destdir, name), mode)
114
b635cd93
IJ
115 def mk_from_dir(s, d, infol):
116 if s.show_pathnames: infol.append(d)
117 find_rune = s.src_find_rune(d)
4460af45 118 total_rune = s.rune_cpio % find_rune
b635cd93 119 fh = s.new_output_fh('src.cpio', infol)
4460af45 120 subprocess.run(s.rune_shell + [total_rune],
b635cd93 121 cwd=d,
4460af45
IJ
122 stdin=subprocess.DEVNULL,
123 stdout=fh,
b635cd93
IJ
124 restore_signals=True,
125 check=True)
4460af45
IJ
126 fh.close()
127
128 def mk_from_src(s, d, infol):
129 d = s.src_prenormaliser(d, infol)
130 if not s.src_filter(d): return
131 d = s.src_parentfinder(d, infol)
132 s.mk_from_dir(d, infol)
133
134 def mk_from_srcs(s, dirs=sys.path):
135 s.mk_from_src(sys.argv[0], ['argv[0]'])
136 for d in sys.path:
137 s.mk_from_src(d, ['sys.path'])
138
b635cd93 139 def mk_portmanteau(s):
4460af45
IJ
140 cmdl = s.rune_shell + [ s.rune_portmanteau, 'x',
141 s.output_name, s.manifest_name ]
b635cd93 142 mfh = s.open_output_fh(s.manifest_name,'w')
4460af45
IJ
143 for (name, info) in s._manifest:
144 cmdl.append(name)
b635cd93 145 print('%s\t%s' % (name,info), file=mfh)
4460af45
IJ
146 mfh.close()
147 subprocess.run(s.rune_shell + cmdl,
b635cd93 148 cwd=s._destdir,
4460af45
IJ
149 stdin=subprocess.DEVNULL,
150 stdout=sys.stderr,
b635cd93
IJ
151 restore_signals=True,
152 check=True)
4460af45
IJ
153
154 def generate(s):
b635cd93 155 s.mk_from_srcs()
4460af45 156 s.mk_portmanteau()