]> git.donarmstrong.com Git - lilypond.git/blob - scripts/update-lily.py
3ff0442234b070a61a32223c18b5e67e6e8068ec
[lilypond.git] / scripts / update-lily.py
1 #!@PYTHON@
2 #
3 # update-lily.py -- lilypond autobuilder
4
5 # source file of the GNU LilyPond music typesetter
6 #
7 # download and rebuild latest lilypond or from specified url
8 #
9
10
11 '''
12 TODO:
13
14     * use urllib iso ftplib
15
16     * more flexible build/ftp/patches/releases paths
17
18     
19     show only: --command='echo "latest is: %n-%v"'
20 '''
21
22 import ftplib
23 import fnmatch
24 import getopt
25 import re
26 import operator
27 import os
28 import tempfile
29 import stat
30 import string
31 import sys
32 import __main__
33
34 package_name = 'lilypond'
35 program_name = 'build-lily'
36 program_version = '@TOPLEVEL_VERSION@'
37
38 original_dir = os.getcwd ()
39 temp_dir = os.path.join (original_dir,  '%s.dir' % program_name)
40 errorport = sys.stderr
41 keep_temp_dir_p = 0
42 verbose_p = 0
43 remove_previous_p = 0
44
45 url = 'file:/home/ftp/pub/gnu/LilyPond/development/lilypond-*.tar.gz'
46 url = 'ftp://appel.lilypond.org/pub/gnu/LilyPond/development/lilypond-*.tar.gz'
47 url = 'ftp://ftp.cs.uu.nl/pub/GNU/LilyPond/development/lilypond-*.tar.gz'
48
49
50 build_root = os.path.join (os.environ ['HOME'], 'usr', 'src')
51 release_dir = build_root + '/releases'
52 patch_dir = build_root + '/patches'
53
54
55
56 try:
57         import gettext
58         gettext.bindtextdomain ('lilypond', '@localedir@')
59         gettext.textdomain ('lilypond')
60         _ = gettext.gettext
61 except:
62         def _ (s):
63                 return s
64
65 # Attempt to fix problems with limited stack size set by Python!
66 # Sets unlimited stack size. Note that the resource module only
67 # is available on UNIX.
68 try:
69        import resource
70        resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
71 except:
72        pass
73
74
75 help_summary = _ ("Fetch and rebuild from latest source package")
76
77 option_definitions = [
78         ('DIR', 'b', 'build-root', _ ("unpack and build in DIR [%s]") % build_root),
79         ('COMMAND', 'c', 'command', _ ("execute COMMAND, subtitute:") \
80          + '\n                            ' + _ ("%b: build root") \
81          + '\n                            ' + _ ("%n: package name") \
82          + '\n                            ' + _ ("%r: release directory") \
83          + '\n                            ' + _ ("%t: tarball") \
84          + '\n                            ' + _ ("%v: package version") \
85          ),
86         ('', 'h', 'help', _ ("this help")),
87         ('', 'k', 'keep', _ ("keep all output, and name the directory %s") % temp_dir),
88         ('EMAIL', 'n', 'notify', _ ("upon failure notify EMAIL[,EMAIL]")),
89         ('', 'r', 'remove-previous', _ ("remove previous build")),
90         ('', 'V', 'verbose', _ ("verbose")),
91         ('', 'v', 'version', _ ("print version number")),
92         ('URL', 'u', 'url', _ ("fetch and build URL [%s]") % url),
93         ('', 'w', 'warranty', _ ("show warranty and copyright")),
94         ]
95
96
97 ################################################################
98 # lilylib.py -- options and stuff
99
100 # source file of the GNU LilyPond music typesetter
101
102 import os
103
104 try:
105         import gettext
106         gettext.bindtextdomain ('lilypond', localedir)
107         gettext.textdomain ('lilypond')
108         _ = gettext.gettext
109 except:
110         def _ (s):
111                 return s
112
113 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
114         program_version = '1.5.17'
115
116 def identify ():
117         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
118
119 def warranty ():
120         identify ()
121         sys.stdout.write ('\n')
122         sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001'))
123         sys.stdout.write ('\n')
124         sys.stdout.write ('  Han-Wen Nienhuys')
125         sys.stdout.write ('  Jan Nieuwenhuizen')
126         sys.stdout.write ('\n')
127         sys.stdout.write (_ (r'''
128 Distributed under terms of the GNU General Public License. It comes with
129 NO WARRANTY.'''))
130         sys.stdout.write ('\n')
131
132 def progress (s):
133         errorport.write (s + '\n')
134
135 def warning (s):
136         progress (_ ("warning: ") + s)
137                 
138 def error (s):
139
140
141         '''Report the error S.  Exit by raising an exception. Please
142         do not abuse by trying to catch this error. If you do not want
143         a stack trace, write to the output directly.
144
145         RETURN VALUE
146
147         None
148         
149         '''
150         
151         progress (_ ("error: ") + s)
152         raise _ ("Exiting ... ")
153
154 def getopt_args (opts):
155         '''Construct arguments (LONG, SHORT) for getopt from  list of options.'''
156         short = ''
157         long = []
158         for o in opts:
159                 if o[1]:
160                         short = short + o[1]
161                         if o[0]:
162                                 short = short + ':'
163                 if o[2]:
164                         l = o[2]
165                         if o[0]:
166                                 l = l + '='
167                         long.append (l)
168         return (short, long)
169
170 def option_help_str (o):
171         '''Transform one option description (4-tuple ) into neatly formatted string'''
172         sh = '  '       
173         if o[1]:
174                 sh = '-%s' % o[1]
175
176         sep = ' '
177         if o[1] and o[2]:
178                 sep = ','
179                 
180         long = ''
181         if o[2]:
182                 long= '--%s' % o[2]
183
184         arg = ''
185         if o[0]:
186                 if o[2]:
187                         arg = '='
188                 arg = arg + o[0]
189         return '  ' + sh + sep + long + arg
190
191
192 def options_help_str (opts):
193         '''Convert a list of options into a neatly formatted string'''
194         w = 0
195         strs =[]
196         helps = []
197
198         for o in opts:
199                 s = option_help_str (o)
200                 strs.append ((s, o[3]))
201                 if len (s) > w:
202                         w = len (s)
203
204         str = ''
205         for s in strs:
206                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
207         return str
208
209 def help ():
210         ls = [(_ ("Usage: %s [OPTION]... FILE") % program_name),
211                 ('\n\n'),
212                 (help_summary),
213                 ('\n\n'),
214                 (_ ("Options:")),
215                 ('\n'),
216                 (options_help_str (option_definitions)),
217                 ('\n\n'),
218                 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
219                 ('\n')]
220         map (sys.stdout.write, ls)
221         
222 def setup_temp ():
223         """
224         Create a temporary directory, and return its name. 
225         """
226         global temp_dir
227         if not keep_temp_dir_p:
228                 temp_dir = tempfile.mktemp (program_name)
229         try:
230                 os.mkdir (temp_dir, 0777)
231         except OSError:
232                 pass
233
234         return temp_dir
235
236
237 def system (cmd, ignore_error = 0):
238         """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
239
240         RETURN VALUE
241
242         Exit status of CMD
243         """
244         
245         if verbose_p:
246                 progress (_ ("Invoking `%s\'") % cmd)
247         st = os.system (cmd)
248         if st:
249                 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
250                 msg = name + ': ' + _ ("command exited with value %d") % st
251                 if ignore_error:
252                         warning (msg + ' ' + _ ("(ignored)") + ' ')
253                 else:
254                         error (msg)
255
256         return st
257
258
259 def cleanup_temp ():
260         if not keep_temp_dir_p:
261                 if verbose_p:
262                         progress (_ ("Cleaning %s...") % temp_dir)
263                 shutil.rmtree (temp_dir)
264
265
266 def strip_extension (f, ext):
267         (p, e) = os.path.splitext (f)
268         if e == ext:
269                 e = ''
270         return p + e
271
272
273
274 notify = 0
275
276 build_command = '''
277 set -x
278 cd %b &&
279 [ -d %n-%v ] && exit 1 || true;
280 mkdir -p %n-%v
281 (
282 tar xzf %r/%t &&
283 rm -f building &&
284 ln -s %n-%v building &&
285 cd %n-%v &&
286 ./configure --prefix=$HOME/usr && make all web
287 ) >> %n-%v/log.txt 2>&1 &&
288 rm -f %n &&
289 ln -s %n-%v %n
290 '''
291
292
293 def list_file (user, passwd, host, dir, file):
294         match = []
295         for i in os.listdir (dir):
296                 if fnmatch.fnmatch (i, file):
297                         match.append (i)
298         return match
299
300 list_ = list_file
301
302 def list_ftp (user, passwd, host, dir, file):
303         if user == 'None':
304                 user = 'anonymous'
305         if passwd == 'None':
306                 passwd = program_name
307
308         ftp = ftplib.FTP (host)
309         ftp.login (user, passwd)
310         ftp.set_pasv (1)
311         ftp.cwd (dir)
312         list = ftp.nlst (file)
313         try:
314                 ftp.quit ()
315         except:
316                 ftp.close ()
317         return list
318         
319 def split_url (url):
320         m = re.match ('([^:/]*)(:)?(/*([^:]*):)?(/*([^@]*)@)?(//([^/]*))?(.*)/(.*)',
321                       url)
322         if not m:
323                 error ("can't parse url: %s " % url)
324         return (m.group (1), m.group (4), m.group (6), m.group (8),
325                 m.group (9), m.group (10))
326         
327 def list_url (url):
328         s = "list_%s ('%s', '%s', '%s', '%s', '%s')" % split_url (url)
329         return eval (s)
330
331 def version_tuple_to_str (t):
332         if t[3]:
333                 my = '.%s%d' % (t[3], t[4])
334         else:
335                 my = ''
336         return ('%d.%d.%d' % t[0:3]) + my
337
338 def version_str_to_tuple (s):
339         t = string.split (s, '.')
340         if len (t) >= 4:
341                 my_name = t[3][:-1]
342                 my_number = string.atoi (t[3][-1])
343         else:
344                 my_name = None
345                 my_number = None
346         return (string.atoi (t[0]), string.atoi (t[1]), string.atoi (t[2]),
347                 my_name, my_number)
348
349 def split_package (p):
350         m = re.match ('(.*)-([0-9]*.*).tar.gz', p)
351         return (m.group (1), version_str_to_tuple (m.group (2)))
352
353 def join_package (t):
354         return t[0] + '-' + version_tuple_to_str (t[1])
355
356 def copy_file (user, passwd, host, dir, file):
357         os.system ('cp %s/%s .' % (dir, file))
358
359 copy_ = copy_file
360
361 def copy_ftp (user, passwd, host, dir, file):
362         if user == 'None':
363                 user = 'anonymous'
364         if passwd == 'None':
365                 passwd = program_name
366
367         ftp = ftplib.FTP (host)
368         ftp.login (user, passwd)
369         ftp.set_pasv (1)
370         t = tempfile.mktemp (program_name)
371         try:
372                 f = open (t, 'w')
373                 ftp.retrbinary ('RETR %s/%s' % (dir, file),
374                         lambda x, f=f: f.write (x))
375                 f.close ()
376                 # huh? Invalid cross-device link
377                 # os.rename (t, file)
378                 system ('mv %s %s' % (t, file))
379         except:
380                 os.remove (t)
381                 raise 'Foo'
382         try:
383                 ftp.quit ()
384         except:
385                 ftp.close ()
386         return list
387         
388
389
390 def copy_url (url, dir):
391         os.chdir (dir)
392         s = "copy_%s ('%s', '%s', '%s', '%s', '%s')" % split_url (url)
393         eval (s)
394
395
396 def find_latest (url):
397         progress (_ ("Listing `%s'...") % url)
398         list = map (split_package, list_url (url))
399         list.sort ()
400         return join_package (list[-1])
401
402 def build (p):
403         tar_ball = p + '.tar.gz'
404         (tar_name, tar_version) = split_package (tar_ball)
405         
406         expand = {
407                 '%b' : build_root,
408                 '%n' : tar_name,
409                 '%r' : release_dir,
410                 '%v' : version_tuple_to_str (tar_version),
411                 '%t' : tar_ball,
412                 }
413
414         c = build_command
415         for i in expand.keys ():
416                 c = re.sub (i, expand[i], c)
417         return system (c, 1)
418
419
420
421 (sh, long) = getopt_args (__main__.option_definitions)
422 try:
423         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
424 except getopt.error, s:
425         errorport.write ('\n')
426         errorport.write (_ ("error: ") + _ ("getopt says: `%s\'" % s))
427         errorport.write ('\n')
428         errorport.write ('\n')
429         help ()
430         sys.exit (2)
431
432 for opt in options:     
433         o = opt[0]
434         a = opt[1]
435
436         if 0:
437                 pass
438         elif o == '--help' or o == '-h':
439                 help ()
440                 sys.exit (0)
441         elif o == '--buid-root' or o == '-b':
442                 build_root = a
443         elif o == '--command' or o == '-c':
444                 build_command = a
445         elif o == '--notify' or o == '-n':
446                 notify = a
447         elif o == '--remove-previous' or o == '-r':
448                 remove_previous_p = 1
449         elif o == '--url' or o == '-u':
450                 url = a
451         elif o == '--verbose' or o == '-V':
452                 verbose_p = 1
453         elif o == '--version' or o == '-v':
454                 identify ()
455                 sys.exit (0)
456         elif o == '--warranty' or o == '-w':
457                 warranty ()
458                 sys.exit (0)
459                 
460 if 1:
461         latest = find_latest (url)
462
463         # if os.path.isdir ('%s/%s' % (build_root, latest)):
464         if os.path.exists ('%s/%s/index.html' % (build_root, latest)):
465                 progress (_ ("latest is: %s") % latest)
466                 progress (_ ("relax, %s is up to date" % package_name))
467                 sys.exit (0)
468
469         get_base = url[:string.rindex (url, '/')] + '/'
470         if os.path.isdir (patch_dir):
471                 os.chdir (patch_dir)
472                 if not os.path.isfile (latest + '.diff.gz'):
473                         get = get_base + latest + '.diff.gz'
474                         progress (_ ("Fetching `%s'...") % get)
475                         copy_url (get, '.')
476
477         if not os.path.isdir (build_root):
478                 build_root = temp_dir
479                 
480         if not os.path.isdir (release_dir):
481                 release_dir = temp_dir
482                 setup_temp ()
483                 
484         os.chdir (release_dir)
485         if not os.path.isfile (latest + '.tar.gz'):
486                 get = get_base + latest + '.tar.gz'
487                 progress (_ ("Fetching `%s'...") % get)
488                 copy_url (get, '.')
489
490         if os.path.isdir (os.path.join (build_root, package_name)):
491                 os.chdir (os.path.join (build_root, package_name))
492                 previous = os.getcwd ()
493         else:
494                 previous = 0
495
496         progress (_ ("Building `%s'...") % latest)
497         os.chdir (build_root)
498         if not build (latest):
499                 if previous and remove_previous_p:
500                         system ('rm -rf %s' % os.path.join (build_root, previous))
501         else:
502                 if notify:
503                         system ('(date; uname -a) | mail -s "%s failed" %s' % (program_name, notify))
504                 sys.exit (1)
505                 
506         os.chdir (original_dir)
507         if release_dir != temp_dir:
508                 cleanup_temp ()
509         sys.exit (0)
510