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