source: orange/setup.py @ 10349:da7bff0e3b13

Revision 10349:da7bff0e3b13, 25.9 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Use setuptools version of install command when available (fixes failing installation with pip).

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