diff -r 3edd2f19bce2 -r e0424e96e3fd svgui/pyjs/build.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svgui/pyjs/build.py Sat May 12 11:21:10 2012 +0200 @@ -0,0 +1,724 @@ +#!/usr/bin/env python + +import sys +import os +import shutil +from copy import copy +from os.path import join, dirname, basename, abspath, split, isfile, isdir +from optparse import OptionParser +import pyjs +from cStringIO import StringIO +try: + # Python 2.5 and above + from hashlib import md5 +except: + import md5 +import re + +usage = """ + usage: %prog [options] + +This is the command line builder for the pyjamas project, which can +be used to build Ajax applications from Python. +For more information, see the website at http://pyjs.org/ +""" + +# GWT1.2 Impl | GWT1.2 Output | Pyjamas 0.2 Platform | Pyjamas 0.2 Output +# -------------+-----------------------+----------------------+---------------------- +# IE6 | ie6 | IE6 | ie6 +# Opera | opera | Opera | opera +# Safari | safari | Safari | safari +# -- | gecko1_8 | Mozilla | mozilla +# -- | gecko | OldMoz | oldmoz +# Standard | all | (default code) | all +# Mozilla | gecko1_8, gecko | -- | -- +# Old | safari, gecko, opera | -- | -- + +version = "%prog pyjamas version 2006-08-19" + +# these names in lowercase need match the strings +# returned by "provider$user.agent" in order to be selected corretly +app_platforms = ['IE6', 'Opera', 'OldMoz', 'Safari', 'Mozilla'] + +# usually defaults to e.g. /usr/share/pyjamas +_data_dir = os.path.join(pyjs.prefix, "share/pyjamas") + + +# .cache.html files produces look like this +CACHE_HTML_PAT=re.compile('^[a-z]*.[0-9a-f]{32}\.cache\.html$') + +# ok these are the three "default" library directories, containing +# the builtins (str, List, Dict, ord, round, len, range etc.) +# the main pyjamas libraries (pyjamas.ui, pyjamas.Window etc.) +# and the contributed addons + +for p in ["library/builtins", + "library", + "addons"]: + p = os.path.join(_data_dir, p) + if os.path.isdir(p): + pyjs.path.append(p) + + +def read_boilerplate(data_dir, filename): + return open(join(data_dir, "builder/boilerplate", filename)).read() + +def copy_boilerplate(data_dir, filename, output_dir): + filename = join(data_dir, "builder/boilerplate", filename) + shutil.copy(filename, output_dir) + + +# taken and modified from python2.4 +def copytree_exists(src, dst, symlinks=False): + if not os.path.exists(src): + return + + names = os.listdir(src) + try: + os.mkdir(dst) + except: + pass + + errors = [] + for name in names: + if name.startswith('CVS'): + continue + if name.startswith('.git'): + continue + if name.startswith('.svn'): + continue + + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + try: + if symlinks and os.path.islink(srcname): + linkto = os.readlink(srcname) + os.symlink(linkto, dstname) + elif isdir(srcname): + copytree_exists(srcname, dstname, symlinks) + else: + shutil.copy2(srcname, dstname) + except (IOError, os.error), why: + errors.append((srcname, dstname, why)) + if errors: + print errors + +def check_html_file(source_file, dest_path): + """ Checks if a base HTML-file is available in the PyJamas + output directory. + If the HTML-file isn't available, it will be created. + + If a CSS-file with the same name is available + in the output directory, a reference to this CSS-file + is included. + + If no CSS-file is found, this function will look for a special + CSS-file in the output directory, with the name + "pyjamas_default.css", and if found it will be referenced + in the generated HTML-file. + + [thank you to stef mientki for contributing this function] + """ + + base_html = """\ + + + + + %(css)s + %(title)s + + + + + +""" + + filename = os.path.split ( source_file )[1] + mod_name = os.path.splitext ( filename )[0] + file_name = os.path.join ( dest_path, mod_name + '.html' ) + + # if html file in output directory exists, leave it alone. + if os.path.exists ( file_name ): + return 0 + + if os.path.exists ( + os.path.join ( dest_path, mod_name + '.css' ) ) : + css = "" + elif os.path.exists ( + os.path.join ( dest_path, 'pyjamas_default.css' ) ) : + css = "" + + else: + css = '' + + title = 'PyJamas Auto-Generated HTML file ' + mod_name + + base_html = base_html % {'modulename': mod_name, 'title': title, 'css': css} + + fh = open (file_name, 'w') + fh.write (base_html) + fh.close () + + return 1 + + +def build(app_name, output, js_includes=(), debug=False, dynamic=0, + data_dir=None, cache_buster=False, optimize=False): + + # make sure the output directory is always created in the current working + # directory or at the place given if it is an absolute path. + output = os.path.abspath(output) + msg = "Building '%(app_name)s' to output directory '%(output)s'" % locals() + if debug: + msg += " with debugging statements" + print msg + + # check the output directory + if os.path.exists(output) and not os.path.isdir(output): + print >>sys.stderr, "Output destination %s exists and is not a directory" % output + return + if not os.path.isdir(output): + try: + print "Creating output directory" + os.mkdir(output) + except StandardError, e: + print >>sys.stderr, "Exception creating output directory %s: %s" % (output, e) + + ## public dir + for p in pyjs.path: + pub_dir = join(p, 'public') + if isdir(pub_dir): + print "Copying: public directory of library %r" % p + copytree_exists(pub_dir, output) + + ## AppName.html - can be in current or public directory + html_input_filename = app_name + ".html" + html_output_filename = join(output, basename(html_input_filename)) + if os.path.isfile(html_input_filename): + if not os.path.isfile(html_output_filename) or \ + os.path.getmtime(html_input_filename) > \ + os.path.getmtime(html_output_filename): + try: + shutil.copy(html_input_filename, html_output_filename) + except: + print >>sys.stderr, "Warning: Missing module HTML file %s" % html_input_filename + + print "Copying: %(html_input_filename)s" % locals() + + if check_html_file(html_input_filename, output): + print >>sys.stderr, "Warning: Module HTML file %s has been auto-generated" % html_input_filename + + ## pygwt.js + + print "Copying: pygwt.js" + + pygwt_js_template = read_boilerplate(data_dir, "pygwt.js") + pygwt_js_output = open(join(output, "pygwt.js"), "w") + + print >>pygwt_js_output, pygwt_js_template + + pygwt_js_output.close() + + ## Images + + print "Copying: Images and History" + copy_boilerplate(data_dir, "corner_dialog_topleft_black.png", output) + copy_boilerplate(data_dir, "corner_dialog_topright_black.png", output) + copy_boilerplate(data_dir, "corner_dialog_bottomright_black.png", output) + copy_boilerplate(data_dir, "corner_dialog_bottomleft_black.png", output) + copy_boilerplate(data_dir, "corner_dialog_edge_black.png", output) + copy_boilerplate(data_dir, "corner_dialog_topleft.png", output) + copy_boilerplate(data_dir, "corner_dialog_topright.png", output) + copy_boilerplate(data_dir, "corner_dialog_bottomright.png", output) + copy_boilerplate(data_dir, "corner_dialog_bottomleft.png", output) + copy_boilerplate(data_dir, "corner_dialog_edge.png", output) + copy_boilerplate(data_dir, "tree_closed.gif", output) + copy_boilerplate(data_dir, "tree_open.gif", output) + copy_boilerplate(data_dir, "tree_white.gif", output) + copy_boilerplate(data_dir, "history.html", output) + + + ## all.cache.html + app_files = generateAppFiles(data_dir, js_includes, app_name, debug, + output, dynamic, cache_buster, optimize) + + ## AppName.nocache.html + + print "Creating: %(app_name)s.nocache.html" % locals() + + home_nocache_html_template = read_boilerplate(data_dir, "home.nocache.html") + home_nocache_html_output = open(join(output, app_name + ".nocache.html"), + "w") + + # the selector templ is added to the selectScript function + select_tmpl = """O(["true","%s"],"%s");""" + script_selectors = StringIO() + + for platform, file_prefix in app_files: + print >> script_selectors, select_tmpl % (platform, file_prefix) + + print >>home_nocache_html_output, home_nocache_html_template % dict( + app_name = app_name, + script_selectors = script_selectors.getvalue(), + ) + + home_nocache_html_output.close() + + print "Done. You can run your app by opening '%(html_output_filename)s' in a browser" % locals() + + +def generateAppFiles(data_dir, js_includes, app_name, debug, output, dynamic, + cache_buster, optimize): + + all_cache_html_template = read_boilerplate(data_dir, "all.cache.html") + mod_cache_html_template = read_boilerplate(data_dir, "mod.cache.html") + + # clean out the old ones first + for name in os.listdir(output): + if CACHE_HTML_PAT.match(name): + p = join(output, name) + print "Deleting existing app file %s" % p + os.unlink(p) + + app_files = [] + tmpl = read_boilerplate(data_dir, "all.cache.html") + parser = pyjs.PlatformParser("platform") + app_headers = '' + scripts = [''%script \ + for script in js_includes] + app_body = '\n'.join(scripts) + + mod_code = {} + mod_libs = {} + modules = {} + app_libs = {} + early_app_libs = {} + app_code = {} + overrides = {} + pover = {} + app_modnames = {} + mod_levels = {} + + # First, generate all the code. + # Second, (dynamic only), post-analyse the places where modules + # haven't changed + # Third, write everything out. + + for platform in app_platforms: + + mod_code[platform] = {} + mod_libs[platform] = {} + modules[platform] = [] + pover[platform] = {} + app_libs[platform] = '' + early_app_libs[platform] = '' + app_code[platform] = {} + app_modnames[platform] = {} + + # Application.Platform.cache.html + + parser.setPlatform(platform) + app_translator = pyjs.AppTranslator( + parser=parser, dynamic=dynamic, optimize=optimize) + early_app_libs[platform], appcode = \ + app_translator.translate(None, is_app=False, + debug=debug, + library_modules=['dynamicajax.js', + '_pyjs.js', 'sys', + 'pyjslib']) + pover[platform].update(app_translator.overrides.items()) + for mname, name in app_translator.overrides.items(): + pd = overrides.setdefault(mname, {}) + pd[platform] = name + + print appcode + #mod_code[platform][app_name] = appcode + + # platform.Module.cache.js + + modules_done = ['pyjslib', 'sys', '_pyjs.js'] + #modules_to_do = [app_name] + app_translator.library_modules + modules_to_do = [app_name] + app_translator.library_modules + + dependencies = {} + + deps = map(pyjs.strip_py, modules_to_do) + for d in deps: + sublist = add_subdeps(dependencies, d) + modules_to_do += sublist + deps = uniquify(deps) + #dependencies[app_name] = deps + + modules[platform] = modules_done + modules_to_do + + while modules_to_do: + + #print "modules to do", modules_to_do + + mn = modules_to_do.pop() + mod_name = pyjs.strip_py(mn) + + if mod_name in modules_done: + continue + + modules_done.append(mod_name) + + mod_cache_name = "%s.%s.cache.js" % (platform.lower(), mod_name) + + parser.setPlatform(platform) + mod_translator = pyjs.AppTranslator(parser=parser, optimize=optimize) + mod_libs[platform][mod_name], mod_code[platform][mod_name] = \ + mod_translator.translate(mod_name, + is_app=False, + debug=debug) + pover[platform].update(mod_translator.overrides.items()) + for mname, name in mod_translator.overrides.items(): + pd = overrides.setdefault(mname, {}) + pd[platform] = name + + mods = mod_translator.library_modules + modules_to_do += mods + modules[platform] += mods + + deps = map(pyjs.strip_py, mods) + sd = subdeps(mod_name) + if len(sd) > 1: + deps += sd[:-1] + while mod_name in deps: + deps.remove(mod_name) + + #print + #print + #print "modname preadd:", mod_name, deps + #print + #print + for d in deps: + sublist = add_subdeps(dependencies, d) + modules_to_do += sublist + modules_to_do += add_subdeps(dependencies, mod_name) + #print "modname:", mod_name, deps + deps = uniquify(deps) + #print "modname:", mod_name, deps + dependencies[mod_name] = deps + + # work out the dependency ordering of the modules + + mod_levels[platform] = make_deps(None, dependencies, modules_done) + + # now write everything out + + for platform in app_platforms: + + early_app_libs_ = early_app_libs[platform] + app_libs_ = app_libs[platform] + app_code_ = app_code[platform] + #modules_ = filter_mods(app_name, modules[platform]) + mods = flattenlist(mod_levels[platform]) + mods.reverse() + modules_ = filter_mods(None, mods) + + for mod_name in modules_: + + mod_code_ = mod_code[platform][mod_name] + + mod_name = pyjs.strip_py(mod_name) + + override_name = "%s.%s" % (platform.lower(), mod_name) + if pover[platform].has_key(override_name): + mod_cache_name = "%s.cache.js" % (override_name) + else: + mod_cache_name = "%s.cache.js" % (mod_name) + + print "Creating: " + mod_cache_name + + modlevels = make_deps(None, dependencies, dependencies[mod_name]) + + modnames = [] + + for md in modlevels: + mnames = map(lambda x: "'%s'" % x, md) + mnames = "new pyjslib.List([\n\t\t\t%s])" % ',\n\t\t\t'.join(mnames) + modnames.append(mnames) + + modnames.reverse() + modnames = "new pyjslib.List([\n\t\t%s\n\t])" % ',\n\t\t'.join(modnames) + + # convert the overrides + + overnames = map(lambda x: "'%s': '%s'" % x, pover[platform].items()) + overnames = "new pyjslib.Dict({\n\t\t%s\n\t})" % ',\n\t\t'.join(overnames) + + if dynamic: + mod_cache_html_output = open(join(output, mod_cache_name), "w") + else: + mod_cache_html_output = StringIO() + + print >>mod_cache_html_output, mod_cache_html_template % dict( + mod_name = mod_name, + app_name = app_name, + modnames = modnames, + overrides = overnames, + mod_libs = mod_libs[platform][mod_name], + dynamic = dynamic, + mod_code = mod_code_, + ) + + if dynamic: + mod_cache_html_output.close() + else: + mod_cache_html_output.seek(0) + app_libs_ += mod_cache_html_output.read() + + # write out the dependency ordering of the modules + + app_modnames = [] + + for md in mod_levels[platform]: + mnames = map(lambda x: "'%s'" % x, md) + mnames = "new pyjslib.List([\n\t\t\t%s])" % ',\n\t\t\t'.join(mnames) + app_modnames.append(mnames) + + app_modnames.reverse() + app_modnames = "new pyjslib.List([\n\t\t%s\n\t])" % ',\n\t\t'.join(app_modnames) + + # convert the overrides + + overnames = map(lambda x: "'%s': '%s'" % x, pover[platform].items()) + overnames = "new pyjslib.Dict({\n\t\t%s\n\t})" % ',\n\t\t'.join(overnames) + + #print "platform names", platform, overnames + #print pover + + # now write app.allcache including dependency-ordered list of + # library modules + + file_contents = all_cache_html_template % dict( + app_name = app_name, + early_app_libs = early_app_libs_, + app_libs = app_libs_, + app_code = app_code_, + app_body = app_body, + overrides = overnames, + platform = platform.lower(), + dynamic = dynamic, + app_modnames = app_modnames, + app_headers = app_headers + ) + if cache_buster: + digest = md5.new(file_contents).hexdigest() + file_name = "%s.%s.%s" % (platform.lower(), app_name, digest) + else: + file_name = "%s.%s" % (platform.lower(), app_name) + file_name += ".cache.html" + out_path = join(output, file_name) + out_file = open(out_path, 'w') + out_file.write(file_contents) + out_file.close() + app_files.append((platform.lower(), file_name)) + print "Created app file %s:%s: %s" % ( + app_name, platform, out_path) + + return app_files + +def flattenlist(ll): + res = [] + for l in ll: + res += l + return res + +# creates sub-dependencies e.g. pyjamas.ui.Widget +# creates pyjamas.ui.Widget, pyjamas.ui and pyjamas. +def subdeps(m): + d = [] + m = m.split(".") + for i in range(0, len(m)): + d.append('.'.join(m[:i+1])) + return d + +import time + +def add_subdeps(deps, mod_name): + sd = subdeps(mod_name) + if len(sd) == 1: + return [] + #print "subdeps", mod_name, sd + #print "deps", deps + res = [] + for i in range(0, len(sd)-1): + parent = sd[i] + child = sd[i+1] + l = deps.get(child, []) + l.append(parent) + deps[child] = l + if parent not in res: + res.append(parent) + #print deps + return res + +# makes unique and preserves list order +def uniquify(md): + res = [] + for m in md: + if m not in res: + res.append(m) + return res + +def filter_mods(app_name, md): + while 'sys' in md: + md.remove('sys') + while 'pyjslib' in md: + md.remove('pyjslib') + while app_name in md: + md.remove(app_name) + md = filter(lambda x: not x.endswith('.js'), md) + md = map(pyjs.strip_py, md) + + return uniquify(md) + +def filter_deps(app_name, deps): + + res = {} + for (k, l) in deps.items(): + mods = filter_mods(k, l) + while k in mods: + mods.remove(k) + res[k] = mods + return res + +def has_nodeps(mod, deps): + if not deps.has_key(mod) or not deps[mod]: + return True + return False + +def nodeps_list(mod_list, deps): + res = [] + for mod in mod_list: + if has_nodeps(mod, deps): + res.append(mod) + return res + +# this function takes a dictionary of dependent modules and +# creates a list of lists. the first list will be modules +# that have no dependencies; the second list will be those +# modules that have the first list as dependencies; the +# third will be those modules that have the first and second... +# etc. + + +def make_deps(app_name, deps, mod_list): + print "Calculating Dependencies ..." + mod_list = filter_mods(app_name, mod_list) + deps = filter_deps(app_name, deps) + + if not mod_list: + return [] + + #print mod_list + #print deps + + ordered_deps = [] + last_len = -1 + while deps: + l_deps = len(deps) + #print l_deps + if l_deps==last_len: + for m, dl in deps.items(): + for d in dl: + if m in deps.get(d, []): + raise Exception('Circular Imports found: \n%s %s -> %s %s' + % (m, dl, d, deps[d])) + #raise Exception('Could not calculate dependencies: \n%s' % deps) + break + last_len = l_deps + #print "modlist", mod_list + nodeps = nodeps_list(mod_list, deps) + #print "nodeps", nodeps + mod_list = filter(lambda x: x not in nodeps, mod_list) + newdeps = {} + for k in deps.keys(): + depslist = deps[k] + depslist = filter(lambda x: x not in nodeps, depslist) + if depslist: + newdeps[k] = depslist + #print "newdeps", newdeps + deps = newdeps + ordered_deps.append(nodeps) + #time.sleep(0) + + if mod_list: + ordered_deps.append(mod_list) # last dependencies - usually the app(s) + + ordered_deps.reverse() + + return ordered_deps + +def main(): + global app_platforms + + parser = OptionParser(usage = usage, version = version) + parser.add_option("-o", "--output", dest="output", + help="directory to which the webapp should be written") + parser.add_option("-j", "--include-js", dest="js_includes", action="append", + help="javascripts to load into the same frame as the rest of the script") + parser.add_option("-I", "--library_dir", dest="library_dirs", + action="append", help="additional paths appended to PYJSPATH") + parser.add_option("-D", "--data_dir", dest="data_dir", + help="path for data directory") + parser.add_option("-m", "--dynamic-modules", action="store_true", + dest="dynamic", default=False, + help="Split output into separate dynamically-loaded modules (experimental)") + parser.add_option("-P", "--platforms", dest="platforms", + help="platforms to build for, comma-separated") + parser.add_option("-d", "--debug", action="store_true", dest="debug") + parser.add_option("-O", "--optimize", action="store_true", + dest="optimize", default=False, + help="Optimize generated code (removes all print statements)", + ) + parser.add_option("-c", "--cache_buster", action="store_true", + dest="cache_buster", + help="Enable browser cache-busting (MD5 hash added to output filenames)") + + parser.set_defaults(output = "output", js_includes=[], library_dirs=[], + platforms=(','.join(app_platforms)), + data_dir=os.path.join(sys.prefix, "share/pyjamas"), + dynamic=False, + cache_buster=False, + debug=False) + (options, args) = parser.parse_args() + if len(args) != 1: + parser.error("incorrect number of arguments") + + data_dir = abspath(options.data_dir) + + app_path = args[0] + if app_path.endswith('.py'): + app_path = abspath(app_path) + if not isfile(app_path): + parser.error("Application file not found %r" % app_path) + app_path, app_name = split(app_path) + app_name = app_name[:-3] + pyjs.path.append(app_path) + elif os.path.sep in app_path: + parser.error("Not a valid module declaration %r" % app_path) + else: + app_name = app_path + + for d in options.library_dirs: + pyjs.path.append(abspath(d)) + + if options.platforms: + app_platforms = options.platforms.split(',') + + # this is mostly for getting boilerplate stuff + data_dir = os.path.abspath(options.data_dir) + + build(app_name, options.output, options.js_includes, + options.debug, options.dynamic and 1 or 0, data_dir, + options.cache_buster, options.optimize) + +if __name__ == "__main__": + main() +