]> git.donarmstrong.com Git - lilypond.git/blob - scripts/update-lily.py
* Another grand 2003 update.
[lilypond.git] / scripts / update-lily.py
1 #!@PYTHON@
2 # update-lily.py -- lilypond autobuilder
3
4 # source file of the GNU LilyPond music typesetter
5 #
6 # download and rebuild latest lilypond or from specified url
7 #
8 # To show latest version do:
9 #
10 #     update-lily --command='echo "Latest is: %n-%v"'
11 #
12
13
14 '''
15 TODO:
16
17     * use urllib iso ftplib
18
19     * more flexible build/ftp/patches/releases paths
20     
21 '''
22
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__
35
36 package_name = 'lilypond'
37 program_name = 'build-lily'
38 program_version = '@TOPLEVEL_VERSION@'
39
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
46
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'
50
51
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 = ''
56
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
66
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
75
76
77 help_summary = _ ("Fetch and rebuild from latest source package")
78
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") \
87          ),
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")),
96         ]
97
98
99 ################################################################
100 # lilylib.py -- options and stuff
101
102 # source file of the GNU LilyPond music typesetter
103
104 import os
105
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
114
115 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
116         program_version = '1.7.0'
117
118 def identify ():
119         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
120
121 def warranty ():
122         identify ()
123         sys.stdout.write ('\n')
124         sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001--2003'))
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')
133
134 def progress (s):
135         errorport.write (s + '\n')
136
137 def warning (s):
138         progress (_ ("warning: ") + s)
139                 
140 def error (s):
141
142
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.
146
147         RETURN VALUE
148
149         None
150         
151         '''
152         
153         progress (_ ("error: ") + s)
154         raise _ ("Exiting ... ")
155
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)
171
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]
177
178         sep = ' '
179         if o[1] and o[2]:
180                 sep = ','
181                 
182         long = ''
183         if o[2]:
184                 long= '--%s' % o[2]
185
186         arg = ''
187         if o[0]:
188                 if o[2]:
189                         arg = '='
190                 arg = arg + o[0]
191         return '  ' + sh + sep + long + arg
192
193
194 def options_help_str (opts):
195         '''Convert a list of options into a neatly formatted string'''
196         w = 0
197         strs =[]
198         helps = []
199
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)
205
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
210
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)
223         
224 def setup_temp ():
225         """
226         Create a temporary directory, and return its name. 
227         """
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
235
236         return temp_dir
237
238
239 def system (cmd, ignore_error = 0):
240         """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
241
242         RETURN VALUE
243
244         Exit status of CMD
245         """
246         
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)
257
258         return st
259
260
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)
266
267
268 def strip_extension (f, ext):
269         (p, e) = os.path.splitext (f)
270         if e == ext:
271                 e = ''
272         return p + e
273
274
275
276 notify = 0
277
278 build_command = '''
279 set -x
280 cd %b &&
281 [ -d %n-%v ] && exit 1 || true;
282 mkdir -p %n-%v
283 (
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
292 '''
293
294 ### URL lib
295
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
302
303 list_ = list_file
304
305 def list_ftp (user, passwd, host, dir, file):
306         if user == 'None':
307                 user = 'anonymous'
308         if passwd == 'None':
309                 passwd = program_name
310
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
321         
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))
329         
330 def list_url (url):
331         s = "list_%s ('%s', '%s', '%s', '%s', '%s')" % split_url (url)
332         return eval (s)
333
334 def copy_file (user, passwd, host, dir, file):
335         os.system ('cp %s/%s .' % (dir, file))
336
337 copy_ = copy_file
338
339 def copy_ftp (user, passwd, host, dir, file):
340         if user == 'None':
341                 user = 'anonymous'
342         if passwd == 'None':
343                 passwd = program_name
344
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
365         
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)
370
371 ### End URL lib
372
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
379
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)
390
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
403
404         return tuple (l)
405
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
432                 
433         return tuple (l)
434
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)))
438
439 def join_package (t):
440         return t[0] + '-' + version_tuple_to_str (t[1])
441
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]))
446         
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])
452
453 def build (p):
454         tar_ball = p + '.tar.gz'
455         (tar_name, tar_version) = split_package (tar_ball)
456         
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,
464                 }
465
466         c = build_command
467         for i in expand.keys ():
468                 c = re.sub (i, expand[i], c)
469         return system (c, 1)
470
471
472
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)
483
484 for opt in options:     
485         o = opt[0]
486         a = opt[1]
487
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)
513                 
514 if 1:
515         latest = find_latest (url)
516
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)
522
523         if not symlink_name:
524                 symlink_name = string.split (url, '/')[-2]
525                 
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)
537
538         if not os.path.isdir (build_root):
539                 build_root = temp_dir
540                 
541         if not os.path.isdir (release_dir):
542                 release_dir = temp_dir
543                 setup_temp ()
544                 
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, '.')
550
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
556
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)
566                 
567         os.chdir (original_dir)
568         if release_dir != temp_dir and os.path.isdir (temp_dir):
569                 cleanup_temp ()
570         sys.exit (0)
571