#!/usr/bin/env python from distutils.core import setup, Extension import distutils.sysconfig from distutils.cmd import Command from distutils import log import sys import os.path from jToolkit import __version__ # py2exe only available on Windows try: import py2exe build_exe = py2exe.build_exe.py2exe Distribution = py2exe.Distribution except ImportError: py2exe = None build_exe = Command join = os.path.join jtoolkitversion = __version__.ver packagesdir = distutils.sysconfig.get_python_lib() sitepackages = packagesdir.replace(sys.prefix + os.sep, '') class fileset(list): """this is a installation list of a set of files from a directory""" def __init__(self, src, dest, destsubdir, exclude=["CVS"]): """creates the fileset by walking through src directory""" self.src = src self.dest = dest self.destsubdir = destsubdir self.exclude = exclude # this calls self.adddirfiles(None, dirname, names) for each subdirectory dirname of self.src os.path.walk(self.src, self.adddirfiles, None) def adddirfiles(self, arg, dirname, names): """adds the files names from dirname to self (which is a list)""" # arg is ignored filenames = [] for name in names: if name in self.exclude: continue filename = join(dirname,name) if not os.path.isdir(filename): filenames.append(filename) if len(filenames) > 0: destsubdirname = dirname.replace(self.src,self.destsubdir,1) destpath = join(self.dest,destsubdirname) self.append((destpath,filenames)) class InnoScript: """class that builds an InnoSetup script""" def __init__(self, name, lib_dir, dist_dir, exe_files = [], other_files = [], install_scripts = [], version = "1.0"): self.lib_dir = lib_dir self.dist_dir = dist_dir if not self.dist_dir.endswith(os.sep): self.dist_dir += os.sep self.name = name self.version = version self.exe_files = [self.chop(p) for p in exe_files] self.other_files = [self.chop(p) for p in other_files] self.install_scripts = install_scripts def getcompilecommand(self): try: import _winreg compile_key = _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, "innosetupscriptfile\\shell\\compile\\command") compilecommand = _winreg.QueryValue(compile_key, "") compile_key.Close() except: compilecommand = "compil32.exe" return compilecommand def chop(self, pathname): """returns the path relative to self.dist_dir""" assert pathname.startswith(self.dist_dir) return pathname[len(self.dist_dir):] def create(self, pathname=None): """creates the InnoSetup script""" if pathname is None: self.pathname = os.path.join(self.dist_dir, self.name + os.extsep + "iss") else: self.pathname = pathname ofi = self.file = open(self.pathname, "w") print >> ofi, "; WARNING: This script has been created by py2exe. Changes to this script" print >> ofi, "; will be overwritten the next time py2exe is run!" print >> ofi, r"[Setup]" print >> ofi, r"AppName=%s" % self.name print >> ofi, r"AppVerName=%s %s" % (self.name, self.version) print >> ofi, r"DefaultDirName={pf}\%s" % self.name print >> ofi, r"DefaultGroupName=%s" % self.name print >> ofi, r"OutputBaseFilename=%s-%s-setup" % (self.name, self.version) print >> ofi print >> ofi, r"[Files]" for path in self.exe_files + self.other_files: print >> ofi, r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion' % (path, os.path.dirname(path)) print >> ofi print >> ofi, r"[Icons]" for path in self.exe_files: if path in self.install_scripts: continue linkname = os.path.splitext(os.path.basename(path))[0] print >> ofi, r'Name: "{group}\%s"; Filename: "{app}\%s"; WorkingDir: "{app}"; Flags: dontcloseonexit' % \ (linkname, path) print >> ofi, 'Name: "{group}\Uninstall %s"; Filename: "{uninstallexe}"' % self.name if self.install_scripts: print >> ofi, r"[Run]" for path in self.install_scripts: print >> ofi, r'Filename: "{app}\%s"; WorkingDir: "{app}"; Parameters: "-install"' % path print >> ofi print >> ofi, r"[UninstallRun]" for path in self.install_scripts: print >> ofi, r'Filename: "{app}\%s"; WorkingDir: "{app}"; Parameters: "-remove"' % path print >> ofi ofi.close() def compile(self): """compiles the script using InnoSetup""" shellcompilecommand = self.getcompilecommand() compilecommand = shellcompilecommand.replace('"%1"', self.pathname) result = os.system(compilecommand) if result: print "Error compiling iss file" print "Opening iss file, use InnoSetup GUI to compile manually" os.startfile(self.pathname) def fix_bdist_rpm(setupfile): """Fixes bdist_rpm to use the given setup filename instead of setup.py""" try: from distutils.command import bdist_rpm build_rpm = bdist_rpm.bdist_rpm except ImportError: return if not hasattr(build_rpm, "_make_spec_file"): return orig_make_spec_file = build_rpm._make_spec_file def fixed_make_spec_file(self): """Generate the text of an RPM spec file and return it as a list of strings (one per line). """ orig_spec_file = orig_make_spec_file(self) return [line.replace("setup.py", setupfile) for line in orig_spec_file] build_rpm._make_spec_file = fixed_make_spec_file def exclude_python_file(excludefile): """Adjusts build_py to not include the given .py file even if it is in a package""" # TODO: complete this, some other commands still need to be adjusted from distutils.command import build_py if hasattr(build_py.build_py, "find_package_modules"): orig_find_package_modules = build_py.build_py.find_package_modules def find_package_modules_with_exclude(*args, **kwargs): """Finds package modules, but excludes excludefile""" orig_package_modules = orig_find_package_modules(*args, **kwargs) return [(package, module_base, filename) for (package, module_base, filename) in orig_package_modules if not filename.endswith(excludefile)] build_py.build_py.find_package_modules = find_package_modules_with_exclude ############# remove_source alterations to distutils ############ def extend_function(orig_function, extended_function): def wrapped_function(*args, **kwargs): result = orig_function(*args, **kwargs) result = extended_function(result, *args, **kwargs) return result return wrapped_function def is_removable (py_file): """Checks whether a given python file should be removed. Can be overridden to only remove selected files; by default always returns True""" return True def map_data_file (data_file): """remaps a data_file (could be a directory) to a different location Can be overridden to rearrange data locations; by default always returns data_file""" return data_file def remove_source (py_files, verbose=1, dry_run=0): """Remove the original source for a collection of Python source files (assuming they have been compiled to either .pyc or .pyo form). 'py_files' is a list of files to remove; any files that don't end in ".py" are silently skipped. If 'verbose' is true, prints out a report of each file. If 'dry_run' is true, doesn't actually do anything that would affect the filesystem. """ for file in py_files: if file[-3:] != ".py": # This lets us be lazy and not filter filenames in # the "install_lib" command. continue if not os.path.exists(file+"c") or os.path.exists(file+"o"): log.warn("compiled file does not exist for %s" % (file)) if os.path.exists(file) and is_removable(file): log.info("removing source file %s" % (file)) if not dry_run: os.remove(file) def bdist_get_inidata_removesource(result, self): return result + "\nremove_source=%d" % (self.remove_source) # in run, set install_lib.remove_source (and compile) appropriately def reinitialize_command_removesource(result, self, command, reinit_subcommands=0): if command == "install_lib": # pass the remove_source argument on to install_lib result.remove_source = self.remove_source return result def make_finalize_options_removesource(command_source): """makes an extender method for getting the removesource option from the given command name""" def finalize_options_removesource(result, self): self.set_undefined_options(command_source, ('remove_source', 'remove_source')) return result return finalize_options_removesource def byte_compile_removesource(self, files): if self.remove_source and not (self.compile or self.optimize > 0): self.compile = 1 self.byte_compile_orig(files) if self.remove_source: remove_source(files, verbose=self.verbose, dry_run=self.dry_run) def get_outputs_removesource(result, self): if self.remove_source: filtered_result = [] for filename in result: if filename.endswith(".pycc"): continue elif filename.endswith(".py"): if not is_removable(filename): filtered_result.append(filename) else: filtered_result.append(filename) return filtered_result else: return result def initialize_remove_source(result, self): self.remove_source = None return result def allow_distutils_remove_source(): """adds the remove_source capabilities to distutils""" from distutils import util util.remove_source = remove_source option = ('remove-source', None, "don't include original .py source files (remove from distribution)") option_passing = {"build_py": "build", "install_lib": "install"} def add_remove_source_option(commandclass): commandclass.user_options.append(option) commandclass.boolean_options.append('remove-source') commandclass.initialize_options = extend_function(commandclass.initialize_options, initialize_remove_source) if hasattr(commandclass, "byte_compile"): commandclass.byte_compile_orig = commandclass.byte_compile commandclass.byte_compile = byte_compile_removesource if commandclass.__name__ in option_passing: finalize_options_removesource = make_finalize_options_removesource(option_passing[commandclass.__name__]) commandclass.finalize_options = extend_function(commandclass.finalize_options, finalize_options_removesource) # bdist_wininst changes from distutils.command import bdist_wininst wininst = bdist_wininst.bdist_wininst add_remove_source_option(wininst) wininst.reinitialize_command = extend_function(wininst.reinitialize_command, reinitialize_command_removesource) wininst.get_inidata = extend_function(wininst.get_inidata, bdist_get_inidata_removesource) # bdist_rpm changes from distutils.command import bdist_rpm add_remove_source_option(bdist_rpm.bdist_rpm) # build changes from distutils.command import build add_remove_source_option(build.build) from distutils.command import build_py add_remove_source_option(build_py.build_py) # install changes from distutils.command import install add_remove_source_option(install.install) from distutils.command import install_lib libinst = install_lib.install_lib add_remove_source_option(libinst) libinst.get_outputs = extend_function(libinst.get_outputs, get_outputs_removesource) class build_installer(build_exe): """distutils class that first builds the exe file(s), then creates a Windows installer using InnoSetup""" description = "create an executable installer for MS Windows using InnoSetup and py2exe" user_options = getattr(build_exe, 'user_options', []) + \ [('install-script=', None, "basename of installation script to be run after installation or before deinstallation")] def initialize_options(self): build_exe.initialize_options(self) self.install_script = None def reinitialize_command(self, command, reinit_subcommands=0): if command == "install_data": install_data = build_exe.reinitialize_command(self, command, reinit_subcommands) install_data.data_files = self.remap_data_files(install_data.data_files) return install_data return build_exe.reinitialize_command(self, command, reinit_subcommands) def remap_data_files(self, data_files): new_data_files = [] for f in data_files: if type(f) in (str, unicode): f = map_data_file(f) else: dir, files = f dir = map_data_file(dir) if dir is None: f = None else: f = dir, files if f is not None: new_data_files.append(f) return new_data_files def run(self): # First, let py2exe do it's work. build_exe.run(self) lib_dir = self.lib_dir dist_dir = self.dist_dir # create the Installer, using the files py2exe has created. exe_files = self.windows_exe_files + self.console_exe_files install_scripts = self.install_script if isinstance(install_scripts, (str, unicode)): install_scripts = [install_scripts] script = InnoScript(self.distribution.metadata.name, lib_dir, dist_dir, exe_files, self.lib_files, version=self.distribution.metadata.version, install_scripts=install_scripts) print "*** creating the inno setup script***" script.create() print "*** compiling the inno setup script***" script.compile() # Note: By default the final setup.exe will be in an Output subdirectory. # the localize directory contains localized files... localizesrc = 'localize' # join('jLogbook','localize') localizedest = sitepackages localizedestsubdir = join('localize') localizefileset = fileset(localizesrc, localizedest, localizedestsubdir) sitejtoolkit = join(sitepackages, 'jToolkit') def get_data_files(*subdir_parts): return fileset(join(sitepackages, *subdir_parts), sitepackages, join(*subdir_parts)) datafiles = get_data_files('jToolkit', 'icons') + \ get_data_files('jToolkit', 'demo', 'tutorial') + \ get_data_files('jToolkit', 'js') initfiles = [(join(sitepackages,'jToolkit'),[join('jToolkit','__init__.py')]), (join(sitepackages,'jToolkit', 'data'),[join('jToolkit', 'data', '__init__.py')]), (join(sitepackages,'jToolkit', 'demo'),[join('jToolkit', 'demo', '__init__.py')]), (join(sitepackages,'jToolkit', 'test'),[join('jToolkit', 'test', '__init__.py')]), (join(sitepackages,'jToolkit', 'web'),[join('jToolkit', 'web', '__init__.py')]), (join(sitepackages,'jToolkit', 'widgets'),[join('jToolkit', 'widgets', '__init__.py')]), (join(sitepackages,'jToolkit', 'xml'),[join('jToolkit', 'xml', '__init__.py')])] setuppackages = ['jToolkit', 'jToolkit.data', 'jToolkit.demo', 'jToolkit.demo.tutorial', 'jToolkit.test', 'jToolkit.web', 'jToolkit.widgets', 'jToolkit.xml'] standarddatafiles = localizefileset + datafiles + initfiles setupdatafiles = standarddatafiles def buildmanifest_in(file): """This writes the required files to a MANIFEST.in file""" print >>file, "# MANIFEST.in: the below autogenerated by setup.py from jToolkit %s" % jtoolkitversion print >>file, "# things needed by translate setup.py to rebuild" print >>file, "graft jToolkit/icons" print >>file, "graft jToolkit/demo/tutorial" print >>file, "graft jToolkit/js" print >>file, "include ChangeLog" print >>file, "include COPYING" print >>file, "include LICENSE" print >>file, "graft jToolkit/js" print >>file, "# MANIFEST.in: the above autogenerated by setup.py from translate %s" % jtoolkitversion def standardsetup(name, version, custompackages=[], customdatafiles=[]): try: manifest_in = open("MANIFEST.in", "w") buildmanifest_in(manifest_in) manifest_in.close() except IOError, e: print >> sys.stderr, "warning: could not recreate MANIFEST.in, continuing anyway. Error was %s" % e fix_bdist_rpm(os.path.basename(__file__)) exclude_python_file(join("jToolkit", "data", "ADODB.py")) dosetup(name, version, setuppackages + custompackages, setupdatafiles + customdatafiles) classifiers = [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License (GPL)", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: OS Independent", "Operating System :: Microsoft :: Windows", "Operating System :: Unix" ] jToolkitBlurb = """jToolkit is a Python web application framework built on modpython and Apache. There is also a simple command line webserver for running applications from. It is aimed at dynamically generated pages rather than mostly-static pages (for which there are templating solutions). Pages can be produced using a variety of widgets. It handles sessions and database connections (and multi-database portability).""" def dosetup(name, version, packages, datafiles): setup(name=name, version=version, license="GNU General Public License (GPL)", description="jToolkit web framework", long_description=jToolkitBlurb, author="St James Software", author_email="info@sjsoft.com", url="http://jtoolkit.sourceforge.net/", platforms=["any"], classifiers=classifiers, packages=packages, data_files=datafiles ) if __name__ == "__main__": standardsetup("jToolkit", jtoolkitversion)