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