#!/usr/bin/env python2.4

##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

"""
Distutils setup for Zope

  In-place building

    This builds extension modules in-place, much like build_extensions.py
    does.  Use 'setup.py' like this::

      python setup.py build_ext -i

  Installation

    This builds extension modules, compiles python modules, and installs
    everything needed to support Zope instances in the directory of
    your choosing.  For example, to use '/usr/local/lib/zope'::

      python setup.py install \
        --home=/usr/local/lib/zope \
        --install-platlib=/usr/local/lib/zope \
        --install-purelib=/usr/local/lib/zope
"""

import glob
import os
import sys
import shutil

import distutils.core

# Override install_data to install into module directories, and to support
# globbing on data_files.

from distutils.command.install import install
from distutils.command.install_data import install_data
from distutils.util import convert_path

where = os.path.split(__file__)[0]
sys.path.insert(0, os.path.join(where, 'inst'))
import versions
del sys.path[0]

ZOPE_VERSION = '%s%s-%s' % (versions.ZOPE_MAJOR_VERSION,  versions.ZOPE_MINOR_VERSION, versions.VERSION_RELEASE_TAG)

class ZopeInstallData(install_data):
    def finalize_options(self):
        self.set_undefined_options('install',
                                   ('install_purelib', 'install_dir'),
                                   ('root', 'root'),
                                   ('force', 'force'),
                                  )

    def run(self):
        self.mkpath(self.install_dir)
        for f in self.data_files:
            if isinstance(f, str):
                # it's a simple file, so copy it
                f = convert_path(f)
                gl = glob.glob(f)
                if len(gl) == 0:
                    raise distutils.core.DistutilsFileError, \
                          "can't copy '%s': no matching files" % f
                for g in gl:
                    if os.path.isfile(g):
                        if self.warn_dir:
                            self.warn("setup script did not provide a "
                                      "directory for '%s' -- installing "
                                      "right in '%s'" %
                                      (g, self.install_dir))
                        (out, _) = self.copy_file(g, self.install_dir)
                        self.outfiles.append(out)
            else:
                # it's a tuple with path to install to and a list of files
                dir = convert_path(f[0])
                if not os.path.isabs(dir):
                    dir = os.path.join(self.install_dir, dir)
                elif self.root:
                    dir = change_root(self.root, dir)
                self.mkpath(dir)
                for data in f[1]:
                    data = convert_path(data)
                    gl = glob.glob(data)
                    if len(gl) == 0:
                        raise distutils.core.DistutilsFileError, \
                              "can't copy '%s': no matching files" % data
                    for g in gl:
                        if os.path.isfile(g):
                            (out, _) = self.copy_file(g, dir)
                            self.outfiles.append(out)

# We create a custom "install scheme" that works the same way on all
# platforms.  We do this in order to prevent distutils from trying to
# guess where to put our files on a per-platform basis.

ZOPE_INSTALL_SCHEME = {
    'purelib': '$base/lib/python',
    'platlib': '$base/lib/python',
    'headers': '$base/lib/python',
    'scripts': '$base/bin',
    'data'   : '$base/lib/python',
    }

class ZopeInstall(install):
    # give distutils install step knowledge about build file placement options
    user_options = install.user_options + [
        ('build-base=', None, 'base directory for build library'),
        ('build-lib=', None, 'build directory for all distribution'),
        ('build-scripts=', None, 'build directory for scripts'),
        ('build-temp=', None, 'temporary build directory'),
        ]
    build_scripts = None
    build_temp = None

    def run(self):
        """ Override run to pass along build location info so
        we can use custom build directories """
        build = self.distribution.get_command_obj('build')
        build.build_base = self.build_base
        build.build_lib = self.build_lib
        build.build_scripts = self.build_scripts
        build.build_temp = self.build_temp

        install.run(self)

    def select_scheme(self, name):
        """
        Override the default platform installation schemes, ignoring whatever
        'name' is passed in.  For our purposes, we want to put all library,
        header, and data into [install_base]/lib/python.  Comment
        this method out to achieve distutils-standard platform-specific
        behavior for 'setup.py install'.  This is most useful if you set the
        [install-base] by using the '--prefix' or '--home' flags on the
        setup.py install command line.  Otherwise, all Zope software
        will probably be installed to your Python's 'lib/python' directory.
        """
        scheme = ZOPE_INSTALL_SCHEME
        import distutils.command.install
        for key in distutils.command.install.SCHEME_KEYS:
            attrname = 'install_' + key
            if getattr(self, attrname) is None:
                setattr(self, attrname, scheme[key])

class ZopeDistribution(distutils.core.Distribution):
    def __init__(self, attrs):
        distutils.core.Distribution.__init__(self, attrs)
        self.cmdclass["install"] = ZopeInstall
        self.cmdclass["install_data"] = ZopeInstallData

# presumes this script lives in the base dir
BASE_DIR=os.path.dirname(os.path.abspath(sys.argv[0]))

AUTHOR = 'Zope Corporation and Contributors'

# Most modules are in lib/python in the source distribution
PACKAGES_ROOT = os.path.join(BASE_DIR, 'lib', 'python')
os.chdir(PACKAGES_ROOT)


# Most of this is ripped from the Zope 3 setup.py.

from distutils import dir_util
from distutils.command.build import build as buildcmd
from distutils.command.build_ext import build_ext
from distutils.command.install_lib import install_lib as installcmd
from distutils.core import setup
from distutils.dist import Distribution
from distutils.extension import Extension


# This class serves multiple purposes.  It walks the file system looking for
# auxiliary files that distutils doesn't install properly, and it actually
# copies those files (when hooked into by distutils).  It also walks the file
# system looking for candidate packages for distutils to install as normal.
# The key here is that the package must have an __init__.py file.
class Finder:
    def __init__(self, ignore, prefix):
        self._files = []
        self._pkgs = {}
        self._ignore = ignore
        # We're finding packages in lib/python in the source dir, but we're
        # copying them directly under build/lib.<plat>.  So we need to lop off
        # the prefix when calculating the package names from the file names.
        self._plen = len(prefix) + 1

    def visit(self, dir, files):
        # First see if this is one of the packages we want to add, or if
        # we're really skipping this package.
        if '__init__.py' in files:
            aspkg = dir[self._plen:].replace(os.sep, '.')
            self._pkgs[aspkg] = True
            ignore = ('.py',) + self._ignore
        else:
            ignore = self._ignore
        # Add any extra files we're interested in
        for file in files:
            base, ext = os.path.splitext(file)
            if ext not in ignore:
                self._files.append(os.path.join(dir, file))

    def copy_files(self, cmd, outputbase):
        for file in self._files:
            dest = os.path.join(outputbase, file[self._plen:])
            # Make sure the destination directory exists
            dir = os.path.dirname(dest)
            if not os.path.exists(dir):
                dir_util.mkpath(dir)
            cmd.copy_file(file, dest)

    def get_packages(self):
        return self._pkgs.keys()

def remove_stale_bytecode(arg, dirname, names):
    names = map(os.path.normcase, names)
    for name in names:
        if name.endswith(".pyc") or name.endswith(".pyo"):
            srcname = name[:-1]
            if srcname not in names:
                fullname = os.path.join(dirname, name)
                print "Removing stale bytecode file", fullname
                os.unlink(fullname)
#
#   Aliases for directories containing headers, to allow for Zope3 extensions
#   to include headers via stuff like '#include "zope.proxy/proxy.h"
#
HEADER_PATH_ALIASES = {'zope.proxy': 'zope/proxy'}

# Create the finder instance, which will be used in lots of places.  `finder'
# is the global we're most interested in.
IGNORE_EXTS = ('.pyc', '.pyo', '.c', '.h', '.so', '.o', '.dll', '.lib', '.obj', '.cfg')
finder = Finder(IGNORE_EXTS, PACKAGES_ROOT)

for dirpath, dirnames, filenames in os.walk(PACKAGES_ROOT):
    if not '.svn' in dirpath:
        finder.visit(dirpath, filenames)
packages = finder.get_packages()

# Distutils hook classes
class MyBuilder(buildcmd):
    def run(self):
        os.path.walk(os.curdir, remove_stale_bytecode, None)
        buildcmd.run(self)
        finder.copy_files(self, self.build_lib)

class MyExtBuilder(build_ext):
    # Override the default build_ext to remove stale bytecodes.
    # Technically, removing bytecode has nothing to do with
    # building extensions, but Zope's the build_ext -i variant
    # is used to build Zope in place.
    #
    # Note that we also create make a copy for the oddball include
    # directories used by some Zope3 extensions.
    def run(self):
        os.path.walk(os.curdir, remove_stale_bytecode, None)
        for k, v in HEADER_PATH_ALIASES.items():
            if os.path.exists(k):
                shutil.rmtree(k)
            ignore = list(IGNORE_EXTS)
            ignore.remove('.h')
            f = Finder(tuple(ignore), v)
            for dirpath, dirnames, filenames in os.walk(v):
                if not '.svn' in dirpath:
                    f.visit(dirpath, filenames)
            f.copy_files(self, k)
        build_ext.run(self)

class MyLibInstaller(installcmd):
    def run(self):
        installcmd.run(self)
        finder.copy_files(self, self.install_dir)

class MyDistribution(Distribution):
    # To control the selection of MyLibInstaller and MyPyBuilder, we
    # have to set it into the cmdclass instance variable, set in
    # Distribution.__init__().
    def __init__(self, *attrs):
        Distribution.__init__(self, *attrs)
        self.cmdclass['install'] = ZopeInstall
        self.cmdclass['build'] = MyBuilder
        self.cmdclass['build_ext'] = MyExtBuilder
        self.cmdclass['install_lib'] = MyLibInstaller


EXTENSIONCLASS_INCLUDEDIRS = ['ExtensionClass', '.']

# All extension modules must be listed here.
ext_modules = [

    # AccessControl
    Extension(name='AccessControl.cAccessControl',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['Acquisition'],
              sources=['AccessControl/cAccessControl.c'],
              depends=['ExtensionClass/ExtensionClass.h',
                       'ExtensionClass/pickle/pickle.c',
                       'Acquisition/Acquisition.h']),

    # BTrees
    Extension(name='BTrees._OOBTree',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
              sources=['BTrees/_OOBTree.c']),
    Extension(name='BTrees._OIBTree',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
              sources=['BTrees/_OIBTree.c']),
    Extension(name='BTrees._IIBTree',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
              define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
              sources=['BTrees/_IIBTree.c']),
    Extension(name='BTrees._IOBTree',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
              define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
              sources=['BTrees/_IOBTree.c']),
    Extension(name='BTrees._IFBTree',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
              define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
              sources=['BTrees/_IFBTree.c']),
    Extension(name='BTrees._fsBTree',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
              define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
              sources=['BTrees/_fsBTree.c']),

    # DocumentTemplate
    Extension(name='DocumentTemplate.cDocumentTemplate',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=['DocumentTemplate/cDocumentTemplate.c']),

    # ExtensionClass
    Extension(name='ExtensionClass._ExtensionClass',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=["ExtensionClass/_ExtensionClass.c"],
              depends=["ExtensionClass/ExtensionClass.h"]),
    Extension(name='Acquisition._Acquisition',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=["Acquisition/_Acquisition.c"],
              depends=["ExtensionClass/ExtensionClass.h",
                       "Acquisition/Acquisition.h"]),
    Extension(name='MethodObject._MethodObject',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=["MethodObject/_MethodObject.c"],
              depends=["ExtensionClass/ExtensionClass.h"]),
    Extension(name='MultiMapping._MultiMapping',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=["MultiMapping/_MultiMapping.c"],
              depends=["ExtensionClass/ExtensionClass.h"]),
    Extension(name='ThreadLock._ThreadLock',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=["ThreadLock/_ThreadLock.c"],
              depends=["ExtensionClass/ExtensionClass.h"]),
    Extension(name='Missing._Missing',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=["Missing/_Missing.c"],
              depends=["ExtensionClass/ExtensionClass.h"]),
    Extension(name='Record._Record',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=["Record/_Record.c"],
              depends=["ExtensionClass/ExtensionClass.h"]),
    Extension(name='ComputedAttribute._ComputedAttribute',
              include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
              sources=["ComputedAttribute/_ComputedAttribute.c"],
              depends=["ExtensionClass/ExtensionClass.h"]),

    # initgroups
    Extension(name='initgroups._initgroups',
              sources=['initgroups/_initgroups.c']),

    # indexes
    Extension(name='Products.PluginIndexes.TextIndex.Splitter.ZopeSplitter.ZopeSplitter',
              sources=['Products/PluginIndexes/TextIndex/Splitter/ZopeSplitter/src/ZopeSplitter.c']),
    Extension(name='Products.PluginIndexes.TextIndex.Splitter.ISO_8859_1_Splitter.ISO_8859_1_Splitter',
              sources=['Products/PluginIndexes/TextIndex/Splitter/ISO_8859_1_Splitter/src/ISO_8859_1_Splitter.c']),
    Extension(name='Products.PluginIndexes.TextIndex.Splitter.UnicodeSplitter.UnicodeSplitter',
              sources=['Products/PluginIndexes/TextIndex/Splitter/UnicodeSplitter/src/UnicodeSplitter.c']),
    Extension(name='Products.ZCTextIndex.stopper',
              sources=['Products/ZCTextIndex/stopper.c']),
    Extension(name='Products.ZCTextIndex.okascore',
              sources=['Products/ZCTextIndex/okascore.c']),

    #ZODB
    Extension(name = 'persistent.cPersistence',
              include_dirs = ['persistent'],
              sources= ['persistent/cPersistence.c',
                        'persistent/ring.c'],
              depends = ['persistent/cPersistence.h',
                         'persistent/ring.h',
                         'persistent/ring.c']
              ),
    Extension(name = 'Persistence._Persistence',
              include_dirs = ['.', 'persistent', 'ExtensionClass'],
              sources = ['Persistence/_Persistence.c'],
              depends = ['persistent/cPersistence.h',
                         'ExtensionClass/ExtensionClass.h']
              ),
    Extension(name = 'persistent.cPickleCache',
              include_dirs = ['persistent'],
              sources= ['persistent/cPickleCache.c',
                        'persistent/ring.c'],
              depends = ['persistent/cPersistence.h',
                         'persistent/ring.h',
                         'persistent/ring.c']
              ),
    Extension(name = 'persistent.TimeStamp',
              sources= ['persistent/TimeStamp.c']
              ),
    Extension(name = 'ZODB.winlock',
              sources = ['ZODB/winlock.c']
              ),

    #zope
    Extension("zope.proxy._zope_proxy_proxy",
              ["zope/proxy/_zope_proxy_proxy.c"],
              include_dirs = [".", "zope/proxy"],
              depends = ["zope/proxy/proxy.h"]),

    Extension("zope.security._proxy", ["zope/security/_proxy.c"],
              include_dirs = [".", "zope/proxy"],
              depends = ["zope/proxy/proxy.h"]),

    Extension("zope.security._zope_security_checker",
              ["zope/security/_zope_security_checker.c"],
              include_dirs = [],
              depends = []),

    Extension("zope.interface._zope_interface_coptimizations",
              ["zope/interface/_zope_interface_coptimizations.c"]),

    Extension("zope.i18nmessageid._zope_i18nmessageid_message",
              ["zope/i18nmessageid/_zope_i18nmessageid_message.c"]),

    Extension("zope.hookable._zope_hookable",
              ["zope/hookable/_zope_hookable.c"]),

    Extension("zope.thread._zope_thread",
              ["zope/thread/_zope_thread.c"]),

    Extension("zope.app.container._zope_app_container_contained",
              ["zope/app/container/_zope_app_container_contained.c"],
              include_dirs = [".",
                              "persistent",
                              "zope/proxy",
                              "zope/app/container"],
              depends = [
                 "persistent/cPersistence.h",
                 "zope/proxy/_zope_proxy_proxy.c",
                 ]),

    ]

# We're using the module docstring as the distutils descriptions.
doclines = __doc__.split("\n")

setup(name='Zope',
      author=AUTHOR,
      version=ZOPE_VERSION,
      maintainer="Zope Corporation",
      maintainer_email="zope-dev@zope.org",
      url = "http://www.zope.org/",
      ext_modules = ext_modules,
      license = "http://www.zope.org/Resources/ZPL",
      platforms = ["any"],
      description = doclines[0],
      long_description = "\n".join(doclines[2:]),
      packages = packages,
      distclass = MyDistribution,
      )

# The rest of these modules live in the root of the source tree
os.chdir(BASE_DIR)

IGNORE_NAMES = (
    'CVS', '.svn', # Revision Control Directories
    )

def skel_visit(skel, dirname, names):
    for ignore in IGNORE_NAMES:
        if ignore in names:
            names.remove(ignore)
    L = []
    for name in names:
        if os.path.isfile(os.path.join(dirname, name)):
            L.append("%s/%s" % (dirname, name))
    skel.append(("../../" + dirname, L))

installed_data_files = [
    ["../../doc", ['doc/*.txt']],
    ["../../bin", ['utilities/README.txt']],
    ]

os.path.walk("skel", skel_visit, installed_data_files)

setup(
    name='Zope',
    author=AUTHOR,

    data_files=installed_data_files,
    scripts=["utilities/mkzeoinstance.py", "utilities/mkzopeinstance.py",
             "utilities/check_catalog.py", "utilities/load_site.py",
             "utilities/requestprofiler.py", "utilities/zpasswd.py",
             "utilities/copyzopeskel.py", "utilities/reindex_catalog.py",
             "utilities/compilezpy.py", "utilities/decompilezpy.py",
             "utilities/ZODBTools/timeout.py", "utilities/ZODBTools/analyze.py",
             "utilities/ZODBTools/checkbtrees.py", "utilities/ZODBTools/fsdump.py",
             "utilities/ZODBTools/fsrefs.py" , "utilities/ZODBTools/fstail.py",
             "utilities/ZODBTools/fstest.py", "utilities/ZODBTools/migrate.py",
             "utilities/ZODBTools/netspace.py", "utilities/ZODBTools/parsezeolog.py",
             "utilities/ZODBTools/repozo.py", "utilities/ZODBTools/space.py",
             "utilities/ZODBTools/timeout.py", "utilities/ZODBTools/zeopack.py",
             "utilities/ZODBTools/zeoqueue.py", "utilities/ZODBTools/zeoreplay.py",
             "utilities/ZODBTools/zeoserverlog.py", "utilities/ZODBTools/zeoup.py",
             "utilities/ZODBTools/zodbload.py",
             "test.py"],
    distclass=ZopeDistribution,
    )
