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