1# -*- coding: ascii -*-
2#
3# Copyright 2007, 2008, 2009, 2010, 2011
4# Andr\xe9 Malo or his licensors, as applicable
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17"""
18===================
19 C extension tools
20===================
21
22C extension tools.
23"""
24__author__ = u"Andr\xe9 Malo"
25__docformat__ = "restructuredtext en"
26__test__ = False
27
28from distutils import core as _core
29from distutils import errors as _distutils_errors
30import os as _os
31import posixpath as _posixpath
32import shutil as _shutil
33import tempfile as _tempfile
34
35from _setup import commands as _commands
36from _setup.util import log
37
38
39def _install_finalizer(installer):
40    if installer.without_c_extensions:
41        installer.distribution.ext_modules = []
42
43def _build_finalizer(builder):
44    if builder.without_c_extensions:
45        builder.extensions = []
46
47
48class Extension(_core.Extension):
49    """
50    Extension with prerequisite check interface
51
52    If your check is cacheable (during the setup run), override
53    `cached_check_prerequisites`, `check_prerequisites` otherwise.
54
55    :IVariables:
56      `cached_check` : ``bool``
57        The cached check result
58    """
59    cached_check = None
60
61    def __init__(self, *args, **kwargs):
62        """ Initialization """
63        if kwargs.has_key('depends'):
64            self.depends = kwargs['depends'] or []
65        else:
66            self.depends = []
67        _core.Extension.__init__(self, *args, **kwargs)
68
69        # add include path
70        included = _posixpath.join('_setup', 'include')
71        if included not in self.include_dirs:
72            self.include_dirs.append(included)
73
74        # add cext.h to the dependencies
75        cext_h = _posixpath.join(included, 'cext.h')
76        if cext_h not in self.depends:
77            self.depends.append(cext_h)
78
79        _commands.add_option('install_lib', 'without-c-extensions',
80            help_text='Don\'t install C extensions',
81            inherit='install',
82        )
83        _commands.add_finalizer('install_lib', 'c-extensions',
84            _install_finalizer
85        )
86        _commands.add_option('build_ext', 'without-c-extensions',
87            help_text='Don\'t build C extensions',
88            inherit=('build', 'install_lib'),
89        )
90        _commands.add_finalizer('build_ext', 'c-extensions', _build_finalizer)
91
92    def check_prerequisites(self, build):
93        """
94        Check prerequisites
95
96        The check should cover all dependencies needed for the extension to
97        be built and run. The method can do the following:
98
99        - return a false value: the extension will be built
100        - return a true value: the extension will be skipped. This is useful
101          for optional extensions
102        - raise an exception. This is useful for mandatory extensions
103
104        If the check result is cacheable (during the setup run), override
105        `cached_check_prerequisites` instead.
106
107        :Parameters:
108          `build` : `BuildExt`
109            The extension builder
110
111        :Return: Skip the extension?
112        :Rtype: ``bool``
113        """
114        if self.cached_check is None:
115            log.debug("PREREQ check for %s" % self.name)
116            self.cached_check = self.cached_check_prerequisites(build)
117        else:
118            log.debug("PREREQ check for %s (cached)" % self.name)
119        return self.cached_check
120
121    def cached_check_prerequisites(self, build):
122        """
123        Check prerequisites
124
125        The check should cover all dependencies needed for the extension to
126        be built and run. The method can do the following:
127
128        - return a false value: the extension will be built
129        - return a true value: the extension will be skipped. This is useful
130          for optional extensions
131        - raise an exception. This is useful for mandatory extensions
132
133        If the check result is *not* cacheable (during the setup run),
134        override `check_prerequisites` instead.
135
136        :Parameters:
137          `build` : `BuildExt`
138            The extension builder
139
140        :Return: Skip the extension?
141        :Rtype: ``bool``
142        """
143        # pylint: disable = W0613
144        log.debug("Nothing to check for %s!" % self.name)
145        return False
146
147
148class ConfTest(object):
149    """
150    Single conftest abstraction
151
152    :IVariables:
153      `_tempdir` : ``str``
154        The tempdir created for this test
155
156      `src` : ``str``
157        Name of the source file
158
159      `target` : ``str``
160        Target filename
161
162      `compiler` : ``CCompiler``
163        compiler instance
164
165      `obj` : ``list``
166        List of object filenames (``[str, ...]``)
167    """
168    _tempdir = None
169
170    def __init__(self, build, source):
171        """
172        Initialization
173
174        :Parameters:
175          `build` : ``distuils.command.build_ext.build_ext``
176            builder instance
177
178          `source` : ``str``
179            Source of the file to compile
180        """
181        self._tempdir = tempdir = _tempfile.mkdtemp()
182        src = _os.path.join(tempdir, 'conftest.c')
183        fp = open(src, 'w')
184        try:
185            fp.write(source)
186        finally:
187            fp.close()
188        self.src = src
189        self.compiler = compiler = build.compiler
190        self.target = _os.path.join(tempdir, 'conftest')
191        self.obj = compiler.object_filenames([src], output_dir=tempdir)
192
193    def __del__(self):
194        """ Destruction """
195        self.destroy()
196
197    def destroy(self):
198        """ Destroy the conftest leftovers on disk """
199        tempdir, self._tempdir = self._tempdir, None
200        if tempdir is not None:
201            _shutil.rmtree(tempdir)
202
203    def compile(self, **kwargs):
204        """
205        Compile the conftest
206
207        :Parameters:
208          `kwargs` : ``dict``
209            Optional keyword parameters for the compiler call
210
211        :Return: Was the compilation successful?
212        :Rtype: ``bool``
213        """
214        kwargs['output_dir'] = self._tempdir
215        try:
216            self.compiler.compile([self.src], **kwargs)
217        except _distutils_errors.CompileError:
218            return False
219        return True
220
221    def link(self, **kwargs):
222        r"""
223        Link the conftest
224
225        Before you can link the conftest objects they need to be `compile`\d.
226
227        :Parameters:
228          `kwargs` : ``dict``
229            Optional keyword parameters for the linker call
230
231        :Return: Was the linking successful?
232        :Rtype: ``bool``
233        """
234        try:
235            self.compiler.link_executable(self.obj, self.target, **kwargs)
236        except _distutils_errors.LinkError:
237            return False
238        return True
239
240    def pipe(self, mode="r"):
241        r"""
242        Execute the conftest binary and connect to it using a pipe
243
244        Before you can pipe to or from the conftest binary it needs to
245        be `link`\ed.
246
247        :Parameters:
248          `mode` : ``str``
249            Pipe mode - r/w
250
251        :Return: The open pipe
252        :Rtype: ``file``
253        """
254        return _os.popen(self.compiler.executable_filename(self.target), mode)
255