summaryrefslogtreecommitdiffstats
path: root/Lib/distutils/command/build_ext.py
blob: a3982c1b226f1e4f4bb5303f5b2f59872cfb42e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
"""distutils.command.build_ext

Implements the Distutils 'build_ext' command, for building extension
modules (currently limited to C extensions, should accomodate C++
extensions ASAP)."""

# created 1999/08/09, Greg Ward

__rcsid__ = "$Id$"

import sys, os, string, re
from types import *
from distutils.core import Command
from distutils.ccompiler import new_compiler
from distutils.sysconfig import INCLUDEPY, SO, exec_prefix
from distutils.errors import *


# This is the same as a Python NAME, since we must accept any
# valid module name for the extension name.
extension_name_re = re.compile (r'^[a-zA-Z_][a-zA-Z_0-9]*$')


class BuildExt (Command):
    
    # XXX thoughts on how to deal with complex command-line options like
    # these, i.e. how to make it so fancy_getopt can suck them off the
    # command line and make it look like setup.py defined the appropriate
    # lists of tuples of what-have-you.
    #   - each command needs a callback to process its command-line options
    #   - Command.__init__() needs access to its share of the whole
    #     command line (must ultimately come from
    #     Distribution.parse_command_line())
    #   - it then calls the current command class' option-parsing
    #     callback to deal with weird options like -D, which have to
    #     parse the option text and churn out some custom data
    #     structure
    #   - that data structure (in this case, a list of 2-tuples)
    #     will then be present in the command object by the time
    #     we get to set_final_options() (i.e. the constructor
    #     takes care of both command-line and client options
    #     in between set_default_options() and set_final_options())

    options = [('dir=', 'd',
                "directory for compiled extension modules"),
               ('include-dirs=', 'I',
                "list of directories to search for header files"),
               ('define=', 'D',
                "C preprocessor macros to define"),
               ('undef=', 'U',
                "C preprocessor macros to undefine"),
               ('libs=', 'l',
                "external C libraries to link with"),
               ('library-dirs=', 'L',
                "directories to search for external C libraries"),
               ('rpath=', 'R',
                "directories to search for shared C libraries at runtime"),
               ('link-objects=', 'O',
                "extra explicit link objects to include in the link"),
              ]


    def set_default_options (self):
        self.extensions = None
        self.dir = None
        self.include_dirs = None
        self.define = None
        self.undef = None
        self.libs = None
        self.library_dirs = None
        self.rpath = None
        self.link_objects = None

    def set_final_options (self):
        self.set_undefined_options ('build', ('platdir', 'dir'))

        # Make sure Python's include directories (for Python.h, config.h,
        # etc.) are in the include search path.  We have to roll our own
        # "exec include dir", because the Makefile parsed by sysconfig
        # doesn't have it (sigh).
        py_include = INCLUDEPY         # prefix + "include" + "python" + ver
        exec_py_include = os.path.join (exec_prefix, 'include',
                                        'python' + sys.version[0:3])
        if self.include_dirs is None:
            self.include_dirs = []
        self.include_dirs.insert (0, py_include)
        if exec_py_include != py_include:
            self.include_dirs.insert (0, exec_py_include)


    def run (self):

        self.set_final_options ()

        # XXX we should care about the package we compile extensions
        # into! 

        #(extensions, package) = \
        #    self.distribution.get_options ('ext_modules', 'package')

        # 'self.extensions', as supplied by setup.py, is a list of 2-tuples.
        # Each tuple is simple:
        #    (ext_name, build_info)
        # build_info is a dictionary containing everything specific to
        # building this extension.  (Info pertaining to all extensions
        # should be handled by general distutils options passed from
        # setup.py down to right here, but that's not taken care of yet.)

        if not self.extensions:
            return

        # First, sanity-check the 'self.extensions' list
        self.check_extensions_list (self.extensions)

        # Setup the CCompiler object that we'll use to do all the
        # compiling and linking
        self.compiler = new_compiler (plat=os.environ.get ('PLAT'),
                                      verbose=self.distribution.verbose,
                                      dry_run=self.distribution.dry_run)
        if self.include_dirs is not None:
            self.compiler.set_include_dirs (self.include_dirs)
        if self.define is not None:
            # 'define' option is a list of (name,value) tuples
            for (name,value) in self.define:
                self.compiler.define_macro (name, value)
        if self.undef is not None:
            for macro in self.undef:
                self.compiler.undefine_macro (macro)
        if self.libs is not None:
            self.compiler.set_libraries (self.libs)
        if self.library_dirs is not None:
            self.compiler.set_library_dirs (self.library_dirs)
        if self.rpath is not None:
            self.compiler.set_runtime_library_dirs (self.rpath)
        if self.link_objects is not None:
            self.compiler.set_link_objects (self.link_objects)
                                      
        # Now the real loop over extensions
        self.build_extensions (self.extensions)

            

    def check_extensions_list (self, extensions):

        if type (extensions) is not ListType:
            raise DistutilsValueError, \
                  "'ext_modules' option must be a list of tuples"
        
        for ext in extensions:
            if type (ext) is not TupleType and len (ext) != 2:
                raise DistutilsValueError, \
                      "each element of 'ext_modules' option must be a 2-tuple"

            if not (type (ext[0]) is StringType and
                    extension_name_re.match (ext[0])):
                raise DistutilsValueError, \
                      "first element of each tuple in 'ext_modules' " + \
                      "must be the extension name (a string)"

            if type (ext[1]) is not DictionaryType:
                raise DistutilsValueError, \
                      "second element of each tuple in 'ext_modules' " + \
                      "must be a dictionary"

        # end sanity-check for

    # check_extensions_list ()


    def build_extensions (self, extensions):

        for (extension_name, build_info) in extensions:
            sources = build_info.get ('sources')
            if sources is None or type (sources) is not ListType:
                raise DistutilsValueError, \
                      "in ext_modules option, 'sources' must be present " + \
                      "and must be a list of source filenames"
            
            macros = build_info.get ('macros')
            include_dirs = build_info.get ('include_dirs')
            self.compiler.compile (sources, macros, include_dirs)

            objects = self.compiler.object_filenames (sources)
            extra_objects = build_info.get ('extra_objects')
            if extra_objects:
                objects.extend (extra_objects)
            libraries = build_info.get ('libraries')
            library_dirs = build_info.get ('library_dirs')
            ext_filename = self.extension_filename (extension_name)
            self.compiler.link_shared_object (objects, ext_filename,
                                              libraries, library_dirs, build_info)

    # build_extensions ()


    def extension_filename (self, ext_name):
        return ext_name + SO

# class BuildExt