source: orange/setup.py @ 10352:5b8a22072fef

Revision 10352:5b8a22072fef, 26.2 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Do not depend on numpy in setup.py since it might not be installed yet.

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