source: orange/setup.py @ 10351:dc2ce7a256ff

Revision 10351:dc2ce7a256ff, 26.0 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Use setuptools whenever possible

Line 
1#!usr/bin/env python
2
3import os, sys       
4import distutils.core
5try:
6    from setuptools import setup
7    from setuptools.command.install import install
8    have_setuptools = True
9except ImportError:
10    from distutils.core import setup
11    from distutils.command.install import install
12    have_setuptools = False
13
14from distutils.core import Extension
15from distutils.command.build_ext import build_ext
16from distutils.command.install_lib import install_lib
17from distutils.util import convert_path
18from distutils.msvccompiler import MSVCCompiler
19from distutils.unixccompiler import UnixCCompiler
20
21# This is set in setupegg.py
22have_setuptools = getattr(distutils.core, "have_setuptools", have_setuptools) 
23if have_setuptools:
24    setuptools_args = {"zip_safe": False,
25                       "install_requires": ["numpy"],
26                       "extra_requires": ["networkx", "PyQt4", "PyQwt"]
27                      }
28else:
29    setuptools_args = {}
30
31
32import re
33import glob
34
35from subprocess import check_call
36
37import types
38
39from distutils.dep_util import newer_group
40from distutils.file_util import copy_file
41from distutils import log
42
43from distutils.sysconfig import get_python_inc, get_config_var
44
45import numpy
46numpy_include_dir = numpy.get_include()
47python_include_dir = get_python_inc(plat_specific=1)
48
49include_dirs = [python_include_dir, numpy_include_dir, "source/include"]
50
51if sys.platform == "darwin":
52    extra_compile_args = "-fPIC -fpermissive -fno-common -w -DDARWIN".split()
53    extra_link_args = "-headerpad_max_install_names -undefined dynamic_lookup".split()
54elif sys.platform == "win32":
55    extra_compile_args = ["-EHsc"]
56    extra_link_args = []
57elif sys.platform.startswith("linux"):
58    extra_compile_args = "-fPIC -fpermissive -w -DLINUX".split()
59    extra_link_args = ["-Wl,-R$ORIGIN"]   
60else:
61    extra_compile_args = []
62    extra_link_args = []
63       
64class LibStatic(Extension):
65    pass
66
67class PyXtractExtension(Extension):
68    def __init__(self, *args, **kwargs):
69        for name, default in [("extra_pyxtract_cmds", []), ("lib_type", "dynamic")]:
70            setattr(self, name, kwargs.get(name, default))
71            if name in kwargs:   
72                del kwargs[name]
73           
74        Extension.__init__(self, *args, **kwargs)
75       
76class PyXtractSharedExtension(PyXtractExtension):
77    pass
78       
79class pyxtract_build_ext(build_ext):
80    def run_pyxtract(self, ext, dir):
81        original_dir = os.path.realpath(os.path.curdir)
82        log.info("running pyxtract for %s" % ext.name)
83        try:
84            os.chdir(dir)
85            ## we use the commands which are used for building under windows
86            pyxtract_cmds = [cmd.split() for cmd in getattr(ext, "extra_pyxtract_cmds", [])]
87            if os.path.exists("_pyxtract.bat"): 
88                pyxtract_cmds.extend([cmd.split()[1:] for cmd in open("_pyxtract.bat").read().strip().splitlines()])
89            for cmd in pyxtract_cmds:
90                log.info(" ".join([sys.executable] + cmd))
91                check_call([sys.executable] + cmd)
92            if pyxtract_cmds:
93                ext.include_dirs.append(os.path.join(dir, "ppp"))
94                ext.include_dirs.append(os.path.join(dir, "px"))
95
96        finally:
97            os.chdir(original_dir)
98       
99    def finalize_options(self):
100        build_ext.finalize_options(self)
101        # add the build_lib dir and build_temp (for
102        # liborange_include and liborange linking)           
103        if not self.inplace:
104            # for linking with liborange.so (it is in Orange package)
105            self.library_dirs.append(os.path.join(self.build_lib, "Orange"))
106            # for linking with liborange_include.a
107            self.library_dirs.append(self.build_temp)
108        else:
109            # for linking with liborange.so
110            self.library_dirs.append("./Orange") 
111            # for linking with liborange_include.a
112            self.library_dirs.append(self.build_temp)
113       
114    def build_extension(self, ext):
115        if isinstance(ext, LibStatic):
116            self.build_static(ext)
117        elif isinstance(ext, PyXtractExtension):
118            self.build_pyxtract(ext)
119        else:
120            build_ext.build_extension(self, ext)
121           
122        if isinstance(ext, PyXtractSharedExtension):
123            if isinstance(self.compiler, MSVCCompiler):
124                # Copy ${TEMP}/orange/orange.lib to ${BUILD}/orange.lib
125                ext_fullpath = self.get_ext_fullpath(ext.name)
126                # Get the last component of the name
127                ext_name = ext.name.rsplit(".", 1)[-1]
128                libs = glob.glob(os.path.join(self.build_temp, 
129                                              "*", "*", ext_name + ".lib"))
130                if not libs:
131                    log.info("Could not locate library %r in directory %r" \
132                             %(ext_name, self.build_temp))
133                else:
134                    lib = libs[0]
135                    copy_file(lib, os.path.splitext(ext_fullpath)[0] + ".lib")
136            else:
137                # Make lib{name}.so link to {name}.so
138                ext_path = self.get_ext_fullpath(ext.name)
139                ext_path, ext_filename = os.path.split(ext_path)
140                realpath = os.path.realpath(os.curdir)
141                try:
142                    os.chdir(ext_path)
143                    # Get the shared library name
144                    _, name = ext.name.rsplit(".", 1)
145                    lib_filename = self.compiler.library_filename(name, lib_type="shared")
146                    # Create the link
147                    copy_file(ext_filename, lib_filename, link="sym")
148                except OSError, ex:
149                    log.info("failed to create shared library for %s: %s" % (ext.name, str(ex)))
150                finally:
151                    os.chdir(realpath)
152           
153    def build_pyxtract(self, ext):
154        ## mostly copied from build_extension
155        sources = ext.sources
156        if sources is None or type(sources) not in (types.ListType, types.TupleType):
157            raise DistutilsSetupError, \
158                  ("in 'ext_modules' option (extension '%s'), " +
159                   "'sources' must be present and must be " +
160                   "a list of source filenames") % ext.name
161        sources = list(sources)
162       
163        ext_path = self.get_ext_fullpath(ext.name)
164       
165        depends = sources + ext.depends
166        if not (self.force or newer_group(depends, ext_path, 'newer')):
167            log.debug("skipping '%s' extension (up-to-date)", ext.name)
168            return
169        else:
170            log.info("building '%s' extension", ext.name)
171
172        # First, scan the sources for SWIG definition files (.i), run
173        # SWIG on 'em to create .c files, and modify the sources list
174        # accordingly.
175        sources = self.swig_sources(sources, ext)
176       
177        # Run pyxtract in dir this adds ppp and px dirs to include_dirs
178        dir = os.path.commonprefix([os.path.split(s)[0] for s in ext.sources])
179        self.run_pyxtract(ext, dir)
180
181        # Next, compile the source code to object files.
182
183        # XXX not honouring 'define_macros' or 'undef_macros' -- the
184        # CCompiler API needs to change to accommodate this, and I
185        # want to do one thing at a time!
186
187        # Two possible sources for extra compiler arguments:
188        #   - 'extra_compile_args' in Extension object
189        #   - CFLAGS environment variable (not particularly
190        #     elegant, but people seem to expect it and I
191        #     guess it's useful)
192        # The environment variable should take precedence, and
193        # any sensible compiler will give precedence to later
194        # command line args.  Hence we combine them in order:
195        extra_args = ext.extra_compile_args or []
196
197        macros = ext.define_macros[:]
198        for undef in ext.undef_macros:
199            macros.append((undef,))
200
201        objects = self.compiler.compile(sources,
202                                         output_dir=self.build_temp,
203                                         macros=macros,
204                                         include_dirs=ext.include_dirs,
205                                         debug=self.debug,
206                                         extra_postargs=extra_args,
207                                         depends=ext.depends)
208
209        # XXX -- this is a Vile HACK!
210        #
211        # The setup.py script for Python on Unix needs to be able to
212        # get this list so it can perform all the clean up needed to
213        # avoid keeping object files around when cleaning out a failed
214        # build of an extension module.  Since Distutils does not
215        # track dependencies, we have to get rid of intermediates to
216        # ensure all the intermediates will be properly re-built.
217        #
218        self._built_objects = objects[:]
219
220        # Now link the object files together into a "shared object" --
221        # of course, first we have to figure out all the other things
222        # that go into the mix.
223        if ext.extra_objects:
224            objects.extend(ext.extra_objects)
225        extra_args = ext.extra_link_args or []
226
227        # Detect target language, if not provided
228        language = ext.language or self.compiler.detect_language(sources)
229
230        self.compiler.link_shared_object(
231            objects, ext_path,
232            libraries=self.get_libraries(ext),
233            library_dirs=ext.library_dirs,
234            runtime_library_dirs=ext.runtime_library_dirs,
235            extra_postargs=extra_args,
236            export_symbols=self.get_export_symbols(ext),
237            debug=self.debug,
238            build_temp=self.build_temp,
239            target_lang=language)
240       
241       
242    def build_static(self, ext):
243        ## mostly copied from build_extension, changed
244        sources = ext.sources
245        if sources is None or type(sources) not in (types.ListType, types.TupleType):
246            raise DistutilsSetupError, \
247                  ("in 'ext_modules' option (extension '%s'), " +
248                   "'sources' must be present and must be " +
249                   "a list of source filenames") % ext.name
250        sources = list(sources)
251       
252        # Static libs get build in the build_temp directory
253        output_dir = self.build_temp
254        if not os.path.exists(output_dir): #VSC fails if the dir does not exist
255            os.makedirs(output_dir)
256           
257        lib_filename = self.compiler.library_filename(ext.name, lib_type='static', output_dir=output_dir)
258       
259        depends = sources + ext.depends
260        if not (self.force or newer_group(depends, lib_filename, 'newer')):
261            log.debug("skipping '%s' extension (up-to-date)", ext.name)
262            return
263        else:
264            log.info("building '%s' extension", ext.name)
265
266        # First, scan the sources for SWIG definition files (.i), run
267        # SWIG on 'em to create .c files, and modify the sources list
268        # accordingly.
269        sources = self.swig_sources(sources, ext)
270
271        # Next, compile the source code to object files.
272
273        # XXX not honouring 'define_macros' or 'undef_macros' -- the
274        # CCompiler API needs to change to accommodate this, and I
275        # want to do one thing at a time!
276
277        # Two possible sources for extra compiler arguments:
278        #   - 'extra_compile_args' in Extension object
279        #   - CFLAGS environment variable (not particularly
280        #     elegant, but people seem to expect it and I
281        #     guess it's useful)
282        # The environment variable should take precedence, and
283        # any sensible compiler will give precedence to later
284        # command line args.  Hence we combine them in order:
285        extra_args = ext.extra_compile_args or []
286
287        macros = ext.define_macros[:]
288        for undef in ext.undef_macros:
289            macros.append((undef,))
290
291        objects = self.compiler.compile(sources,
292                                         output_dir=self.build_temp,
293                                         macros=macros,
294                                         include_dirs=ext.include_dirs,
295                                         debug=self.debug,
296                                         extra_postargs=extra_args,
297                                         depends=ext.depends)
298
299        # XXX -- this is a Vile HACK!
300        #
301        # The setup.py script for Python on Unix needs to be able to
302        # get this list so it can perform all the clean up needed to
303        # avoid keeping object files around when cleaning out a failed
304        # build of an extension module.  Since Distutils does not
305        # track dependencies, we have to get rid of intermediates to
306        # ensure all the intermediates will be properly re-built.
307        #
308        self._built_objects = objects[:]
309
310        # Now link the object files together into a "shared object" --
311        # of course, first we have to figure out all the other things
312        # that go into the mix.
313        if ext.extra_objects:
314            objects.extend(ext.extra_objects)
315        extra_args = ext.extra_link_args or []
316
317        # Detect target language, if not provided
318        language = ext.language or self.compiler.detect_language(sources)
319       
320        #first remove old library (ar only appends the contents if archive already exists)
321        try:
322            os.remove(lib_filename)
323        except OSError, ex:
324            log.debug("failed to remove obsolete static library %s: %s" %(ext.name, str(ex)))
325
326        # The static library is created in the temp dir, it is used during the compile step only
327        # it should not be included in the final install
328        self.compiler.create_static_lib(
329            objects, ext.name, output_dir,
330            debug=self.debug,
331            target_lang=language)
332       
333    def get_libraries(self, ext):
334        """ Change the 'orange' library name to 'orange_d' if
335        building in debug mode. Using ``get_ext_filename`` to discover if
336        _d postfix is required.
337       
338        """
339        libraries = build_ext.get_libraries(self, ext)
340        if "orange" in libraries and self.debug:
341            filename = self.get_ext_filename("orange")
342            basename = os.path.basename(filename)
343            name, ext = os.path.splitext(basename)
344            if name.endswith("_d"):
345                index = libraries.index("orange")
346                libraries[index] = "orange_d"
347           
348        return libraries
349       
350    if not hasattr(build_ext, "get_ext_fullpath"):
351        #On mac OS X python 2.6.1 distutils does not have this method
352        def get_ext_fullpath(self, ext_name):
353            """Returns the path of the filename for a given extension.
354           
355            The file is located in `build_lib` or directly in the package
356            (inplace option).
357            """
358            import string
359            # makes sure the extension name is only using dots
360            all_dots = string.maketrans('/' + os.sep, '..')
361            ext_name = ext_name.translate(all_dots)
362            fullname = self.get_ext_fullname(ext_name)
363            modpath = fullname.split('.')
364            filename = self.get_ext_filename(ext_name)
365            filename = os.path.split(filename)[-1]
366            if not self.inplace:
367                # no further work needed
368                # returning :
369                #   build_dir/package/path/filename
370                filename = os.path.join(*modpath[:-1] + [filename])
371                return os.path.join(self.build_lib, filename)
372            # the inplace option requires to find the package directory
373            # using the build_py command for that
374            package = '.'.join(modpath[0:-1])
375            build_py = self.get_finalized_command('build_py')
376            package_dir = os.path.abspath(build_py.get_package_dir(package))
377            # returning
378            #   package_dir/filename
379            return os.path.join(package_dir, filename)
380       
381       
382class my_install_lib(install_lib):
383    """ An command to install orange (preserves liborange.so -> orange.so symlink)
384    """
385    def run(self):
386        install_lib.run(self)
387       
388    def copy_tree(self, infile, outfile, preserve_mode=1, preserve_times=1, preserve_symlinks=1, level=1):
389        """ Run copy_tree with preserve_symlinks=1 as default
390        """ 
391        install_lib.copy_tree(self, infile, outfile, preserve_mode, preserve_times, preserve_symlinks, level)
392       
393    def install(self):
394        """ Copy build_dir to install_dir
395        """
396        # A Hack to unlink liborange.so -> orange.so if it already exists,
397        # because copy_tree fails to overwrite it
398        #
399        liborange = os.path.join(self.install_dir, "Orange", "liborange.so")
400        if self.force and os.path.exists(liborange) and os.path.islink(liborange):
401            log.info("unlinking %s -> %s", liborange, os.path.join(self.install_dir, "orange.so"))
402            os.unlink(liborange)
403           
404        return install_lib.install(self)
405   
406   
407class my_install(install):
408    """ A command to install orange while also creating
409    a .pth path to access the old orng* modules and orange,
410    orangeom etc.
411   
412    """
413    def run(self):
414        install.run(self)
415       
416        # Create a .pth file with a path inside the Orange/orng directory
417        # so the old modules are importable
418        self.path_file, self.extra_dirs = ("Orange-orng-modules", "Orange/orng")
419        self.extra_dirs = convert_path(self.extra_dirs)
420        log.info("creating portal path for orange compatibility.")
421        self.create_path_file()
422        self.path_file, self.extra_dirs = None, None
423       
424           
425def get_source_files(path, ext="cpp", exclude=[]):
426    files = glob.glob(os.path.join(path, "*." + ext))
427    files = [file for file in files if os.path.basename(file) not in exclude]
428    return files
429
430
431include_ext = LibStatic("orange_include",
432                        get_source_files("source/include/"),
433                        include_dirs=include_dirs)
434
435
436if sys.platform == "win32": # ?? mingw/cygwin
437    libraries = ["orange_include"]
438else:
439    libraries = ["stdc++", "orange_include"]
440
441
442import ConfigParser
443config = ConfigParser.RawConfigParser()
444
445config.read(["setup-site.cfg",
446             os.path.expanduser("~/.orange-site.cfg")]
447            )
448
449orange_sources = get_source_files("source/orange/")
450orange_include_dirs = list(include_dirs)
451orange_libraries = list(libraries)
452
453if config.has_option("blas", "library"):
454    # Link external blas library
455    orange_libraries += [config.get("blas", "library")]
456else:
457    orange_sources += get_source_files("source/orange/blas/", "c")
458   
459if config.has_option("R", "library"):
460    # Link external R library (for linpack)
461    orange_libraries += [config.get("R", "library")]
462else:
463    orange_sources += get_source_files("source/orange/linpack/", "c")
464   
465if config.has_option("liblinear", "library"):
466    # Link external LIBLINEAR library
467    orange_libraries += [config.get("liblinear", "library")]
468else:
469    orange_sources += get_source_files("source/orange/liblinear/", "cpp")
470    orange_include_dirs += ["source/orange/liblinear"]
471   
472if config.has_option("libsvm", "library"):
473    # Link external LibSVM library
474    orange_libraries += [config.get("libsvm", "library")]
475else:
476    orange_sources += get_source_files("source/orange/libsvm/", "cpp")
477   
478
479orange_ext = PyXtractSharedExtension("Orange.orange", orange_sources,
480                                      include_dirs=orange_include_dirs,
481                                      extra_compile_args = extra_compile_args + ["-DORANGE_EXPORTS"],
482                                      extra_link_args = extra_link_args,
483                                      libraries=orange_libraries,
484                                      extra_pyxtract_cmds = ["../pyxtract/defvectors.py"],
485                                      )
486
487if sys.platform == "darwin":
488    build_shared_cmd = get_config_var("BLDSHARED")
489    # Dont link liborange.so with orangeom and orangene - MacOS X treats
490    # loadable modules and shared libraries different
491    if "-bundle" in build_shared_cmd.split():
492        shared_libs = libraries
493    else:
494        shared_libs = libraries + ["orange"]
495else:
496    shared_libs = libraries + ["orange"]
497   
498orangeom_sources = get_source_files("source/orangeom/", exclude=["lib_vectors.cpp"])
499orangeom_libraries = list(shared_libs)
500orangeom_include_dirs = list(include_dirs)
501
502if config.has_option("qhull", "library"):
503    # Link external qhull library
504    orangeom_libraries += [config.get("qhull", "library")]
505else:
506    orangeom_sources += get_source_files("source/orangeom/qhull/", "c")
507    orangeom_include_dirs += ["source/orangeom"]
508
509
510orangeom_ext = PyXtractExtension("Orange.orangeom", orangeom_sources,
511                                  include_dirs=orangeom_include_dirs + ["source/orange/"],
512                                  extra_compile_args = extra_compile_args + ["-DORANGEOM_EXPORTS"],
513                                  extra_link_args = extra_link_args,
514                                  libraries=orangeom_libraries,
515                                  )
516
517orangene_ext = PyXtractExtension("Orange.orangene",
518    get_source_files("source/orangene/", exclude=["lib_vectors.cpp"]),
519                                  include_dirs=include_dirs + ["source/orange/"], 
520                                  extra_compile_args = extra_compile_args + ["-DORANGENE_EXPORTS"],
521                                  extra_link_args = extra_link_args,
522                                  libraries=shared_libs,
523                                  )
524
525corn_ext = Extension("Orange.corn", get_source_files("source/corn/"),
526                     include_dirs=include_dirs + ["source/orange/"], 
527                     extra_compile_args = extra_compile_args + ["-DCORN_EXPORTS"],
528                     extra_link_args = extra_link_args,
529                     libraries=libraries
530                     )
531
532statc_ext = Extension("Orange.statc", get_source_files("source/statc/"),
533                      include_dirs=include_dirs + ["source/orange/"], 
534                      extra_compile_args = extra_compile_args + ["-DSTATC_EXPORTS"],
535                      extra_link_args = extra_link_args,
536                      libraries=libraries
537                      )
538
539import fnmatch
540matches = []
541
542#Recursively find '__init__.py's
543for root, dirnames, filenames in os.walk('Orange'): 
544
545  for filename in fnmatch.filter(filenames, '__init__.py'):
546      matches.append(os.path.join(root, filename))
547packages = [os.path.dirname(pkg).replace(os.path.sep, '.') for pkg in matches]
548
549setup(cmdclass={"build_ext": pyxtract_build_ext,
550                "install_lib": my_install_lib,
551                "install": my_install},
552      name ="Orange",
553      version = "2.5a3",
554      description = "Machine learning and interactive data mining toolbox.",
555      author = "Bioinformatics Laboratory, FRI UL",
556      author_email = "orange@fri.uni-lj.si",
557      url = "http://orange.biolab.si",
558      download_url = "https://bitbucket.org/biolab/orange/downloads",
559      packages = packages + ["Orange.OrangeCanvas",
560                             "Orange.OrangeWidgets",
561                             "Orange.OrangeWidgets.Associate",
562                             "Orange.OrangeWidgets.Classify",
563                             "Orange.OrangeWidgets.Data",
564                             "Orange.OrangeWidgets.Evaluate",
565                             "Orange.OrangeWidgets.Prototypes",
566                             "Orange.OrangeWidgets.Regression",
567                             "Orange.OrangeWidgets.Unsupervised",
568                             "Orange.OrangeWidgets.Visualize",
569                             "Orange.OrangeWidgets.Visualize Qt",
570                             "Orange.OrangeWidgets.plot",
571                             "Orange.OrangeWidgets.plot.primitives",
572                             "Orange.doc",
573                             ],
574     
575      package_data = {"Orange": [
576          "OrangeCanvas/icons/*.png",
577          "OrangeCanvas/orngCanvas.pyw",
578          "OrangeCanvas/WidgetTabs.txt",
579          "OrangeWidgets/icons/*.png",
580          "OrangeWidgets/icons/backgrounds/*.png",
581          "OrangeWidgets/report/index.html",
582          "OrangeWidgets/Associate/icons/*.png",
583          "OrangeWidgets/Classify/icons/*.png",
584          "OrangeWidgets/Data/icons/*.png",
585          "OrangeWidgets/Evaluate/icons/*.png",
586          "OrangeWidgets/Prototypes/icons/*.png",
587          "OrangeWidgets/Regression/icons/*.png",
588          "OrangeWidgets/Unsupervised/icons/*.png",
589          "OrangeWidgets/Visualize/icons/*.png",
590          "OrangeWidgets/Visualize/icons/*.png",
591          "OrangeWidgets/Visualize/icons/*.png",
592          "OrangeWidgets/plot/*.gs",
593          "OrangeWidgets/plot/*.vs",
594          "OrangeWidgets/plot/primitives/*.obj",
595          "doc/datasets/*.tab",
596          "orangerc.cfg"]
597                      },
598      ext_modules = [include_ext, orange_ext, orangeom_ext,
599                     orangene_ext, corn_ext, statc_ext],
600      scripts = ["bin/orange-canvas"],
601      license = "GNU General Public License (GPL)",
602      keywords = ["data mining", "machine learning", "artificial intelligence"],
603      classifiers = ["Development Status :: 4 - Beta",
604                     "Programming Language :: Python",
605                     "License :: OSI Approved :: GNU General Public License (GPL)",
606                     "Operating System :: POSIX",
607                     "Operating System :: Microsoft :: Windows",
608                     "Topic :: Scientific/Engineering :: Artificial Intelligence",
609                     "Topic :: Scientific/Engineering :: Visualization",
610                     "Intended Audience :: Education",
611                     "Intended Audience :: Science/Research"
612                     ],
613      long_description="""\
614Orange data mining library
615==========================
616
617Orange is a scriptable environment for fast prototyping of new
618algorithms and testing schemes. It is a collection of Python packages
619that sit over the core library and implement some functionality for
620which execution time is not crucial and which is easier done in Python
621than in C++. This includes a variety of tasks such as attribute subset,
622bagging and boosting, and alike.
623
624Orange also includes a set of graphical widgets that use methods from
625core library and Orange modules. Through visual programming, widgets
626can be assembled together into an application by a visual programming
627tool called Orange Canvas.
628""",
629      **setuptools_args)
630     
631
Note: See TracBrowser for help on using the repository browser.