bin/sync.py
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Fri, 11 Jun 2010 14:07:21 +0300
changeset 6 22214389caed
permissions -rw-r--r--
Revision: 201021 Kit: 2010123

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ============================================================================
#  Name        : sync.py
#  Part of     : Hb
#  Description : Hb themes sync script
#  Version     : %version: %
#
#  Copyright (c) 2008-2010 Nokia.  All rights reserved.
#  This material, including documentation and any related computer
#  programs, is protected by copyright controlled by Nokia.  All
#  rights are reserved.  Copying, including reproducing, storing,
#  adapting or translating, any or all of this material requires the
#  prior written consent of Nokia.  This material also contains
#  confidential information which may not be disclosed to others
#  without the prior written consent of Nokia.
# ============================================================================

import os
import re
import sys
import time
import copy
import shutil
import fnmatch
import zipfile
import optparse
import tempfile
import posixpath
if sys.version_info[0] == 2 and sys.version_info[1] < 4:
    # for scratchbox compatibility
    import popen2
else:
    import subprocess

# ============================================================================
# Globals
# ============================================================================
VERBOSE = False
ARCHIVES = False
INCLUDE = None
EXCLUDE = None
INPUT_DIR = os.getcwd()
OUTPUT_DIR = os.getcwd()
IBY_SOURCE_PREFIX = "ZRESOURCE/hb/themes"
IBY_TARGET_PREFIX = "RESOURCE_FILES_DIR/hb/themes"
BLD_HW_TARGET_PREFIX = "/epoc32/data/z/resource/hb/themes"
BLD_EMU_TARGET_PREFIX = "/epoc32/winscw/c/resource/hb/themes"
BLD_TARGET_PREFIXES = []
SYMBIAN = False
EXIT_STATUS = 0
NAME = "themes"
THEME_COMMON = "themecommon"
THEME_SETTINGS_FILE = "theme.theme"
ENCODER = "SVGTBinEncode.exe"
NVG = True

# ============================================================================
# OptionParser
# ============================================================================
class OptionParser(optparse.OptionParser):
    def __init__(self):
        optparse.OptionParser.__init__(self)
        self.add_option("-v", "--verbose", action="store_true", dest="verbose",
                        help="print verbose information about each step of the sync process")
        self.add_option("-q", "--quiet", action="store_false", dest="verbose",
                        help="do not print information about each step of the sync process")
        self.add_option("-n", "--name", dest="name", metavar="name",
                        help="specify the package <name> (default %s)" % NAME)
        self.add_option("--symbian", action="store_true", dest="symbian",
                        help="work in Symbian mode")
        self.add_option("--nvg", action="store_true", dest="nvg",
                        help="do convert svg to nvg")
        self.add_option("--no-nvg", action="store_false", dest="nvg",
                        help="do not convert svg to nvg")

        group = optparse.OptionGroup(self, "Input/output options")
        self.add_option("-i", "--input", dest="input", metavar="dir",
                        help="specify the input <dir> (default %s)" % INPUT_DIR)
        self.add_option("-o", "--output", dest="output", metavar="dir",
                        help="specify the output <dir> (default %s)" % OUTPUT_DIR)
        self.add_option("-a", "--archives", action="store_true", dest="archives",
                        help="export/install archives (default %s)" % ARCHIVES)
        self.add_option("--include", dest="include", action="append", metavar="pattern",
                        help="specify the include <pattern> (default %s)" % INCLUDE)
        self.add_option("--exclude", dest="exclude", action="append", metavar="pattern",
                        help="specify the exclude <pattern> (default %s)" % EXCLUDE)
        self.add_option_group(group)

        group = optparse.OptionGroup(self, "Prefix options")
        self.add_option("--iby-source-prefix", dest="ibysourceprefix", metavar="prefix",
                        help="specify the iby source <prefix> (default %s)" % IBY_SOURCE_PREFIX)
        self.add_option("--iby-target-prefix", dest="ibytargetprefix", metavar="prefix",
                        help="specify the iby target <prefix> (default %s)" % IBY_TARGET_PREFIX)
        self.add_option("--bld-hw-target-prefix", dest="bldhwtargetprefix", metavar="prefix",
                        help="specify the bld harware target <prefix> (default %s)" % BLD_HW_TARGET_PREFIX)
        self.add_option("--bld-emu-target-prefix", dest="bldemutargetprefix", metavar="prefix",
                        help="specify the bld emulator target <prefix> (default %s)" % BLD_EMU_TARGET_PREFIX)
        self.add_option("--bld-target-prefix", dest="bldtargetprefixes", action="append", metavar="prefix",
                        help="specify an additional bld target <prefix>")
        self.add_option_group(group)

# ============================================================================
# Utils
# ============================================================================
if not hasattr(os.path, "relpath"):
    def relpath(path, start=os.curdir):
        abspath = os.path.abspath(path)
        absstart = os.path.abspath(start)
        if abspath == absstart:
            return "."
        i = len(absstart)
        if not absstart.endswith(os.path.sep):
            i += len(os.path.sep)
        if not abspath.startswith(absstart):
            i = 0
        return abspath[i:]
    os.path.relpath = relpath

def run_process(command, cwd=None):
    code = 0
    output = ""
    try:
        if cwd != None:
            oldcwd = os.getcwd()
            os.chdir(cwd)
        if sys.version_info[0] == 2 and sys.version_info[1] < 4:
            process = popen2.Popen4(command)
            code = process.wait()
            output = process.fromchild.read()
        else:
            process = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            (stdout, stderr) = process.communicate()
            code = process.returncode
            output = stdout + stderr
        if cwd != None:
            os.chdir(oldcwd)
    except Exception, e:
        print(e)
        code = -1
    return [code, output]

def make_target(path):
    # generate a compatible make target name from path
    target = os.path.splitdrive(path)[1].strip("\\/")
    return "_".join(re.split("[\\\/]+", target))

def zip_filelist(filepath):
    files = list()
    archive = zipfile.ZipFile(filepath)
    for entry in archive.namelist():
        if not entry.endswith("/"):
            files.append(entry)
    return files

class Theme:
    def __init__(self, name):
        self.name = name
        self.paths = []
        self.files = {}
        self.archives = {}

    def initialize(self):
        for path in self.paths:
            for root, dirs, files in os.walk(path):
                for file in files:
                    filepath = posixpath.join(root, file).replace("\\", "/")
                    if self._include(filepath):
                        extension = os.path.splitext(filepath)[1]
                        if extension == ".zip":
                            if root not in self.archives:
                                self.archives[root] = list()
                            self.archives[root].append(filepath)
                        else:
                            if root not in self.files:
                                self.files[root] = list()
                            self.files[root].append(filepath)

    def _write_zip_entry(self, archive, filepath):
        path, filename = os.path.split(filepath)
        oldcwd = os.getcwd()
        os.chdir(path)
        archive.write(filename)
        os.chdir(oldcwd)

    def encode(self):
        print "Encoding: %s" % self.name
        for path, archives in self.archives.iteritems():
            relpath = os.path.relpath(path, INPUT_DIR)
            if not relpath.startswith("icons"):
                continue
            for archive in archives:
                # ensure that output dir exists
                outpath = os.path.join(OUTPUT_DIR, relpath)
                if not os.path.exists(outpath):
                    os.makedirs(outpath)

                # extract to a temp dir
                tempdir = tempfile.mkdtemp()
                zip = zipfile.ZipFile(archive)
                for name in zip.namelist():
                    file = open(os.path.join(tempdir, name),'w')
                    file.write(zip.read(name))
                    file.close()

                # convert & re-archive
                total = 0
                converted = 0
                tmpfile, tmpfilepath = tempfile.mkstemp(".zip")
                tmparchive = zipfile.ZipFile(tmpfilepath, 'w')
                for root, dirs, files in os.walk(tempdir):
                    for file in files:
                        filepath = os.path.join(root, file)
                        basepath, extension = os.path.splitext(filepath)
                        if extension == ".svg":
                            total += 1
                            encoder = ENCODER
                            if os.path.exists("/ext/tools/hbbins/bin/3rdparty/%s" % ENCODER):
                                encoder = "/ext/tools/hbbins/bin/3rdparty/%s" % ENCODER
                            res = run_process([encoder, "-v", "6", filepath, "-e", ".nvg"])[0]
                            exists = os.path.exists(basepath + ".nvg")
                            if not exists:
                                self._write_zip_entry(tmparchive, filepath)
                            else:
                                converted += 1
                                self._write_zip_entry(tmparchive, basepath + ".nvg")
       
                # cleanup
                tmparchive.close()
                os.close(tmpfile)
                if converted > 0:
                    shutil.move(tmpfilepath, os.path.join(outpath, os.path.basename(archive)))
                else:
                    os.remove(tmpfilepath)
                shutil.rmtree(tempdir, True)
                print "          %s (%s/%s)" % (os.path.join(relpath, os.path.basename(archive)), converted, total)

    def write_css(self, csspath):
        outpath = os.path.dirname(csspath)
        if not os.path.exists(outpath):
            os.makedirs(outpath)
        groupfile = open(csspath, "w")
        for path, files in copy.deepcopy(self.files.items()):
            for filepath in files:
                basename = os.path.basename(filepath)
                extension = os.path.splitext(basename)[1]
                if extension == ".css":
                    if basename != os.path.basename(csspath):
                        cssfile = open(filepath, "r")
                        groupfile.write(cssfile.read())
                        cssfile.close()
                    self.files[path].remove(filepath)
        groupfile.close()
        if outpath not in self.files:
            self.files[outpath] = list()
        if csspath not in self.files[outpath]:
            self.files[outpath].append(csspath)

    def write_iby(self, ibypath):
        global IBY_SOURCE_PREFIX, IBY_TARGET_PREFIX, EXIT_STATUS
        outpath = os.path.dirname(ibypath)
        if not os.path.exists(outpath):
            os.makedirs(outpath)
        out = open(ibypath, "w")
        out.write("#ifndef __%s_IBY__\n" % self.name.upper())
        out.write("#define __%s_IBY__\n" % self.name.upper())
        out.write("\n")
        out.write("#include <bldvariant.hrh>\n")
        out.write("\n")
        out.write("data=%s/%s.themeindex\t%s/%s.themeindex\n" % (IBY_SOURCE_PREFIX, self.name, IBY_TARGET_PREFIX, self.name))
        written_entries = list()
        for path, files in self.files.iteritems():
            relpath = os.path.relpath(path, INPUT_DIR).replace("\\", "/")
            for filepath in files:
                filename = os.path.basename(filepath)
                entry = posixpath.join(relpath, filename)
                if entry not in written_entries:
                    written_entries.append(filepath)
                    out.write("data=%s/%s\t%s/%s\n" % (IBY_SOURCE_PREFIX, entry, IBY_TARGET_PREFIX, entry))
                else:
                    print "ERROR: %s duplicate entry %s" % (ibypath, entry)
                    EXIT_STATUS = -1
        for path, archives in self.archives.iteritems():
            relpath = os.path.relpath(path, INPUT_DIR).replace("\\", "/")
            for archive in archives:
                files = zip_filelist(archive)
                for filepath in files:
                    entry = posixpath.join(relpath, filepath)
                    if entry not in written_entries:
                        written_entries.append(entry)
                        out.write("data=%s/%s\t%s/%s\n" % (IBY_SOURCE_PREFIX, entry, IBY_TARGET_PREFIX, entry))
                    else:
                        print "ERROR: %s duplicate entry %s" % (ibypath, entry)
                        EXIT_STATUS = -1
        out.write("\n")
        out.write("#endif __%s_IBY__\n" % self.name.upper())
        out.close()

    def _include(self, filepath):
        result = True
        if INCLUDE != None:
            for pattern in INCLUDE:
                if not fnmatch.fnmatch(filepath, pattern):
                    result = False
        if EXCLUDE != None:
            for pattern in EXCLUDE:
                if fnmatch.fnmatch(filepath, pattern):
                    result = False
        return result

def lookup_themes(path):
    themes = {}
    # base: effects, icons...
    for base in os.listdir(path):
        basepath = posixpath.join(path, base)
        if os.path.isdir(basepath):
            # theme: footheme, bartheme...
            for theme in os.listdir(basepath):
                themepath = posixpath.join(basepath, theme)
                if os.path.isdir(themepath):
                    if theme not in themes:
                        themes[theme] = Theme(theme)
                    themes[theme].paths.append(themepath)
    return themes

def write_txt(filepath, themes, prefixes):
    outpath = os.path.dirname(filepath)
    if not os.path.exists(outpath):
        os.makedirs(outpath)
    out = open(filepath, "w")
    for name, theme in themes.iteritems():
        for prefix in prefixes:
            out.write("%s %s %s\n" % (name, prefix, prefix))
    out.close()

def write_pri(filepath, themes, prefixes, settingsfile_exists):
    outpath = os.path.dirname(filepath)
    if not os.path.exists(outpath):
        os.makedirs(outpath)
    outpath = os.path.splitdrive(OUTPUT_DIR)[1]
    out = open(filepath, "w")

    # clean & dist clean rules
    out.write("QMAKE_CLEAN += %s\n" % filepath)
    out.write("QMAKE_CLEAN += %s\n" % (os.path.splitext(filepath)[0] + ".txt"))
    if settingsfile_exists:
        out.write("QMAKE_CLEAN += %s.iby\n" % posixpath.join(outpath, THEME_COMMON))
    for name, theme in themes.iteritems():
        out.write("QMAKE_CLEAN += %s.iby\n" % posixpath.join(outpath, name))
        for prefix in prefixes:
            out.write("QMAKE_CLEAN += %s.themeindex\n" % posixpath.join(prefix, name))

    out.write("symbian {\n")
    out.write("\tBLD_INF_RULES.prj_exports += \"$${LITERAL_HASH}include <platform_paths.hrh>\"\n")

    if settingsfile_exists:
        # exporting theme settings file
        settingsPath = os.path.splitdrive(posixpath.join(INPUT_DIR,THEME_SETTINGS_FILE))[1]
        out.write("\tBLD_INF_RULES.prj_exports += \"%s\t%s/%s\"\n" % (settingsPath, BLD_HW_TARGET_PREFIX, THEME_SETTINGS_FILE))
        out.write("\tBLD_INF_RULES.prj_exports += \"%s\t%s/%s\"\n" % (settingsPath, BLD_EMU_TARGET_PREFIX, THEME_SETTINGS_FILE))
        out.write("\tBLD_INF_RULES.prj_exports += \"%s.iby\tCORE_MW_LAYER_IBY_EXPORT_PATH(%s.iby)\"\n" % (posixpath.join(outpath, THEME_COMMON), THEME_COMMON))

    for name, theme in themes.iteritems():
        ibyfile = "%s.iby" % name
        out.write("\tBLD_INF_RULES.prj_exports += \"%s\tCORE_MW_LAYER_IBY_EXPORT_PATH(%s)\"\n" % (posixpath.join(outpath, ibyfile), ibyfile))
        for path, files in theme.files.iteritems():
            relpath = os.path.relpath(path, INPUT_DIR).replace("\\", "/")
            for filepath in files:
                filepath = os.path.splitdrive(filepath)[1]
                filename = os.path.basename(filepath)
                out.write("\tBLD_INF_RULES.prj_exports += \"%s\t%s/%s\"\n" % (filepath, BLD_HW_TARGET_PREFIX, posixpath.join(relpath, filename)))
                out.write("\tBLD_INF_RULES.prj_exports += \"%s\t%s/%s\"\n" % (filepath, BLD_EMU_TARGET_PREFIX, posixpath.join(relpath, filename)))
        for path, archives in theme.archives.iteritems():
            relpath = os.path.relpath(path, INPUT_DIR).replace("\\", "/")
            for filepath in archives:
                filepath = os.path.splitdrive(filepath)[1]
                filename = os.path.basename(filepath)
                if ARCHIVES:
                    out.write("\tBLD_INF_RULES.prj_exports += \"%s\t%s/%s\"\n" % (filepath, BLD_HW_TARGET_PREFIX, posixpath.join(relpath, filename)))
                    out.write("\tBLD_INF_RULES.prj_exports += \"%s\t%s/%s\"\n" % (filepath, BLD_EMU_TARGET_PREFIX, posixpath.join(relpath, filename)))
                else:
                    out.write("\tBLD_INF_RULES.prj_exports += \":zip %s\t%s/%s\"\n" % (filepath, BLD_HW_TARGET_PREFIX, relpath))
                    out.write("\tBLD_INF_RULES.prj_exports += \":zip %s\t%s/%s\"\n" % (filepath, BLD_EMU_TARGET_PREFIX, relpath))
    out.write("} else {\n")
    out.write("\tisEmpty(QMAKE_UNZIP):QMAKE_UNZIP = unzip -u -o\n")

    if settingsfile_exists:
        # installing theme settings file
        settingsPath = posixpath.join(INPUT_DIR,THEME_SETTINGS_FILE)
        out.write("\t%s.path += $$(HB_THEMES_DIR)/themes\n" % THEME_COMMON)
        out.write("\t%s.files += %s\n" % (THEME_COMMON, settingsPath))
        out.write("\tINSTALLS += %s\n" % THEME_COMMON)

    for name, theme in themes.iteritems():
        for path, files in theme.files.iteritems():
            target = make_target(path)
            relpath = os.path.relpath(path, INPUT_DIR).replace("\\", "/")
            out.write("\t%s.CONFIG += no_build\n" % target)
            out.write("\t%s.path += $$(HB_THEMES_DIR)/themes/%s\n" % (target, relpath))
            out.write("\t%s.files += %s\n" % (target, " ".join(files)))
            out.write("\tINSTALLS += %s\n" % target)
        for path, archives in theme.archives.iteritems():
            target = make_target(path)
            relpath = os.path.relpath(path, INPUT_DIR).replace("\\", "/")
            out.write("\t%s_zip.CONFIG += no_build\n" % target)
            out.write("\t%s_zip.path += $$(HB_THEMES_DIR)/themes/%s\n" % (target, relpath))
            if ARCHIVES:
                out.write("\t%s_zip.files += %s\n" % (target, " ".join(archives)))
            else:
                commands = []
                for archive in archives:
                    commands.append("$$QMAKE_UNZIP %s -d $$(HB_THEMES_DIR)/themes/%s" % (archive, relpath))
                out.write("\t%s_zip.commands += %s\n" % (target, " && ".join(commands)))
                out.write("\t%s_zip.uninstall += -$(DEL_FILE) $$(HB_THEMES_DIR)/themes/%s/*\n" % (target, relpath))
            out.write("\tINSTALLS += %s_zip\n" % target)
    out.write("}\n")
    out.close()


def write_common_iby(path):
    global VERBOSE, IBY_SOURCE_PREFIX, IBY_TARGET_PREFIX, OUTPUT_DIR, INPUT_DIR 
    global THEME_COMMON, THEME_SETTINGS_FILE

    # Create iby file for theme.theme if it is there
    theme_theme = posixpath.join(INPUT_DIR,THEME_SETTINGS_FILE)
    if os.path.isfile(theme_theme):
        if VERBOSE:
            print "Writing:  %s.iby" % THEME_COMMON
        ibypath = posixpath.join(OUTPUT_DIR, THEME_COMMON + ".iby")
        outpath = os.path.dirname(ibypath)
        if not os.path.exists(outpath):
            os.makedirs(outpath)
        out = open(ibypath, "w")
        out.write("#ifndef __%s_IBY__\n" % THEME_COMMON.upper())
        out.write("#define __%s_IBY__\n" % THEME_COMMON.upper())
        out.write("\n")
        out.write("#include <bldvariant.hrh>\n")
        out.write("\n")
        out.write("data=%s/%s\t%s/%s\n" % (IBY_SOURCE_PREFIX, THEME_SETTINGS_FILE, IBY_TARGET_PREFIX, THEME_SETTINGS_FILE))
        out.write("\n")
        out.write("#endif __%s_IBY__\n" % THEME_COMMON.upper())
        return True

    # theme common iby not written, return false
    return False

# ============================================================================
# main()
# ============================================================================
def main():
    global VERBOSE, ARCHIVES, INPUT_DIR, OUTPUT_DIR, INCLUDE, EXCLUDE, SYMBIAN, NAME, NVG
    global IBY_SOURCE_PREFIX, IBY_TARGET_PREFIX
    global BLD_HW_TARGET_PREFIX, BLD_EMU_TARGET_PREFIX, BLD_TARGET_PREFIXES

    parser = OptionParser()
    (options, args) = parser.parse_args()

    if options.verbose != None:
        VERBOSE = options.verbose
    if options.symbian != None:
        SYMBIAN = options.symbian
    if options.nvg != None:
        NVG = options.nvg
    if options.name != None:
        NAME = options.name
    if options.archives != None:
        ARCHIVES = options.archives
    if options.include != None:
        INCLUDE = options.include
    if options.exclude != None:
        EXCLUDE = options.exclude
    if options.input != None:
        INPUT_DIR = options.input
    if options.output != None:
        OUTPUT_DIR = options.output
    if options.ibysourceprefix != None:
        IBY_SOURCE_PREFIX = options.ibysourceprefix
    if options.ibytargetprefix != None:
        IBY_TARGET_PREFIX = options.ibytargetprefix
    if options.bldhwtargetprefix != None:
        BLD_HW_TARGET_PREFIX = options.bldhwtargetprefix
    if options.bldemutargetprefix != None:
        BLD_EMU_TARGET_PREFIX = options.bldemutargetprefix
    if options.bldtargetprefixes != None:
        BLD_TARGET_PREFIXES = options.bldtargetprefixes

    settingsfile_exists = write_common_iby(INPUT_DIR)

    themes = lookup_themes(INPUT_DIR)
    for name, theme in themes.iteritems():
        theme.initialize()
        if SYMBIAN and NVG:
            theme.encode()
        if VERBOSE:
            print "Writing:  %s/hbcolorgroup.css" % name
        theme.write_css(posixpath.join(OUTPUT_DIR, "style/%s/variables/color/hbcolorgroup.css" % name))
        if VERBOSE:
            print "Writing:  %s.iby" % name
        theme.write_iby(posixpath.join(OUTPUT_DIR, "%s.iby" % name))

    if SYMBIAN:
        prefixes = [BLD_HW_TARGET_PREFIX, BLD_EMU_TARGET_PREFIX]
        prefixes += BLD_TARGET_PREFIXES
    else:
        prefixes = [posixpath.join(os.environ["HB_THEMES_DIR"], "themes")]

    if VERBOSE:
        print "Writing:  %s.pri" % NAME
    write_pri(posixpath.join(OUTPUT_DIR, "%s.pri" % NAME), themes, prefixes, settingsfile_exists)
    if VERBOSE:
        print "Writing:  %s.txt" % NAME
    write_txt(posixpath.join(OUTPUT_DIR, "%s.txt" % NAME), themes, prefixes)

    return EXIT_STATUS

if __name__ == "__main__":
    sys.exit(main())