2 # update-lily.py -- lilypond autobuilder
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"'
17 * use urllib iso ftplib
19 * more flexible build/ftp/patches/releases paths
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
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'
57 localedir
= '@localedir@'
60 gettext
.bindtextdomain ('lilypond', localedir
)
61 gettext
.textdomain ('lilypond')
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.
72 resource
.setrlimit (resource
.RLIMIT_STACK
, (-1, -1))
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
108 gettext
.bindtextdomain ('lilypond', localedir
)
109 gettext
.textdomain ('lilypond')
115 if program_version
== '@' + 'TOPLEVEL_VERSION' + '@':
116 program_version
= '1.7.0'
119 sys
.stdout
.write ('%s (GNU LilyPond) %s\n' % (program_name
, program_version
))
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
132 sys
.stdout
.write ('\n')
135 errorport
.write (s
+ '\n')
138 progress (_ ("warning: ") + 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.
153 progress (_ ("error: ") + s
)
154 raise _ ("Exiting ... ")
156 def getopt_args (opts
):
157 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
172 def option_help_str (o
):
173 '''Transform one option description (4-tuple ) into neatly formatted string'''
191 return ' ' + sh
+ sep
+ long + arg
194 def options_help_str (opts
):
195 '''Convert a list of options into a neatly formatted string'''
201 s
= option_help_str (o
)
202 strs
.append ((s
, o
[3]))
208 str = str + '%s%s%s\n' % (s
[0], ' ' * (w
- len(s
[0]) + 3), s
[1])
212 ls
= [(_ ("Usage: %s [OPTION]... FILE") % program_name
),
218 (options_help_str (option_definitions
)),
220 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
222 map (sys
.stdout
.write
, ls
)
226 Create a temporary directory, and return its name.
229 if not keep_temp_dir_p
:
230 temp_dir
= tempfile
.mktemp (program_name
)
232 os
.mkdir (temp_dir
, 0777)
239 def system (cmd
, ignore_error
= 0):
240 """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
248 progress (_ ("Invoking `%s\'") % cmd
)
251 name
= re
.match ('[ \t]*([^ \t]*)', cmd
).group (1)
252 msg
= name
+ ': ' + _ ("command exited with value %d") % st
254 warning (msg
+ ' ' + _ ("(ignored)") + ' ')
262 if not keep_temp_dir_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
)
281 [ -d %n-%v ] && exit 1 || true;
286 ln -s %n-%v building &&
288 ./configure --prefix=$(pwd) && make all web
289 ) >> %n-%v/log.txt 2>&1 &&
296 def list_file (user
, passwd
, host
, dir, file):
298 for i
in os
.listdir (dir):
299 if fnmatch
.fnmatch (i
, file):
305 def list_ftp (user
, passwd
, host
, dir, file):
309 passwd
= program_name
311 ftp
= ftplib
.FTP (host
)
312 ftp
.login (user
, passwd
)
315 list = ftp
.nlst (file)
323 m
= re
.match ('([^:/]*)(:)?(/*([^:]*):)?(/*([^@]*)@)?(//([^/]*))?(.*)/(.*)',
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))
331 s
= "list_%s ('%s', '%s', '%s', '%s', '%s')" % split_url (url
)
334 def copy_file (user
, passwd
, host
, dir, file):
335 os
.system ('cp %s/%s .' % (dir, file))
339 def copy_ftp (user
, passwd
, host
, dir, file):
343 passwd
= program_name
345 ftp
= ftplib
.FTP (host
)
346 ftp
.login (user
, passwd
)
348 t
= tempfile
.mktemp (program_name
)
351 ftp
.retrbinary ('RETR %s/%s' % (dir, file),
352 lambda x
, f
=f
: f
.write (x
))
354 # huh? Invalid cross-device link
355 # os.rename (t, file)
356 system ('mv %s %s' % (t
, file))
366 def copy_url (url
, dir):
368 s
= "copy_%s ('%s', '%s', '%s', '%s', '%s')" % split_url (url
)
373 def version_tuple_to_str (t
):
375 my
= '.%s%d' % (t
[3], t
[4])
378 return ('%d.%d.%d' % t
[0:3]) + my
380 def version_str_to_tuple (s
):
381 t
= string
.split (s
, '.')
384 my_number
= string
.atoi (t
[3][-1])
388 return (string
.atoi (t
[0]), string
.atoi (t
[1]), string
.atoi (t
[2]),
391 def next_version (t
):
392 #print 'tup: %s' % `t`
395 # if l[3]: # 1.0.0.my1 -> 1.0.0.my1
396 if l
[4]: # 1.0.0.my1 -> 1.0.1
406 def prev_version (t
):
407 #print 'tup: %s' % `t`
410 if l
[4]: # 1.0.0.my1 -> 1.0.0
415 # if l[3]: # 1.0.0.my1 -> 1.0.0.my0
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])
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
))
451 return join_package (list[-1])
454 tar_ball
= p
+ '.tar.gz'
455 (tar_name
, tar_version
) = split_package (tar_ball
)
461 '%v' : version_tuple_to_str (tar_version
),
467 for i
in expand
.keys ():
468 c
= re
.sub (i
, expand
[i
], c
)
473 (sh
, long) = getopt_args (__main__
.option_definitions
)
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')
490 elif o
== '--help' or o
== '-h':
493 elif o
== '--build-root' or o
== '-b':
495 elif o
== '--command' or o
== '-c':
497 elif o
== '--notify' or o
== '-n':
499 elif o
== '--remove-previous' or o
== '-r':
500 remove_previous_p
= 1
501 elif o
== '--url' or o
== '-u':
503 elif o
== '--verbose' or o
== '-V':
505 elif o
== '--version' or o
== '-v':
508 elif o
== '--warranty' or o
== '-w':
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
))
524 symlink_name
= string
.split (url
, '/')[-2]
526 get_base
= url
[:string
.rindex (url
, '/')] + '/'
527 if os
.path
.isdir (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
)
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
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
)
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 ()
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
))
564 system ('(date; uname -a) | mail -s "%s failed" %s' % (program_name
, notify
))
567 os
.chdir (original_dir
)
568 if release_dir
!= temp_dir
and os
.path
.isdir (temp_dir
):