From 5c4ed9788c48d6307054885c7f4c65ac25d7996a Mon Sep 17 00:00:00 2001 From: Dieter Verfaillie Date: Fri, 12 Nov 2010 09:12:12 +0100 Subject: [PATCH] first commit --- .gitignore | 2 + README.rst | 11 + bin/build_bindings.sh | 133 +++ bin/build_installer.py | 833 ++++++++++++++++++ bin/uuidgen.py | 9 + etc/2.22.0.0-win32.template.wxs | 526 +++++++++++ etc/2.22.0.0-win32.xml | 214 +++++ var/overlays/2.22.0.0/gtk_runtime/__init__.py | 68 ++ .../2.22.0.0/gtk_runtime/__init__.pyc | 0 .../2.22.0.0/gtk_runtime/bin/reconfig.cmd | 5 + .../2.22.0.0/gtk_runtime/etc/gtk-2.0/gtkrc | 2 + .../gtk_runtime/etc/pango/pango.aliases | 8 + var/overlays/2.22.0.0/pygobject/pygtk.pth | 3 + 13 files changed, 1814 insertions(+) create mode 100644 .gitignore create mode 100644 README.rst create mode 100644 bin/build_bindings.sh create mode 100644 bin/build_installer.py create mode 100644 bin/uuidgen.py create mode 100644 etc/2.22.0.0-win32.template.wxs create mode 100644 etc/2.22.0.0-win32.xml create mode 100644 var/overlays/2.22.0.0/gtk_runtime/__init__.py create mode 100644 var/overlays/2.22.0.0/gtk_runtime/__init__.pyc create mode 100644 var/overlays/2.22.0.0/gtk_runtime/bin/reconfig.cmd create mode 100644 var/overlays/2.22.0.0/gtk_runtime/etc/gtk-2.0/gtkrc create mode 100644 var/overlays/2.22.0.0/gtk_runtime/etc/pango/pango.aliases create mode 100644 var/overlays/2.22.0.0/pygobject/pygtk.pth diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5fb3015 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tmp/* +var/cache/* diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..96e8708 --- /dev/null +++ b/README.rst @@ -0,0 +1,11 @@ +=============== +pygtk-installer +=============== + +The pygtk-installer project contains: +- a tool that build windows installer packages (both .exe and .msi) for pygtk and dependencies. +- a tool that uses the separate installers created above and generates an all-in-one installer. + +.. CAUTION:: + The all-in-one installer should be considered as a proof of concept. + Please do not use it on productions systems just yet. diff --git a/bin/build_bindings.sh b/bin/build_bindings.sh new file mode 100644 index 0000000..9d89ed0 --- /dev/null +++ b/bin/build_bindings.sh @@ -0,0 +1,133 @@ +#!/bin/bash + +# What is this? +# ============= +# build_installers.sh is a script for building the +# Py{GObject, GTK, GooCanvas, GtkSourceView, Rsvg} +# installers using MinGW. +# This script has been tested with MSYS on MS Windows, +# but should work fine via wine on a Linux distribution. + +# How does it work? +# ================= +# Install the deps (gtk+-bundle, ...) MinGW/MSYS with GCC 4.5.0 +# $ mingw-get.exe install gcc +# $ mingw-get.exe install msys-base +# +# Configure the CHECKOUT, DESTDIR and INTERPRETERS and GTKBUNDLE +# variables below +# +# To build all installers, execute +# $ build_bindings +# +# To build specific (but known!) targets, execute +# $ build_bindings pygobject pygtk +# or +# $ build_bindings pygoocanvas +# or ... well, you get the idea + + +# Configure the path to source repositories. +CHECKOUT="/d/dev/gnome.org/gnome-windows/checkout" + +# Congfigure the destination path for the built installers. +# A subdirectory will be created into it each time you run +# this script. +DESTDIR="/d/dev/gnome.org/gnome-windows/dist" + +# Configure the path to your Python interpreter installations. +# Installers will be built for any interpreter configured here. +# Only Python 2.6 and Python 2.7 are supported atm. +INTERPRETERS="/d/bin/Python26 /d/bin/Python27" # for msys +#INTERPRETERS="c:/Python26 c:/Python27" # for wine + +# Configure the path to your gtk+-bundle installation. +GTKBUNDLE="/d/dev/gnome.org/gnome-windows/prefix/gtk+-bundle/gtk+-bundle_2.22.0-20101016_win32/lib/pkgconfig" + + +# you can stop configureing now ;) +TARGETS="pycairo-1.8.10 pygobject pygtk pygoocanvas pygtksourceview gnome-python-desktop" +DESTDIR=${DESTDIR}/`date +%Y%m%d-%H%M%S` +OLD_CWD=`pwd` +OLD_PATH=${PATH} +OLD_PKG_CONFIG_PATH=${PKG_CONFIG_PATH} + +# check script arguments for specific targets +ARG_TARGETS="" + +# create destdir +mkdir -p ${DESTDIR} + +if [ $# -gt 0 ]; then + for ARG in $@; do + for i in ${TARGETS[@]}; do + if [[ $i == ${ARG} ]] ; then + ARG_TARGETS+=${ARG}" " + fi + done + done + + TARGETS=${ARG_TARGETS} +fi + +# build each target +for TARGET in ${TARGETS}; do + TARGETROOT=${CHECKOUT}/${TARGET} + + if [ ! -d "${TARGETROOT}" ]; then + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "! Could not build \"${TARGET}\": \"${TARGETROOT}\" does not exist." + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + continue + fi + + if [ ! -f "${TARGETROOT}/setup.py" ]; then + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "! Could not build \"${TARGET}\": \"${TARGETROOT}/setup.py\" does not exist." + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + continue + fi + + if [ ${TARGET} = "pygtk" ]; then + # pygtk takes extra arguments + COMMAND="setup.py build --compiler=mingw32 --enable-threading bdist_wininst --user-access-control=auto bdist_msi" + else + COMMAND="setup.py build --compiler=mingw32 bdist_wininst --user-access-control=auto bdist_msi" + fi + + cd ${TARGETROOT} + rm -rf ${TARGETROOT}/build/* + rm -rf ${TARGETROOT}/dist/* + + for INTERPRETER in ${INTERPRETERS}; do + echo "**********************************************************************" + echo "* Building \"${TARGET}\" for \"${INTERPRETER}\"" + echo "**********************************************************************" + + export PATH=${INTERPRETER}:${INTERPRETER}/Scripts:${GTKBUNDLE}/bin:${OLD_PATH} + export PKG_CONFIG_PATH=${INTERPRETER}/Lib/pkgconfig/:${GTKBUNDLE}/lib/pkgconfig/:${OLD_PKG_CONFIG_PATH} + + ${INTERPRETER}/python.exe ${COMMAND} + + INSTALLERS=() + for i in dist/*.exe; do INSTALLERS+=( ${i%} ); done + mv dist/* ${DESTDIR} + if [ ${#INSTALLERS[*]} -gt 0 ]; then + for INSTALLER in ${INSTALLERS}; do + result=`${DESTDIR}/$(basename ${INSTALLER})` + done + else + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "! Building \"${TARGET}\" failed" + echo "! Press any key to continue..." + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + read -p "" + continue + fi + done +done + +# cleanup +export PATH=${OLD_PATH} +export PKG_CONFIG_PATH=${OLD_PKG_CONFIG_PATH} +cd ${OLD_CWD} diff --git a/bin/build_installer.py b/bin/build_installer.py new file mode 100644 index 0000000..e1acbd5 --- /dev/null +++ b/bin/build_installer.py @@ -0,0 +1,833 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +import os +import sys +import re + +from copy import copy, deepcopy +from datetime import datetime +from hashlib import md5 +from optparse import OptionParser +from os.path import abspath, dirname, isdir, isfile, join +from shutil import copyfile, rmtree +from subprocess import Popen, PIPE +from urllib2 import urlopen, URLError +from uuid import uuid4 +from zipfile import ZipFile + +from lxml import etree, objectify + + +# Known Python target versions +PYTHON_VERSIONS = ['2.6', '2.7'] +# Set to the target version when merging a Product +PYTHON_FULLVERSION = None +PYTHON_VERSION = None + +# These paths are used all over the place +ROOTDIR = abspath(join(dirname(__file__), '..')) +ETCDIR = join(ROOTDIR, 'etc') +TMPDIR = join(ROOTDIR, 'tmp') +VARDIR = join(ROOTDIR, 'var') +CACHEDIR = join(VARDIR, 'cache') + +# Everything we need to know about WiX and it's tools +WIX_VERSION = '3.5.2305.0' +WIX_NAMESPACE = 'http://schemas.microsoft.com/wix/2006/wi' +WIX_NSMAP = {None : WIX_NAMESPACE} +WIX_DIR = None +WIX_HEAT = None +WIX_DARK = None +WIX_CANDLE = None +WIX_LIGHT = None + +# Everything we need to know about xmllint +XML_LINT_VERSION = 20707 +XML_LINT = 'xmllint' + + +def info(message, level=0): + print '%s* %s' % (' ' * level, message) + +def error(message, level=0): + message = '%s! %s' % (' ' * level, message) + raise SystemExit(message) + +def generate_uuid(): + return '{%s}' % str(uuid4()).upper() + +def xmllint_format(src_file, dest_file, logfile): + file = open(logfile, 'w') + process = Popen([XML_LINT, + '--nonet', + '--format', + src_file, + '--output', + dest_file], + stdout=file, + stderr=file, + universal_newlines=True) + process.wait() + file.close() + + +class Builder(object): + def __init__(self, arguments=None): + self.parse_options(arguments) + self.parse_configuration() + self.validate_environment() + + def parse_options(self, arguments=None): + if arguments == None: + arguments = sys.argv[1:] + + parser = OptionParser(usage='usage: %prog [options] config') + parser.add_option('-k', '--keep-work', + action='store_true', dest='keep_work', default=False, + help='keep working directory after build') + + (self.options, self.args) = parser.parse_args(arguments) + + if not len(self.args) == 1: + error(parser.get_usage()) + + def parse_configuration(self): + version, platform = self.args[0].split('-') + + configfilename = '%s-%s.xml' % (version, platform) + templatefilename = '%s-%s.template.wxs' % (version, platform) + configfile = join(ETCDIR, configfilename) + templatefile = join(ETCDIR, templatefilename) + + if not isfile(configfile): + error('Unable to load configuration file "%s".' % configfile) + + if not isfile(templatefile): + error('Unable to load template file "%s".' % templatefile) + + self.config = objectify.parse(configfile).getroot() + etree.SubElement(self.config, 'version', version=version) + etree.SubElement(self.config, 'platform', version=platform) + etree.SubElement(self.config, 'templatefile', path=templatefile) + + info('Loaded configuration "%s" (loaded from "%s").' % (version, configfile)) + + def validate_environment(self): + self.validate_environment_wix() + self.validate_environment_xmllint() + + def validate_environment_wix(self): + global WIX_DIR + global WIX_DARK + global WIX_HEAT + global WIX_CANDLE + global WIX_LIGHT + global XML_LINT + + # Get WiX path from environment variable + if not os.environ.has_key('WIX'): + error('Please verify WiX has been installed and the WIX environment ' + 'variable points to it\'s installation directory.') + + WIX_DIR = join(abspath(os.environ['WIX']), 'bin') + + if not os.path.isdir(WIX_DIR): + error('WiX bin directory does not seem to exist.') + + WIX_HEAT = join(WIX_DIR, 'heat.exe') + WIX_DARK = join(WIX_DIR, 'dark.exe') + WIX_CANDLE = join(WIX_DIR, 'candle.exe') + WIX_LIGHT = join(WIX_DIR, 'light.exe') + + # Validate WiX version + output = Popen([WIX_CANDLE, + '-?'], + stdout=PIPE, + stderr=PIPE, + universal_newlines=True).communicate()[0] + + wix_version = re.compile(r"(version )(.*?)($)", re.S|re.M).search(output).group(2) + + if not int(WIX_VERSION.replace('.', '')) <= int(wix_version.replace('.', '')): + error('Your WiX (version %s) is too old. A mininmum of version %s is required.' % (wix_version, WIX_VERSION)) + + + def validate_environment_xmllint(self): + try: + output = Popen([XML_LINT, + '--version'], + stdout=PIPE, + stderr=PIPE, + universal_newlines=True).communicate()[1] + except WindowsError as e: + error('Please verify xmllint (part of libxml2) has been installed ' + 'and it\'s bin directory is on the PATH environment variable') + else: + xml_lint_version = re.compile(r"(version )(.*?)($)", re.S|re.M).search(output).group(2) + + if not XML_LINT_VERSION <= int(xml_lint_version): + error('Your xmllint (version %s) is too old. A mininmum of version %s is required.' % (xml_lint_version, XML_LINT_VERSION)) + + + def run(self): + for child in self.config.interpreters.iterchildren(): + if child.tag == 'interpreter': + if not child.get('version') in PYTHON_VERSIONS: + error('Unknown interpreter version (%s).' % child.get('version')) + + global PYTHON_FULLVERSION + global PYTHON_VERSION + PYTHON_FULLVERSION = child.get('version') + PYTHON_VERSION = child.get('version').replace('.', '') + + product = Product(self.config) + product.merge() + + +class Product(object): + def __init__(self, config): + self.config = config + self.version = self.config.version.get('version') + self.platform = self.config.platform.get('version') + self.templatefile = self.config.templatefile.get('path') + self.packageid = 'pygtk-all-in-one-%s.%s-py%s' % (self.version, self.platform, PYTHON_FULLVERSION) + + self.workdir = join(TMPDIR, PYTHON_FULLVERSION, self.packageid) + + self.wxsfilename = '%s.wxs' % self.packageid + self.wixobjfilename = '%s.wixobj' % self.packageid + self.msifilename = '%s.msi' % self.packageid + self.tmpwxsfile = join(self.workdir, '%s.unformatted' % self.wxsfilename) + self.wxsfile = join(self.workdir, self.wxsfilename) + self.wixobjfile = join(self.workdir, self.wixobjfilename) + self.msifile = join(self.workdir, self.msifilename) + + def merge(self): + info('Building .msi installer targeting Python %s' % PYTHON_FULLVERSION) + + self.clean() + self.prepare() + self.build() + self.transform() + self.compile() + self.link() + + info('Success: .msi installer targeting Python %s has been created (%s)' % (PYTHON_FULLVERSION, self.msifile)) + + def clean(self): + allworkdirs = join(TMPDIR, PYTHON_FULLVERSION) + + if isdir(allworkdirs): + rmtree(allworkdirs) + + def prepare(self): + if not isdir(self.workdir): + os.makedirs(self.workdir) + + copyfile(self.templatefile, self.wxsfile) + + def build(self): + for child in self.config.product.features.iterchildren(): + if child.tag == 'feature': + self.build_feature(child) + else: + info('Unknown child element in Features: "%s".' % child.tag, 1) + + def build_feature(self, feature): + info('Preparing features for "%s"...' % feature.get('id'), 1) + + for child in feature.iterchildren(): + if child.tag == 'feature': + self.build_feature(child) + elif child.tag == 'package': + info('Preparing source package "%s"' % child.get('id'), 2) + + sourcepackage = SourcePackage.from_packagetype(self.config, child) + sourcepackage.merge() + + def transform(self): + # Open our .wxs file + root = etree.parse(self.wxsfile).getroot() + + info('Transforming variables...', 1) + self.transform_variables(root) + + info('Transforming includes...', 1) + self.transform_includes(root) + + info('Transforming features...', 1) + self.transform_features(root) + + info('Writing .wxs file...', 1) + file = open(self.tmpwxsfile, 'w') + file.write(etree.tostring(root, pretty_print=True, xml_declaration=True, encoding='utf-8')) + file.close() + + info('Reformatting .wxs file...', 1) + self.transform_reformat() + + def transform_variables(self, element): + for child in element: + #TODO: child.tag seems to be a function for Comment and + # ProcessingInstruction elements? Feels dirty :( + if 'ProcessingInstruction' in str(child.tag): + if 'PythonVersion' in child.text: + child.text = child.text.replace('XXX', PYTHON_FULLVERSION) + elif 'ProductName' in child.text: + productname = 'Python %s %s' % (PYTHON_FULLVERSION, self.config.product.get('name')) + child.text = child.text.replace('XXX', productname) + elif 'ProductVersion' in child.text: + child.text = child.text.replace('XXX', self.version) + elif 'UpgradeCode' in child.text: + upgradecode = self.config.product.get('upgradecode_%s' % PYTHON_VERSION) + child.text = child.text.replace('XXX', upgradecode) + + def transform_includes(self, element): + #TODO: there has to be a better way to get at elements than .find + .getnext... XPath??? + product = element.find('{%s}Product' % WIX_NAMESPACE) + FEATURE = product.find('{%s}Feature' % WIX_NAMESPACE) + + def transform(element): + for child in element.iterchildren(): + if child.tag == 'feature': + transform(child) + elif child.tag == 'package': + pi = etree.ProcessingInstruction('include', child.get('wxifile_%s' % PYTHON_VERSION)) + FEATURE.addprevious(pi) + + for child in self.config.product.features.iterchildren(): + transform(child) + + def transform_features(self, element): + #TODO: there has to be a better way to get at elements than .find + .getnext... XPath??? + product = element.find('{%s}Product' % WIX_NAMESPACE) + TARGETDIR = product.find('{%s}Directory' % WIX_NAMESPACE) + FEATURE = product.find('{%s}Feature' % WIX_NAMESPACE) + FEATUREV = FEATURE.getnext() + FEATUREX = FEATUREV.getnext() + + def transform(element, PARENT, PARENTV, PARENTX): + if element.tag == 'feature': + parent = etree.SubElement(PARENT, + 'Feature', + Id = element.get('id'), + Title = element.get('title'), + Description = element.get('description'), + Level = PARENT.get('Level')) + parentv = etree.SubElement(PARENTV, + 'Feature', + Id = '%s%s' % (element.get('id'), PYTHON_FULLVERSION), + Title = element.get('title'), + Description = element.get('description'), + Level = PARENTV.get('Level')) + parentx = etree.SubElement(PARENTX, + 'Feature', + Id = '%sX' % element.get('id'), + Title = element.get('title'), + Description = element.get('description'), + Level = PARENTX.get('Level')) + for child in element.iterchildren(): + transform(child, parent, parentv, parentx) + + elif element.tag == 'package': + wxifile = element.get('wxifile_%s' % PYTHON_VERSION) + + iroot = etree.parse(wxifile).getroot() + ITARGETDIRV = iroot.find('{%s}DirectoryRef' % WIX_NAMESPACE) + assert ITARGETDIRV.get('Id') == 'TARGETDIR%s' % PYTHON_FULLVERSION + ITARGETDIRX = ITARGETDIRV.getnext() + assert ITARGETDIRX.get('Id') == 'TARGETDIRX' + ITARGETDIR = ITARGETDIRX.getnext() + assert ITARGETDIR.get('Id') == 'TARGETDIR' + + def traverse(child, parent): + if child.tag == '{%s}Component' % WIX_NAMESPACE: + etree.SubElement(parent, 'ComponentRef', Id=child.get('Id')) + else: + for x in child: + traverse(x, parent) + + for vchild in ITARGETDIRV.iterchildren(): + traverse(vchild, PARENTV) + + for xchild in ITARGETDIRX.iterchildren(): + traverse(xchild, PARENTX) + + for ichild in ITARGETDIR.iterchildren(): + traverse(ichild, PARENT) + + for child in self.config.product.features.iterchildren(): + transform(child, FEATURE, FEATUREV, FEATUREX) + + def transform_reformat(self): + xmllint_format(self.tmpwxsfile, self.wxsfile, join(self.workdir, 'xmllint.log')) + + def compile(self): + info('Compiling sources...', 3) + + logfile = join(self.workdir, 'candle.log') + file = open(logfile, 'w') + process = Popen([WIX_CANDLE, + '-nologo', + self.wxsfile, + '-out', + self.wixobjfile], + stdout=file, + stderr=file, + universal_newlines=True) + + if process.wait() != 0: + info('WiX "candle" reported error(s). Please review "%s".' % logfile, 4) + + file.close() + + def link(self): + info('Linking objects...', 3) + + logfile = join(self.workdir, 'light.log') + file = open(logfile, 'w') + process = Popen([WIX_LIGHT, + '-nologo', + self.wixobjfile, + '-out', + self.msifile], + stdout=file, + stderr=file, + universal_newlines=True) + + if process.wait() != 0: + info('WiX "light" reported error(s). Please review "%s".' % logfile, 4) + + file.close() + + +class SourcePackage(object): + @staticmethod + def from_packagetype(config, package): + packagetype = package.get('type') + + for subclass in SourcePackage.__subclasses__(): + if subclass.__name__ == packagetype: + return subclass(config, package) + else: + error('Unknown source package type "%s".' % packagetype) + + def __init__(self, config, package): + self.config = config + self.package = package + + if not self.package.get('url').endswith('/'): + self.package.set('url', '%s/' % self.package.get('url')) + + self.cachefile = join(CACHEDIR, self.filename) + self.overlaydir = join(VARDIR, 'overlays', self.config.version.get('version'), self.package.get('id')) + self.workdir = join(TMPDIR, PYTHON_FULLVERSION, self.package.get('id')) + self.wxsfile = join(self.workdir, '%s.wxs' % self.package.get('id')) + self.tmpwxifile = join(self.workdir, '%s.wxi.unformatted' % self.package.get('id')) + self.wxifile = join(self.workdir, '%s.wxi' % self.package.get('id')) + + self.package.set('wxifile_%s' % PYTHON_VERSION, self.wxifile) + + def _check_md5(self, file, digest): + m = md5() + f = open(file, 'rb') + + while True: + t = f.read(1024) + if len(t) == 0: break # end of file + m.update(t) + + hexdigest = m.hexdigest() + + if digest == hexdigest: + return True + else: + info('md5 digest mismatch: got "%s", expected "%s"...' % (hexdigest, digest), 4) + return False + + def merge(self): + self.clean() + self.prepare() + self.fetch() + self.unpack() + self.patch() + self.build() + self.transform() + self.save_include() + + def clean(self): + info('Cleaning build environment...', 3) + + if isdir(self.workdir): + rmtree(self.workdir) + + def prepare(self): + info('Preparing build environment...', 3) + + if not isdir(self.workdir): + os.makedirs(self.workdir) + + if not isdir(CACHEDIR): + os.makedirs(CACHEDIR) + + def _fetch_from_cache(self): + if isfile(self.cachefile): + if self._check_md5(self.cachefile, self.digest): + info('Using cached package sources...', 4) + return True + else: + info('Not using chached package sources...', 4) + os.rename(self.cachefile, '%s.corrupt' % self.cachefile) + return False + else: + return False + + def fetch(self): + info('Fetching package sources...', 3) + + if not self._fetch_from_cache(): + url = self.package.get('url') + self.filename + + try: + info('Downloading package sources...', 4) + response = urlopen(url) + cachefile = open(self.cachefile, 'wb') + cachefile.write(response.read()) + cachefile.close() + except URLError: + error('Failed downloading package sources from "%s".' % url) + + if not self._check_md5(self.cachefile, self.digest): + error('md5 mismatch (%s).' % self.cachefile) + + def unpack(self): + raise NotImplementedError + + def patch(self): + info('Applying overlay...', 3) + + def copytree(srcdir, dstdir): + srcnames = os.listdir(srcdir) + + for name in srcnames: + srcfname = join(srcdir, name) + dstfname = join(dstdir, name) + + if isdir(srcfname): + if not isdir(dstfname): + os.mkdir(dstfname) + + copytree(srcfname, dstfname) + elif isfile(srcfname): + sf = open(srcfname, 'rb') + df = open(dstfname, 'wb') + df.write(sf.read()) + df.close() + sf.close() + + if isdir(self.overlaydir): + copytree(self.overlaydir, join(self.workdir, 'File')) + + def build(self): + raise NotImplementedError + + def transform(self): + raise NotImplementedError + + def transform_create_targetdirs(self, element): + ''' + We need to create a new "Directory" "TARGETDIR$(var.PythonVersion)" and a + new "Directory" "TARGETDIRX", where both mirror all: + - "Directory" nodes from "TARGETDIR" with an adapted "Id" + - "Component" nodes from "TARGETDIR" with an adapted "Id" and a newly generated "Guid" + - "File" nodes from "TARGETDIR" into a "CopyFile" node, with an an adapted "ID" + + This needs to be done for .msi files generated by Python 2.6 distutils + as they simply don't exist an all other known Python versions' distutils + because the don't include "RemoveFile" elements... + ''' + TARGETDIRV = etree.Element('DirectoryRef', Id='TARGETDIR%s' % PYTHON_FULLVERSION) + TARGETDIRX = etree.Element('DirectoryRef', Id='TARGETDIRX') + + def transform(source, dest, suffix, directory=None): + for child in source: + if child.tag == '{%s}DirectoryRef' % WIX_NAMESPACE: + transform(child, dest, suffix, child) + + elif child.tag == '{%s}Directory' % WIX_NAMESPACE: + newchild = etree.SubElement(dest, + 'Directory', + Id = '%s%s' % (child.get('Id'), suffix), + Name = child.get('Name')) + + transform(child, newchild, suffix, newchild) + + elif child.tag == '{%s}Component' % WIX_NAMESPACE: + newchild = etree.SubElement(dest, + 'Component', + Id = '%s%s' % (child.get('Id'), suffix), + Guid = generate_uuid()) + + if 'KeyPath' in child.keys(): + newchild.set('KeyPath', child.get('KeyPath')) + else: + newchild.set('KeyPath', 'yes') + + transform(child, newchild, suffix, directory) + + elif child.tag == '{%s}File' % WIX_NAMESPACE: + newchild = etree.SubElement(dest, + 'CopyFile', + Id = '%s%s' % (child.get('Id'), suffix), + FileId = child.get('Id'), + DestinationDirectory = directory.get('Id')) + + elif child.tag == '{%s}RemoveFile' % WIX_NAMESPACE: + newchild = etree.SubElement(dest, + 'RemoveFile', + Id = '%s%s' % (child.get('Id'), suffix), + Directory = '%s%s' % (child.get('Directory'), suffix), + Name = child.get('Name'), + On = child.get('On')) + + transform(element, TARGETDIRV, PYTHON_FULLVERSION) + transform(element, TARGETDIRX, 'X') + element.insert(0, TARGETDIRV) + element.insert(1, TARGETDIRX) + + def transform_id(self, element, prefix=''): + ''' + Prepend Id attributes with prefix to ensure unique Id's + across all merged packages. These changes need to propagate to + FileId and DestinationDirectory. + ''' + + prefix = '%s_' % self.package.get('id') + + def transform(element): + if 'Id' in element.keys(): + if not element.get('Id').startswith('TARGETDIR'): + element.set('Id', '%s%s' % (prefix, element.get('Id'))) + + if 'FileId' in element.keys(): + element.set('FileId', '%s%s' % (prefix, element.get('FileId'))) + + if 'Directory' in element.keys(): + element.set('Directory', '%s%s' % (prefix, element.get('Directory'))) + + if 'DestinationDirectory' in element.keys(): + if not element.get('DestinationDirectory').startswith('TARGETDIR'): + element.set('DestinationDirectory', '%s%s' % (prefix, element.get('DestinationDirectory'))) + + for child in element: + self.transform_id(child, prefix) + + transform(element) + + def save_include(self): + # Save the transformed include + info('Writing .wxi file...', 4) + file = open(self.tmpwxifile, 'w') + file.write(etree.tostring(self.include, pretty_print=True, xml_declaration=True, encoding='utf-8')) + file.close() + + # Reformat saved .wxi file + info('Reformatting .wxi file...', 4) + xmllint_format(self.tmpwxifile, self.wxifile, join(self.workdir, 'xmllint.log')) + + +class MsiSourcePackage(SourcePackage): + def __init__(self, config, package): + if PYTHON_FULLVERSION == '2.6': + self.filename = package.get('msi_26') + self.digest = package.get('digest_26') + elif PYTHON_FULLVERSION == '2.7': + self.filename = package.get('msi_27') + self.digest = package.get('digest_27') + + SourcePackage.__init__(self, config, package) + + def unpack(self): + info('Unpacking package sources...', 3) + + logfile = join(self.workdir, 'dark.log') + file = open(logfile, 'w') + process = Popen([WIX_DARK, + '-nologo', + '-x', # export binaries from cabinets and embedded binaries + self.workdir , # to our workdir + self.cachefile, # decompile this .msi file + self.wxsfile], # save to this .wxs file + stdout=file, + stderr=file, + universal_newlines=True) + + if process.wait() != 0: + info('WiX "dark" reported error(s). Please review "%s".' % logfile, 4) + + file.close() + + def build(self): + info('Creating .wxi include file...', 3) + + # Get the Wix/Product/Directory node + root = etree.parse(self.wxsfile).getroot() + product = root.find('{%s}Product' % WIX_NAMESPACE) + include = product.find('{%s}Directory' % WIX_NAMESPACE) + + # deepcopy the Wix/Product/Directory node into a new tree + newroot = etree.Element('{%s}Include' % WIX_NAMESPACE, nsmap=WIX_NSMAP) + newinclude = etree.SubElement(newroot, '{%s}DirectoryRef' % WIX_NAMESPACE, Id='TARGETDIR') + + for child in include: + newinclude.append(deepcopy(child)) + + self.include = newroot + + def transform(self): + if PYTHON_FULLVERSION != '2.6': + info('Removing "TARGETDIR%s" and "TARGETDIRX"' % PYTHON_FULLVERSION, 4) + self.transform_remove_targetdirs(self.include) + + info('Creating "TARGETDIR%s" and "TARGETDIRX"' % PYTHON_FULLVERSION, 4) + self.transform_create_targetdirs(self.include) + + info('Transforming "Id" attributes...', 4) + self.transform_id(self.include) + + info('Transforming "ShortName" attributes...', 4) + self.transform_shortname(self.include) + + def transform_remove_targetdirs(self, element): + ''' + Remove "TARGETDIR $(var.PythonVersion)" and "TARGETDIRX" elements so + they can be recreated with "RemoveFile" elements included. + ''' + tmp = element.find('{%s}DirectoryRef' % WIX_NAMESPACE) + + for child in tmp: + if child.get('Id').startswith('TARGETDIR'): + tmp.remove(child) + + def transform_shortname(self, element): + ''' + We want WiX to generate "ShorName" attributes, so we remove them. + This also fixes illegal "ShortName" attributes on "RemoveFile" elements + coming from the .msi files generated by Python distutils. + ''' + def transform(element): + if 'ShortName' in element.keys(): + del element.attrib['ShortName'] + + for child in element: + transform(child) + + transform(element) + + +class ArchiveSourcePackage(SourcePackage): + def __init__(self, config, package): + self.filename = package.get('archive') + self.digest = package.get('digest') + + SourcePackage.__init__(self, config, package) + + def unpack(self): + info('Unpacking package sources...', 3) + + zipfile = ZipFile(self.cachefile) + zipfile.extractall(join(self.workdir, 'File')) + zipfile.close() + + def build(self): + info('Creating .wxi include file...', 3) + + sourcedir = 'var.%s_sourcedir' % self.package.get('id') + + logfile = join(self.workdir, 'heat.log') + file = open(logfile, 'w') + + process = Popen([WIX_HEAT, + '-nologo', + 'dir', # harvest a directory + join(self.workdir, 'File'), # from the directory where we extracted our source package + '-dr', # set directory reference to root directories + 'TARGETDIR', # to TARGETDIR + '-gg', # generate guids now + '-scom', # suppress COM elements + '-sfrag', # suppress fragments + '-srd', # suppress harvesting the root directory as an element + '-sreg', # suppress registry harvesting + '-svb6', # suppress VB6 COM elements + '-template', # set template to use + 'product', # to product + '-var', # substitute File/@Source="SourceDir" with a preprocessor variable + sourcedir, # thus SourceDir will become "$(var.xxx_sourcedir)\myfile.txt" + '-out', # set output file + self.wxsfile], # to our wxsfile + stdout=file, + stderr=file, + universal_newlines=True) + file.close() + + if process.wait() != 0: + info('WiX "heat" reported error(s). Please review "%s".' % logfile, 4) + + root = etree.parse(self.wxsfile).getroot() + product = root.find('{%s}Product' % WIX_NAMESPACE) + include = product.find('{%s}Directory' % WIX_NAMESPACE) + + # deepcopy the Wix/Product/Directory node into a new tree + newroot = etree.Element('{%s}Include' % WIX_NAMESPACE, nsmap=WIX_NSMAP) + targetdir = etree.SubElement(newroot, + '{%s}DirectoryRef' % WIX_NAMESPACE, + Id='TARGETDIR') + libdir = etree.SubElement(targetdir, + '{%s}Directory' % WIX_NAMESPACE, + Name='Lib', Id='Lib') + spdir = etree.SubElement(libdir, + '{%s}Directory' % WIX_NAMESPACE, + Name='site-packages', Id='site_packages') + gtkdir = etree.SubElement(spdir, + '{%s}Directory' % WIX_NAMESPACE, + Name='gtk-2.0', Id='gtk_2.0') + rtdir = etree.SubElement(gtkdir, + '{%s}Directory' % WIX_NAMESPACE, + Name='runtime', Id='runtime') + + for child in include: + rtdir.append(deepcopy(child)) + + self.include = newroot + + def transform(self): + info('Creating "TARGETDIR%s" and "TARGETDIRX"' % PYTHON_FULLVERSION, 4) + self.transform_create_targetdirs(self.include) + + info('Transforming "Id" attributes...', 4) + self.transform_id(self.include) + + info ('Transforming variables...', 4) + self.transform_variables(self.include) + + def transform_variables(self, element): + pi = etree.ProcessingInstruction('define', '%s_sourcedir = "%s"' % (self.package.get('id'), join(self.workdir, 'File'))) + element.insert(0, pi) + + +def main(): + start = datetime.now() + + builder = Builder() + builder.run() + + end = datetime.now() + minutes, seconds = divmod((end - start).seconds, 60) + + info('Builder finished in %s minutes %s seconds' % (minutes, seconds)) + + +if __name__ == '__main__': + main() diff --git a/bin/uuidgen.py b/bin/uuidgen.py new file mode 100644 index 0000000..16aff19 --- /dev/null +++ b/bin/uuidgen.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +import uuid + + +if __name__ == '__main__': + print '{%s}' % str(uuid.uuid4()).upper() diff --git a/etc/2.22.0.0-win32.template.wxs b/etc/2.22.0.0-win32.template.wxs new file mode 100644 index 0000000..a880a5f --- /dev/null +++ b/etc/2.22.0.0-win32.template.wxs @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NEWPRODUCTFOUND + PYTHON.MACHINE + PYTHON.USER + + + + + NOT Installed + NOT Installed + + + + + + + + + + NOT Installed + + + + VersionNT + VersionNT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VersionNT + VersionNT + + + + + + + + + + + + + NEWPRODUCTFOUND + PYTHON.MACHINE + PYTHON.USER + + + + + Not Privileged or Windows9x or Installed + Privileged and not Windows9x and not Installed + Not Installed + Installed AND NOT RESUME AND NOT Preselected + + + + + NOT Installed + NOT Installed + + + + + + + + + + + + + + + + + Searching for installed applications + Binding executables + Searching for qualifying products + Copying new files + Copying network install files + Computing space requirements + Computing space requirements + Computing space requirements + Validating install + Creating shortcuts + Publishing assembly information + Publishing Qualified Components + Publishing Product Features + Publishing product information + Registering Class servers + Registering extension servers + Registering MIME info + Registering program identifiers + Allocating registry space + Creating folders + Deleting services + Creating duplicate files + Searching for related applications + Installing ODBC components + Installing new services + Evaluating launch conditions + Migrating feature states from related applications + Moving files + Patching files + Updating component registration + Registering COM+ Applications and Components + Registering fonts + Registering product + Registering type libraries + Registering user + Removing duplicated files + Updating environment strings + Removing applications + Removing files + Removing folders + Removing INI files entries + Removing ODBC components + Removing system registry values + Removing shortcuts + Searching for qualifying products + Registering modules + Unregistering modules + Initializing ODBC directories + Starting services + Stopping services + Unpublishing assembly information + Unpublishing Qualified Components + Unpublishing Product Features + Unregister Class servers + Unregistering COM+ Applications and Components + Unregistering extension servers + Unregistering fonts + Unregistering MIME info + Unregistering program identifiers + Unregistering type libraries + Updating environment strings + Writing INI files values + Writing system registry values + Advertising application + Generating script operations for action: + Installing system catalog + Rolling back action: + Removing backup files + Removing moved files + Unpublishing product information + + + + + + + + + + + + + + bytes + GB + KB + MB + Entire feature will be unavailable + Feature will be installed when required + Entire feature will be installed to run from CD + Entire feature will be installed on local hard drive + Entire feature will be installed to run from network + Will be installed to run from CD + Will be installed on local hard drive + Will be installed to run from network + Gathering required information... + This feature will remain uninstalled + This feature will be set to be installed when required + This feature will be installed to run from CD + This feature will be installed on the local hard drive + This feature will be installed to run from the network + This feature will become unavailable + Will be installed when required + This feature will be available to run from CD + This feature will be installed on your local hard drive + This feature will be available to run from the network + This feature will be uninstalled completely, you won't be able to run it from CD + This feature will change from run from CD state to set to be installed when required + This feature will remain to be run from CD + This feature will change from run from CD state to be installed on the local hard drive + This feature frees up [1] on your hard drive. + This feature requires [1] on your hard drive. + Compiling cost for this feature... + This feature will be completely removed + This feature will be removed from your local hard drive, but will be set to be installed when required + This feature will be removed from your local hard drive, but will be still available to run from CD + This feature will remain on you local hard drive + This feature will be removed from your local hard drive, but will be still available to run from the network + This feature will be uninstalled completely, you won't be able to run it from the network + This feature will change from run from network state to set to be installed when required + This feature will change from run from network state to be installed on the local hard drive + This feature will remain to be run from the network + This feature frees up [1] on your hard drive. It has [2] of [3] subfeatures selected. The subfeatures free up [4] on your hard drive. + This feature frees up [1] on your hard drive. It has [2] of [3] subfeatures selected. The subfeatures require [4] on your hard drive. + This feature requires [1] on your hard drive. It has [2] of [3] subfeatures selected. The subfeatures free up [4] on your hard drive. + This feature requires [1] on your hard drive. It has [2] of [3] subfeatures selected. The subfeatures require [4] on your hard drive. + Time remaining: {[1] minutes }{[2] seconds} + Available + Difference + Required + Disk Size + Volume + + + 1 + + + + + + + + + + + + + + + 1 + + + + + + + + + + + 1 + + + + + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + + + + + + + + + 1 + + + + + + + + + WhichUsers="ALL" + 1 + + + 1 + + + + + + + 1 + + + FEATURE_SELECTED AND &PythonX=3 + FEATURE_SELECTED AND &PythonX=3 + FEATURE_SELECTED AND &PythonX<>3 + FEATURE_SELECTED AND &PythonX<>3 + + + FEATURE_SELECTED AND &PythonX=3 + FEATURE_SELECTED AND &PythonX=3 + FEATURE_SELECTED AND &PythonX<>3 + FEATURE_SELECTED AND &PythonX<>3 + + + + + 1 + FEATURE_SELECTED AND &Python$(var.PythonVersion)=3 + FEATURE_SELECTED AND &PythonX=3 + 1 + 1 + + + 1 + + + + + + + + + + MaintenanceForm_Action="Repair" + MaintenanceForm_Action="Repair" + MaintenanceForm_Action="Repair" + MaintenanceForm_Action="Repair" + MaintenanceForm_Action="Remove" + MaintenanceForm_Action="Remove" + MaintenanceForm_Action="Remove" + MaintenanceForm_Action="Remove" + MaintenanceForm_Action<>"Change" + + + 1 + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + 1 + + + 1 + + + + + + 1 + + + + + + + + + + 1 + + + + + + + + NOT TARGETDIR$(var.PythonVersion) + + + + + + + + + + diff --git a/etc/2.22.0.0-win32.xml b/etc/2.22.0.0-win32.xml new file mode 100644 index 0000000..138a233 --- /dev/null +++ b/etc/2.22.0.0-win32.xml @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/var/overlays/2.22.0.0/gtk_runtime/__init__.py b/var/overlays/2.22.0.0/gtk_runtime/__init__.py new file mode 100644 index 0000000..f9858f6 --- /dev/null +++ b/var/overlays/2.22.0.0/gtk_runtime/__init__.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + + +import os +import sys + +from ctypes import windll +from ctypes import cdll +from ctypes.util import find_msvcrt + + +verbose = sys.flags.verbose + + +def _putenv(name, value): + ''' + :param name: environment variable name + :param value: environment variable value + + On Microsoft Windows, starting from Python 2.4, os.environ changes only work + within Python and no longer apply to low level C library code within the + same process. This function calls various Windows functions to force the + environment variable up to the C runtime. + ''' + + # Propagate new value to Windows (so SysInternals Process Explorer sees it) + try: + result = windll.kernel32.SetEnvironmentVariableW(name, value) + if result == 0: raise Warning + except Exception as inst: + if verbose: + sys.stderr.write('gtk+-runtime: kernel32.SetEnvironmentVariableW failed\n') + sys.stderr.flush() + + # Propagate new value to msvcrt (used by gtk+ runtime) + try: + result = cdll.msvcrt._putenv('%s=%s' % (name, value)) + if result == -1: raise Warning + except Exception as inst: + if verbose: + sys.stderr.write('gtk+-runtime: msvcrt._putenv failed\n') + sys.stderr.flush() + + # Propagate new value to whatever c runtime is used by python + try: + result = cdll.LoadLibrary(find_msvcrt())._putenv('%s=%s' % (name, value)) + if result == -1: raise Warning + except Exception as inst: + if verbose: + sys.stderr.write('gtk+-runtime: python msvcr?._putenv failed\n') + sys.stderr.flush() + + +if sys.platform == 'win32': + runtime = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bin')) + path = os.environ['PATH'].split(';') + + if verbose: + sys.stderr.write('gtk+-runtime: original PATH=%s\n' % path) + sys.stderr.flush() + + path.insert(1, runtime) + + if verbose: + sys.stderr.write('gtk+-runtime: modified PATH=%s\n' % path) + sys.stderr.flush() + + _putenv('PATH', ';'.join(path)) diff --git a/var/overlays/2.22.0.0/gtk_runtime/__init__.pyc b/var/overlays/2.22.0.0/gtk_runtime/__init__.pyc new file mode 100644 index 0000000..e69de29 diff --git a/var/overlays/2.22.0.0/gtk_runtime/bin/reconfig.cmd b/var/overlays/2.22.0.0/gtk_runtime/bin/reconfig.cmd new file mode 100644 index 0000000..e0f24b2 --- /dev/null +++ b/var/overlays/2.22.0.0/gtk_runtime/bin/reconfig.cmd @@ -0,0 +1,5 @@ +@echo off + +gdk-pixbuf-query-loaders.exe > ..\etc\gtk-2.0\gdk-pixbuf.loaders +gtk-query-immodules-2.0.exe > ..\etc\gtk-2.0\gtk.immodules +pango-querymodules.exe > ..\etc\pango\pango.modules diff --git a/var/overlays/2.22.0.0/gtk_runtime/etc/gtk-2.0/gtkrc b/var/overlays/2.22.0.0/gtk_runtime/etc/gtk-2.0/gtkrc new file mode 100644 index 0000000..74e1e4d --- /dev/null +++ b/var/overlays/2.22.0.0/gtk_runtime/etc/gtk-2.0/gtkrc @@ -0,0 +1,2 @@ +gtk-theme-name = "MS-Windows" +gtk-icon-theme-name = "Tango" diff --git a/var/overlays/2.22.0.0/gtk_runtime/etc/pango/pango.aliases b/var/overlays/2.22.0.0/gtk_runtime/etc/pango/pango.aliases new file mode 100644 index 0000000..e7bf8cf --- /dev/null +++ b/var/overlays/2.22.0.0/gtk_runtime/etc/pango/pango.aliases @@ -0,0 +1,8 @@ +courier = "courier new" + +tahoma = "tahoma,lucida sans unicode,browallia new,mingliu,simhei,gulimche,ms gothic,kartika,latha,mangal,raavi" +sans = "arial,lucida sans unicode,browallia new,mingliu,simhei,gulimche,ms gothic,kartika,latha,mangal,raavi" +serif = "times new roman,angsana new,mingliu,simsun,gulimche,ms gothic,kartika,latha,mangal,raavi" +mono = "courier new,lucida console,courier monothai,mingliu,simsun,gulimche,ms gothic,kartika,latha,mangal,raavi" +monospace = "courier new,lucida console,courier monothai,mingliu,simsun,gulimche,ms gothic,kartika,latha,mangal,raavi" +"segoe ui" = "segoe ui,meiryo,malgun gothic,microsoft jhenghei,microsoft yahei,gisha,leelawadee" diff --git a/var/overlays/2.22.0.0/pygobject/pygtk.pth b/var/overlays/2.22.0.0/pygobject/pygtk.pth new file mode 100644 index 0000000..9b18d1a --- /dev/null +++ b/var/overlays/2.22.0.0/pygobject/pygtk.pth @@ -0,0 +1,3 @@ +gtk-2.0 + +import runtime