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