agpl.py: Exclude the root directory from listers.
[chopwood] / agpl.py
CommitLineData
a2916c06
MW
1### -*-python-*-
2###
3### GNU Affero General Public License compliance
4###
5### (c) 2013 Mark Wooding
6###
7
8###----- Licensing notice ---------------------------------------------------
9###
10### This file is part of Chopwood: a password-changing service.
11###
12### Chopwood is free software; you can redistribute it and/or modify
13### it under the terms of the GNU Affero General Public License as
14### published by the Free Software Foundation; either version 3 of the
15### License, or (at your option) any later version.
16###
17### Chopwood is distributed in the hope that it will be useful,
18### but WITHOUT ANY WARRANTY; without even the implied warranty of
19### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20### GNU Affero General Public License for more details.
21###
22### You should have received a copy of the GNU Affero General Public
23### License along with Chopwood; if not, see
24### <http://www.gnu.org/licenses/>.
25
26import contextlib as CTX
09ba568f 27import grp as GR
a2916c06 28import os as OS
09ba568f 29import pwd as PW
a2916c06
MW
30import shlex as SL
31import shutil as SH
32import subprocess as SUB
33import sys as SYS
34import tarfile as TAR
35import tempfile as TF
09ba568f
MW
36import time as T
37
38from cStringIO import StringIO
a2916c06
MW
39
40from auto import PACKAGE, VERSION
41import util as U
42
43@CTX.contextmanager
44def tempdir():
45 d = TF.mkdtemp()
46 try: yield d
47 finally: SH.rmtree(d, ignore_errors = True)
48
49def dirs_to_dump():
50 dirs = set()
51 for m in SYS.modules.itervalues():
52 try: f = m.__file__
53 except AttributeError: continue
54 d = OS.path.realpath(OS.path.dirname(f))
55 if d.startswith('/usr/') and not d.startswith('/usr/local/'): continue
56 dirs.add(d)
57 dirs = sorted(dirs)
58 last = '!'
59 dump = []
60 for d in dirs:
61 if d.startswith(last): continue
62 dump.append(d)
63 last = d
64 return dump
65
66def exists_subdir(subdir):
67 return lambda dir: OS.path.isdir(OS.path.join(dir, subdir))
68
69def filez(cmd):
70 def _(dir):
71 kid = SUB.Popen(SL.split(cmd), stdout = SUB.PIPE, cwd = dir)
72 left = ''
73 while True:
74 buf = kid.stdout.read(16384)
75 if not buf: break
76 buf = left + buf
77 i = 0
78 while True:
79 z = buf.find('\0', i)
80 if z < 0: break
81 f = buf[i:z]
4338f962
MW
82 i = z + 1
83 if f == '.': continue
a2916c06
MW
84 if f.startswith('./'): f = f[2:]
85 yield f
a2916c06
MW
86 left = buf[i:]
87 if left:
88 raise U.ExpectedError, \
89 (500, "trailing junk from `%s' in `%s'" % (cmd, dir))
90 return _
91
92DUMPERS = [
93 (exists_subdir('.git'), [filez('git ls-files -coz --exclude-standard'),
94 filez('find .git -print0')]),
95 (lambda d: True, [filez('find . ( ! -perm +004 -prune ) -o -print0')])]
96
4338f962 97def dump_dir(name, dir, dirmap, tf, root):
a2916c06
MW
98 for test, listers in DUMPERS:
99 if test(dir): break
100 else:
101 raise U.ExpectedError, (500, "no dumper for `%s'" % dir)
4338f962 102 tf.add(dir, OS.path.join(root, name), recursive = False)
a2916c06
MW
103 for lister in listers:
104 base = OS.path.basename(dir)
105 for file in lister(dir):
106 tf.add(OS.path.join(dir, file), OS.path.join(root, base, file),
107 recursive = False)
108
109def source(out):
110 if SYS.version_info >= (2, 6):
111 tf = TAR.open(fileobj = out, mode = 'w|gz', format = TAR.USTAR_FORMAT)
112 else:
113 tf = TAR.open(fileobj = out, mode = 'w|gz')
114 tf.posix = True
09ba568f
MW
115 root = '%s-%s' % (PACKAGE, VERSION)
116 seen = set()
117 dirmap = []
118 festout = StringIO()
119 for dir in dirs_to_dump():
120 dir = dir.rstrip('/')
121 base = OS.path.basename(dir)
122 if base not in seen:
123 name = base
124 else:
125 for i in I.count():
126 name = '%s.%d' % (base, i)
127 if name not in seen: break
128 dirmap.append((dir + '/', name))
129 festout.write('%s = %s\n' % (name, dir))
130 fest = festout.getvalue()
131 ti = TAR.TarInfo(OS.path.join(root, 'MANIFEST'))
132 ti.size = len(fest)
133 ti.mtime = T.time()
134 ti.mode = 0664
135 ti.type = TAR.REGTYPE
136 uid = OS.getuid(); ti.uid, ti.uname = uid, PW.getpwuid(uid).pw_name
137 gid = OS.getgid(); ti.gid, ti.gname = gid, GR.getgrgid(gid).gr_name
138 tf.addfile(ti, fileobj = StringIO(fest))
139 for dir, name in dirmap:
4338f962 140 dump_dir(name, dir, dirmap, tf, root)
a2916c06
MW
141 tf.close()
142
143###----- That's all, folks --------------------------------------------------