1import distutils.command.build_clib as orig
2from distutils.errors import DistutilsSetupError
3from distutils import log
4from setuptools.dep_util import newer_pairwise_group
5
6
7class build_clib(orig.build_clib):
8    """
9    Override the default build_clib behaviour to do the following:
10
11    1. Implement a rudimentary timestamp-based dependency system
12       so 'compile()' doesn't run every time.
13    2. Add more keys to the 'build_info' dictionary:
14        * obj_deps - specify dependencies for each object compiled.
15                     this should be a dictionary mapping a key
16                     with the source filename to a list of
17                     dependencies. Use an empty string for global
18                     dependencies.
19        * cflags   - specify a list of additional flags to pass to
20                     the compiler.
21    """
22
23    def build_libraries(self, libraries):
24        for (lib_name, build_info) in libraries:
25            sources = build_info.get('sources')
26            if sources is None or not isinstance(sources, (list, tuple)):
27                raise DistutilsSetupError(
28                    "in 'libraries' option (library '%s'), "
29                    "'sources' must be present and must be "
30                    "a list of source filenames" % lib_name)
31            sources = list(sources)
32
33            log.info("building '%s' library", lib_name)
34
35            # Make sure everything is the correct type.
36            # obj_deps should be a dictionary of keys as sources
37            # and a list/tuple of files that are its dependencies.
38            obj_deps = build_info.get('obj_deps', dict())
39            if not isinstance(obj_deps, dict):
40                raise DistutilsSetupError(
41                    "in 'libraries' option (library '%s'), "
42                    "'obj_deps' must be a dictionary of "
43                    "type 'source: list'" % lib_name)
44            dependencies = []
45
46            # Get the global dependencies that are specified by the '' key.
47            # These will go into every source's dependency list.
48            global_deps = obj_deps.get('', list())
49            if not isinstance(global_deps, (list, tuple)):
50                raise DistutilsSetupError(
51                    "in 'libraries' option (library '%s'), "
52                    "'obj_deps' must be a dictionary of "
53                    "type 'source: list'" % lib_name)
54
55            # Build the list to be used by newer_pairwise_group
56            # each source will be auto-added to its dependencies.
57            for source in sources:
58                src_deps = [source]
59                src_deps.extend(global_deps)
60                extra_deps = obj_deps.get(source, list())
61                if not isinstance(extra_deps, (list, tuple)):
62                    raise DistutilsSetupError(
63                        "in 'libraries' option (library '%s'), "
64                        "'obj_deps' must be a dictionary of "
65                        "type 'source: list'" % lib_name)
66                src_deps.extend(extra_deps)
67                dependencies.append(src_deps)
68
69            expected_objects = self.compiler.object_filenames(
70                sources,
71                output_dir=self.build_temp,
72            )
73
74            if (
75                newer_pairwise_group(dependencies, expected_objects)
76                != ([], [])
77            ):
78                # First, compile the source code to object files in the library
79                # directory.  (This should probably change to putting object
80                # files in a temporary build directory.)
81                macros = build_info.get('macros')
82                include_dirs = build_info.get('include_dirs')
83                cflags = build_info.get('cflags')
84                self.compiler.compile(
85                    sources,
86                    output_dir=self.build_temp,
87                    macros=macros,
88                    include_dirs=include_dirs,
89                    extra_postargs=cflags,
90                    debug=self.debug
91                )
92
93            # Now "link" the object files together into a static library.
94            # (On Unix at least, this isn't really linking -- it just
95            # builds an archive.  Whatever.)
96            self.compiler.create_static_lib(
97                expected_objects,
98                lib_name,
99                output_dir=self.build_clib,
100                debug=self.debug
101            )
102