source: orange/setup.py @ 10250:df207ee68f10

Revision 10250:df207ee68f10, 25.8 KB checked in by ales_erjavec, 2 years ago (diff)

Also read ~/.orange-site.cfg file for configuration of 3rd party libraries.

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