Hi,

I'd like to share another possible way of building qooxdoo and dependent libraries. I guess at least Petr might be interested :)

The thing is that classical qooxdoo generator doesn't fit our application toolchain/process well. I.e. we have quite different urls and resources structure/path resolving. Another problem is that we can't gather all qooxdoo classes at build time, hence common build and optimization through the whole application is not possible.

The solution I came up with is building every library to its own file:
qx.js, dbgui.js, some_plugin.js, and gather those files myself (without qxloader). And in this case I need to extract and save all related data: resources, translation, locales. So I need all the functionality qooxdoo generator has but without the last step of generating JS files.

I created the python package called pyqooxdoo, put all qooxdoo trunk there (as resources) and added couple of scripts to automate build process. I'm attaching main of them to illustrate words above.

Thanks,
        Oleksiy
import sys, os, re, tempfile, pkg_resources, subprocess, shutil
import yaml, simplejson
from StringIO import StringIO
from copy import deepcopy
from patch import Generator

TEMPLATE = {
    "export": ['build', 'source'],
    "jobs": {
        "build": {
            "cache": {'compile': ''},
            "library": [{}],
            "exclude": ["qx.legacy.*",
                        "qx.ui.treevirtual.*",
                        "qx.theme.Classic",
                        "qx.theme.classic.*",
                        "qx.dev.*",
                        "qx.test.*",
                        "qx.xml.*"
            ],
            "variants": {
                "qx.debug": ["off"],
                "qx.aspects": ["off"]
            }
        }
    }
}

def get_generator(name, manifest, cache):
    assert isinstance(name, str)
    assert isinstance(manifest, str) or isinstance(manifest, dict)
    assert isinstance(cache, str)
    config = deepcopy(TEMPLATE)
    job = config['jobs']['build']
    if name != 'qx':
        del job['exclude']
    library = job['library']
    if isinstance(manifest, str):
        library[0]['manifest'] = manifest
    else:
        tmpdir = tempfile.mkdtemp(prefix='qooxdoo-%s-build' % name)
        filename = os.path.join(tmpdir, 'Manifest.json')
        f = open(filename, 'w')
        simplejson.dump(manifest, f, indent=2)
        f.close()
        library[0]['manifest'] = filename
    job['cache']['compile'] = cache
    return Generator(config)


def build(name, manifest, cache, source=False, uri=None):
    assert source and uri or not source and uri is None
    generator = get_generator(name, manifest, cache)
    resources = generator.get_resources()
    translations = generator.get_translations()
    locales = generator.get_locales()
    content = generator.compile_source(uri) if source \
              else generator.compile_dist()
    return resources, translations, locales, content
   

def get_qx_prop():
    manifest = pkg_resources.resource_filename('pyqooxdoo',
                                '/data/qooxdoo/framework/Manifest.json')
    import pyqooxdoo
    cache = '/tmp/qx-%s-cache' % pyqooxdoo.VERSION
    return manifest, cache


def build_qx(source=False, uri=None):
    manifest, cache = get_qx_prop()
    return build('qx', manifest, cache, source, uri)


def copy_qx_resources(destination):
    manifest, cache = get_qx_prop()
    get_generator('qx', manifest, cache).copy_resources(destination)

import sys, pkg_resources, re, os, simplejson
qooxdoo_generator_path = pkg_resources.resource_filename('pyqooxdoo',
                            '/data/qooxdoo/tool/pylib')
sys.path.append(qooxdoo_generator_path)

from generator.Generator import Generator as QxGenerator, _ResourceHandler
from generator.config.Config import Config
from generator.code.DependencyLoader import DependencyLoader
from generator.code.TreeLoader import TreeLoader
from generator.code.TreeCompiler import TreeCompiler
from generator.action.ImageInfo import ImageInfo, ImgInfoFmt
from generator.action.Locale import Locale
from generator.runtime.Log import Log
from misc import filetool, textutil, idlist, Path

def _progress_patch(self, pos, length):
    pass
Log.progress = _progress_patch

class Generator(QxGenerator):

    __variants = {'qx.debug': 'off', 'qx.aspects': 'off'}
    __optimize = ["basecalls", "variables", "privates", "strings"]
    __format = True
    __locales = ["C"]
    
    def __init__(self, config):
        job = 'build'
        console = Log(None, 'error')
        config = Config(console, config)
        config.resolveIncludes()
        config.resolveMacros([job])
        config.resolveLibs([job])
        QxGenerator.__init__(self, config, job, console)
        self._preprocess()

    def _preprocess(self):
         self._namespaces, self._classes, self._docs, self._translations, \
            self._libs = self.scanLibrary(self._config.get("library"))
         self._treeLoader = TreeLoader(self._classes, self._cache,
                                       self._console)
         self._depLoader = DependencyLoader(self._classes, self._cache,
                                            self._console,
                                            self._treeLoader, {}, {})
         self._treeCompiler = TreeCompiler(self._classes, self._cache,
                                           self._console, self._treeLoader)
         self._locale = Locale(self._classes, self._translations,
                               self._cache, self._console,
                               self._treeLoader)
         self._resourceHandler= _ResourceHandler(self)
         self.runUpdateTranslation()
         smartInclude, explicitInclude = \
                self.getIncludes(self._config.get("include", []))
         smartExclude, explicitExclude = \
                self.getExcludes(self._config.get("exclude", []))
         self._classList = self._depLoader.getClassList(smartInclude,
                                                        smartExclude,
                                                        explicitInclude,
                                                        explicitExclude,
                                                        self.__variants)

    def compile_dist(self):
        return self._treeCompiler.compileClasses(self._classList,
                                                 self.__variants,
                                                 self.__optimize,
                                                 self.__format)
    def compile_source(self, uri):
        if not uri.endswith('/'):
            uri += '/'
        template = '<script language="JavaScript" src="%s%%s.js"></script>'\
                % uri
        uris = [template % cls.replace('.', '/') for cls in self._classList]
        return "document.write('%s');" % "\\\n".join(uris)

    def get_resources(self):
        """Pre-calculate image information (e.g. sizes)"""
        data    = {}
        resdata = data
        result  = ""
        imgpatt  = re.compile(r'\.(png|jpeg|jpg|gif)$', re.I)
        skippatt = re.compile(r'\.(meta|py)$', re.I)
        self._imageInfo      = ImageInfo(self._console, self._cache)

        # some helper functions

        def replaceWithNamespace(imguri, liburi, libns):
            pre,libsfx,imgsfx = Path.getCommonPrefix(liburi, imguri)
            if imgsfx[0] == os.sep: imgsfx = imgsfx[1:]  # strip leading '/'
            imgshorturi = os.path.join("${%s}" % libns, imgsfx)
            return imgshorturi

        def extractAssetPart(libresuri, imguri):
            pre,libsfx,imgsfx = Path.getCommonPrefix(libresuri, imguri) # split libresuri from imguri
            if imgsfx[0] == os.sep: imgsfx = imgsfx[1:]  # strip leading '/'
            return imgsfx                # use the bare img suffix as its asset Id

        def normalizeImgUri(uriFromMetafile, trueCombinedUri, combinedUriFromMetafile):
            # normalize paths (esp. "./x" -> "x")
            (uriFromMetafile, trueCombinedUri, combinedUriFromMetafile) = map(os.path.normpath,(uriFromMetafile, trueCombinedUri, combinedUriFromMetafile))
            # get the "wrong" prefix (in mappedUriPrefix)
            trueUriPrefix, mappedUriPrefix, sfx = Path.getCommonSuffix(trueCombinedUri, combinedUriFromMetafile)
            # ...and strip it from contained image uri, to get a correct suffix (in uriSuffix)
            pre, mappedUriSuffix, uriSuffix = Path.getCommonPrefix(mappedUriPrefix, uriFromMetafile)
            # ...then compose the correct prefix with the correct suffix
            normalUri = os.path.normpath(os.path.join(trueUriPrefix, uriSuffix))
            return normalUri

        def processCombinedImg(data, meta_fname, cimguri, cimgshorturi, cimgfmt):
            assert cimgfmt.lib, cimgfmt.type
            # read meta file
            mfile = open(meta_fname)
            imgDict = simplejson.loads(mfile.read())
            mfile.close()
            for mimg, mimgs in imgDict.iteritems():
                # sort of like this: mimg : [width, height, type, combinedUri, off-x, off-y]
                mimgspec = ImgInfoFmt(mimgs)
                # have to normalize the uri's from the meta file
                # cimguri is relevant, like: "../../framework/source/resource/qx/decoration/Modern/panel-combined.png"
                # mimg is an uri from when the meta file was generated, like: "./source/resource/qx/decoration/Modern/..."
                mimguri = normalizeImgUri(mimg, cimguri, mimgspec.mappedId)
                ## replace lib uri with lib namespace in mimguri
                ##mimgshorturi = replaceWithNamespace(mimguri, libresuri, cimgfmt.lib)
                mimgshorturi = extractAssetPart(libresuri, mimguri)
                mimgshorturi = Path.posifyPath(mimgshorturi)

                mimgspec.mappedId = cimgshorturi        # correct the mapped uri of the combined image
                mimgspec.lib      = cimgfmt.lib
                mimgspec.mtype    = cimgfmt.type
                mimgspec.mlib     = cimgfmt.lib
                data[mimgshorturi] = mimgspec.flatten()  # this information takes precedence over existing


        # main
        for lib in self._libs.values():
            libresuri = os.path.join(lib['uri'],lib['resource'])
            resourceList = self._resourceHandler.findAllResources([lib],
                                self._resourceHandler.filterResourcesByClasslist(self._classList))
            for resource in resourceList:
                ##assetId = replaceWithNamespace(imguri, libresuri, lib['namespace'])
                assetId = extractAssetPart(libresuri, resource[1])
                assetId = Path.posifyPath(assetId)
                if imgpatt.search(resource[0]): # handle images
                    imgpath= resource[0]
                    imguri = resource[1]
                    imageInfo = self._imageInfo.getImageInfo(imgpath)

                    # use an ImgInfoFmt object, to abstract from flat format
                    imgfmt = ImgInfoFmt()
                    imgfmt.lib = lib['namespace']
                    if not 'type' in imageInfo:
                        raise RuntimeError, "Unable to get image info from file: %s" % imgpath
                    imgfmt.type = imageInfo['type']

                    # check for a combined image and process the contained images
                    meta_fname = os.path.splitext(imgpath)[0]+'.meta'
                    if os.path.exists(meta_fname):  # add included imgs
                        processCombinedImg(data, meta_fname, imguri, assetId, imgfmt)

                    # add this image directly
                    # imageInfo = {width, height, filetype}
                    if not 'width' in imageInfo or not 'height' in imageInfo:
                        raise RuntimeError, "Unable to get image info from file: %s" % imgpath
                    imgfmt.width, imgfmt.height, imgfmt.type = (
                        imageInfo['width'], imageInfo['height'], imageInfo['type'])
                    # check if img is already registered as part of a combined image
                    if assetId in data:
                        x = ImgInfoFmt()
                        x.fromFlat(data[assetId])
                        if x.mappedId:
                            continue  # don't overwrite the combined entry
                    data[assetId] = imgfmt.flatten()
                elif skippatt.search(resource[0]):
                    continue
                else:  # handle other resources
                    resdata[assetId] = lib['namespace']
        return resdata

    def get_translations(self):
        return self._locale.generatePackageData(self._classes,
                                                self.__variants,
                                                self.__locales)
    
    def get_locales(self):
        return self._locale.getLocalizationData(self.__locales)

    def copy_resources(self, destination):
        resList  = self._resourceHandler.findAllResources(self._libs.values(),
            self._resourceHandler.filterResourcesByClasslist(self._classList))
        lib = self._libs.values()[0]
        libpath = os.path.join(lib['path'], lib['resource'])
        libpath = os.path.normpath(libpath)
        for res, _ in resList:
            dst = os.path.dirname(res.replace(libpath, destination))
            self._copyResources(res, dst)
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
qooxdoo-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel

Reply via email to