Commit | Line | Data |
---|---|---|
4460af45 IJ |
1 | # Automatic source code provision (AGPL compliance) |
2 | ||
b635cd93 | 3 | import os |
4460af45 IJ |
4 | import sys |
5 | import fnmatch | |
b635cd93 IJ |
6 | import stat |
7 | import subprocess | |
4460af45 IJ |
8 | |
9 | class 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" | |
2371516a | 36 | GZIP=-9 tar zcf "$outfile" "$@" |
4460af45 IJ |
37 | ''' |
38 | s.manifest_name='0000-MANIFEST.txt' | |
39 | # private | |
40 | s._destdir = destdir | |
b635cd93 | 41 | s._outcounter = 0 |
4460af45 | 42 | s._manifest = [] |
2371516a | 43 | s._dirmap = { } |
4460af45 IJ |
44 | |
45 | def src_filter_glob(s, src): # default s.src_filter | |
46 | for pat in s.src_filter_globs: | |
47 | negate = pat.startswith('!') | |
48 | if negate: pat = pat[1:] | |
49 | if fnmatch.fnmatch(src, pat): | |
50 | return not negate | |
51 | return negate | |
52 | ||
53 | def src_likeparent_git(s, src): | |
54 | try: | |
b635cd93 | 55 | os.stat(os.path.join(src, '.git/.')) |
4460af45 IJ |
56 | except FileNotFoundError: |
57 | return False | |
58 | else: | |
59 | return True | |
60 | ||
61 | def src_parentfinder(s, src, infol): # callers may monkey-patch away | |
62 | for deref in (False,True): | |
63 | xinfo = [] | |
64 | ||
65 | search = src | |
66 | if deref: | |
67 | search = os.path.realpath(search) | |
68 | ||
69 | def ascend(): | |
b635cd93 | 70 | nonlocal search |
4460af45 IJ |
71 | xinfo.append(os.path.basename(search)) |
72 | search = os.path.dirname(search) | |
73 | ||
74 | try: | |
b635cd93 | 75 | stab = os.lstat(search) |
4460af45 IJ |
76 | except FileNotFoundError: |
77 | return | |
78 | if stat.S_ISREG(stab.st_mode): | |
79 | ascend() | |
80 | ||
81 | while not os.path.ismount(search): | |
82 | if s.src_likeparent(search): | |
83 | xinfo.reverse() | |
b635cd93 | 84 | if len(xinfo): infol.append('want=' + os.path.join(*xinfo)) |
4460af45 IJ |
85 | return search |
86 | ||
87 | ascend() | |
88 | ||
89 | # no .git found anywhere | |
b635cd93 | 90 | return src |
4460af45 | 91 | |
b635cd93 | 92 | def src_prenormaliser(s, d, infol): # callers may monkey-patch away |
4460af45 IJ |
93 | return os.path.join(s.cwd, os.path.abspath(d)) |
94 | ||
95 | def src_find_rune(s, d): | |
b635cd93 IJ |
96 | script = s.find_rune_base |
97 | for excl in s.excludes + [s.output_name, s.manifest_name]: | |
4460af45 IJ |
98 | assert("'" not in excl) |
99 | script += r" \! -name '%s'" % excl | |
100 | script += ' -print0' | |
b635cd93 | 101 | return script |
4460af45 | 102 | |
2371516a IJ |
103 | def manifest_append(s, name, infol): |
104 | s._manifest.append((name, ' '.join(infol))) | |
105 | ||
4460af45 | 106 | def new_output_name(s, nametail, infol): |
b635cd93 IJ |
107 | s._outcounter += 1 |
108 | name = '%04d-%s' % (s._outcounter, nametail) | |
2371516a | 109 | s.manifest_append(name, infol) |
4460af45 IJ |
110 | return name |
111 | ||
112 | def open_output_fh(s, name, mode): | |
113 | return open(os.path.join(s._destdir, name), mode) | |
114 | ||
b635cd93 | 115 | def mk_from_dir(s, d, infol): |
2371516a IJ |
116 | try: name = s._dirmap[d] |
117 | except KeyError: pass | |
118 | else: | |
119 | s.manifest_append(name, infol) | |
120 | return | |
121 | ||
b635cd93 IJ |
122 | if s.show_pathnames: infol.append(d) |
123 | find_rune = s.src_find_rune(d) | |
4460af45 | 124 | total_rune = s.rune_cpio % find_rune |
2371516a IJ |
125 | |
126 | name = s.new_output_name('src.cpio', infol) | |
127 | s._dirmap[d] = name | |
128 | fh = s.open_output_fh(name, 'wb') | |
129 | ||
4460af45 | 130 | subprocess.run(s.rune_shell + [total_rune], |
b635cd93 | 131 | cwd=d, |
4460af45 IJ |
132 | stdin=subprocess.DEVNULL, |
133 | stdout=fh, | |
b635cd93 IJ |
134 | restore_signals=True, |
135 | check=True) | |
4460af45 IJ |
136 | fh.close() |
137 | ||
138 | def mk_from_src(s, d, infol): | |
139 | d = s.src_prenormaliser(d, infol) | |
140 | if not s.src_filter(d): return | |
141 | d = s.src_parentfinder(d, infol) | |
142 | s.mk_from_dir(d, infol) | |
143 | ||
144 | def mk_from_srcs(s, dirs=sys.path): | |
145 | s.mk_from_src(sys.argv[0], ['argv[0]']) | |
146 | for d in sys.path: | |
147 | s.mk_from_src(d, ['sys.path']) | |
148 | ||
b635cd93 | 149 | def mk_portmanteau(s): |
4460af45 IJ |
150 | cmdl = s.rune_shell + [ s.rune_portmanteau, 'x', |
151 | s.output_name, s.manifest_name ] | |
b635cd93 | 152 | mfh = s.open_output_fh(s.manifest_name,'w') |
4460af45 IJ |
153 | for (name, info) in s._manifest: |
154 | cmdl.append(name) | |
b635cd93 | 155 | print('%s\t%s' % (name,info), file=mfh) |
4460af45 | 156 | mfh.close() |
2371516a | 157 | subprocess.run(cmdl, |
b635cd93 | 158 | cwd=s._destdir, |
4460af45 IJ |
159 | stdin=subprocess.DEVNULL, |
160 | stdout=sys.stderr, | |
b635cd93 IJ |
161 | restore_signals=True, |
162 | check=True) | |
4460af45 IJ |
163 | |
164 | def generate(s): | |
b635cd93 | 165 | s.mk_from_srcs() |
4460af45 | 166 | s.mk_portmanteau() |