Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / python / dist.py
1 """
2 Distutils convenience functionality.
3
4 Don't use this outside of Twisted.
5
6 Maintainer: Christopher Armstrong
7 """
8
9 from distutils.command import build_scripts, install_data, build_ext
10 from distutils.errors import CompileError
11 from distutils import core
12 from distutils.core import Extension
13 import fnmatch
14 import os
15 import platform
16 import sys
17
18
19 twisted_subprojects = ["conch", "lore", "mail", "names",
20                        "news", "pair", "runner", "web",
21                        "words"]
22
23
24
25 class ConditionalExtension(Extension):
26     """
27     An extension module that will only be compiled if certain conditions are
28     met.
29
30     @param condition: A callable of one argument which returns True or False to
31         indicate whether the extension should be built. The argument is an
32         instance of L{build_ext_twisted}, which has useful methods for checking
33         things about the platform.
34     """
35     def __init__(self, *args, **kwargs):
36         self.condition = kwargs.pop("condition", lambda builder: True)
37         Extension.__init__(self, *args, **kwargs)
38
39
40
41 def setup(**kw):
42     """
43     An alternative to distutils' setup() which is specially designed
44     for Twisted subprojects.
45
46     Pass twisted_subproject=projname if you want package and data
47     files to automatically be found for you.
48
49     @param conditionalExtensions: Extensions to optionally build.
50     @type conditionalExtensions: C{list} of L{ConditionalExtension}
51     """
52     return core.setup(**get_setup_args(**kw))
53
54
55 def get_setup_args(**kw):
56     if 'twisted_subproject' in kw:
57         if 'twisted' not in os.listdir('.'):
58             raise RuntimeError("Sorry, you need to run setup.py from the "
59                                "toplevel source directory.")
60         projname = kw['twisted_subproject']
61         projdir = os.path.join('twisted', projname)
62
63         kw['packages'] = getPackages(projdir, parent='twisted')
64         kw['version'] = getVersion(projname)
65
66         plugin = "twisted/plugins/twisted_" + projname + ".py"
67         if os.path.exists(plugin):
68             kw.setdefault('py_modules', []).append(
69                 plugin.replace("/", ".")[:-3])
70
71         kw['data_files'] = getDataFiles(projdir, parent='twisted')
72
73         del kw['twisted_subproject']
74     else:
75         if 'plugins' in kw:
76             py_modules = []
77             for plg in kw['plugins']:
78                 py_modules.append("twisted.plugins." + plg)
79             kw.setdefault('py_modules', []).extend(py_modules)
80             del kw['plugins']
81
82     if 'cmdclass' not in kw:
83         kw['cmdclass'] = {
84             'install_data': install_data_twisted,
85             'build_scripts': build_scripts_twisted}
86
87     if "conditionalExtensions" in kw:
88         extensions = kw["conditionalExtensions"]
89         del kw["conditionalExtensions"]
90
91         if 'ext_modules' not in kw:
92             # This is a workaround for distutils behavior; ext_modules isn't
93             # actually used by our custom builder.  distutils deep-down checks
94             # to see if there are any ext_modules defined before invoking
95             # the build_ext command.  We need to trigger build_ext regardless
96             # because it is the thing that does the conditional checks to see
97             # if it should build any extensions.  The reason we have to delay
98             # the conditional checks until then is that the compiler objects
99             # are not yet set up when this code is executed.
100             kw["ext_modules"] = extensions
101
102         class my_build_ext(build_ext_twisted):
103             conditionalExtensions = extensions
104         kw.setdefault('cmdclass', {})['build_ext'] = my_build_ext
105     return kw
106
107
108 def getVersion(proj, base="twisted"):
109     """
110     Extract the version number for a given project.
111
112     @param proj: the name of the project. Examples are "core",
113     "conch", "words", "mail".
114
115     @rtype: str
116     @returns: The version number of the project, as a string like
117     "2.0.0".
118     """
119     if proj == 'core':
120         vfile = os.path.join(base, '_version.py')
121     else:
122         vfile = os.path.join(base, proj, '_version.py')
123     ns = {'__name__': 'Nothing to see here'}
124     execfile(vfile, ns)
125     return ns['version'].base()
126
127
128 # Names that are exluded from globbing results:
129 EXCLUDE_NAMES = ["{arch}", "CVS", ".cvsignore", "_darcs",
130                  "RCS", "SCCS", ".svn"]
131 EXCLUDE_PATTERNS = ["*.py[cdo]", "*.s[ol]", ".#*", "*~", "*.py"]
132
133
134 def _filterNames(names):
135     """
136     Given a list of file names, return those names that should be copied.
137     """
138     names = [n for n in names
139              if n not in EXCLUDE_NAMES]
140     # This is needed when building a distro from a working
141     # copy (likely a checkout) rather than a pristine export:
142     for pattern in EXCLUDE_PATTERNS:
143         names = [n for n in names
144                  if (not fnmatch.fnmatch(n, pattern))
145                  and (not n.endswith('.py'))]
146     return names
147
148
149 def relativeTo(base, relativee):
150     """
151     Gets 'relativee' relative to 'basepath'.
152
153     i.e.,
154
155     >>> relativeTo('/home/', '/home/radix/')
156     'radix'
157     >>> relativeTo('.', '/home/radix/Projects/Twisted') # curdir is /home/radix
158     'Projects/Twisted'
159
160     The 'relativee' must be a child of 'basepath'.
161     """
162     basepath = os.path.abspath(base)
163     relativee = os.path.abspath(relativee)
164     if relativee.startswith(basepath):
165         relative = relativee[len(basepath):]
166         if relative.startswith(os.sep):
167             relative = relative[1:]
168         return os.path.join(base, relative)
169     raise ValueError("%s is not a subpath of %s" % (relativee, basepath))
170
171
172 def getDataFiles(dname, ignore=None, parent=None):
173     """
174     Get all the data files that should be included in this distutils Project.
175
176     'dname' should be the path to the package that you're distributing.
177
178     'ignore' is a list of sub-packages to ignore.  This facilitates
179     disparate package hierarchies.  That's a fancy way of saying that
180     the 'twisted' package doesn't want to include the 'twisted.conch'
181     package, so it will pass ['conch'] as the value.
182
183     'parent' is necessary if you're distributing a subpackage like
184     twisted.conch.  'dname' should point to 'twisted/conch' and 'parent'
185     should point to 'twisted'.  This ensures that your data_files are
186     generated correctly, only using relative paths for the first element
187     of the tuple ('twisted/conch/*').
188     The default 'parent' is the current working directory.
189     """
190     parent = parent or "."
191     ignore = ignore or []
192     result = []
193     for directory, subdirectories, filenames in os.walk(dname):
194         resultfiles = []
195         for exname in EXCLUDE_NAMES:
196             if exname in subdirectories:
197                 subdirectories.remove(exname)
198         for ig in ignore:
199             if ig in subdirectories:
200                 subdirectories.remove(ig)
201         for filename in _filterNames(filenames):
202             resultfiles.append(filename)
203         if resultfiles:
204             result.append((relativeTo(parent, directory),
205                            [relativeTo(parent,
206                                        os.path.join(directory, filename))
207                             for filename in resultfiles]))
208     return result
209
210
211 def getPackages(dname, pkgname=None, results=None, ignore=None, parent=None):
212     """
213     Get all packages which are under dname. This is necessary for
214     Python 2.2's distutils. Pretty similar arguments to getDataFiles,
215     including 'parent'.
216     """
217     parent = parent or ""
218     prefix = []
219     if parent:
220         prefix = [parent]
221     bname = os.path.basename(dname)
222     ignore = ignore or []
223     if bname in ignore:
224         return []
225     if results is None:
226         results = []
227     if pkgname is None:
228         pkgname = []
229     subfiles = os.listdir(dname)
230     abssubfiles = [os.path.join(dname, x) for x in subfiles]
231     if '__init__.py' in subfiles:
232         results.append(prefix + pkgname + [bname])
233         for subdir in filter(os.path.isdir, abssubfiles):
234             getPackages(subdir, pkgname=pkgname + [bname],
235                         results=results, ignore=ignore,
236                         parent=parent)
237     res = ['.'.join(result) for result in results]
238     return res
239
240
241 def getScripts(projname, basedir=''):
242     """
243     Returns a list of scripts for a Twisted subproject; this works in
244     any of an SVN checkout, a project-specific tarball.
245     """
246     scriptdir = os.path.join(basedir, 'bin', projname)
247     if not os.path.isdir(scriptdir):
248         # Probably a project-specific tarball, in which case only this
249         # project's bins are included in 'bin'
250         scriptdir = os.path.join(basedir, 'bin')
251         if not os.path.isdir(scriptdir):
252             return []
253     thingies = os.listdir(scriptdir)
254     for specialExclusion in ['.svn', '_preamble.py', '_preamble.pyc']:
255         if specialExclusion in thingies:
256             thingies.remove(specialExclusion)
257     return filter(os.path.isfile,
258                   [os.path.join(scriptdir, x) for x in thingies])
259
260
261 ## Helpers and distutil tweaks
262
263 class build_scripts_twisted(build_scripts.build_scripts):
264     """Renames scripts so they end with '.py' on Windows."""
265
266     def run(self):
267         build_scripts.build_scripts.run(self)
268         if not os.name == "nt":
269             return
270         for f in os.listdir(self.build_dir):
271             fpath=os.path.join(self.build_dir, f)
272             if not fpath.endswith(".py"):
273                 try:
274                     os.unlink(fpath + ".py")
275                 except EnvironmentError, e:
276                     if e.args[1]=='No such file or directory':
277                         pass
278                 os.rename(fpath, fpath + ".py")
279
280
281
282 class install_data_twisted(install_data.install_data):
283     """I make sure data files are installed in the package directory."""
284     def finalize_options(self):
285         self.set_undefined_options('install',
286             ('install_lib', 'install_dir')
287         )
288         install_data.install_data.finalize_options(self)
289
290
291
292 class build_ext_twisted(build_ext.build_ext):
293     """
294     Allow subclasses to easily detect and customize Extensions to
295     build at install-time.
296     """
297
298     def prepare_extensions(self):
299         """
300         Prepare the C{self.extensions} attribute (used by
301         L{build_ext.build_ext}) by checking which extensions in
302         L{conditionalExtensions} should be built.  In addition, if we are
303         building on NT, define the WIN32 macro to 1.
304         """
305         # always define WIN32 under Windows
306         if os.name == 'nt':
307             self.define_macros = [("WIN32", 1)]
308         else:
309             self.define_macros = []
310         self.extensions = [x for x in self.conditionalExtensions
311                            if x.condition(self)]
312         for ext in self.extensions:
313             ext.define_macros.extend(self.define_macros)
314
315
316     def build_extensions(self):
317         """
318         Check to see which extension modules to build and then build them.
319         """
320         self.prepare_extensions()
321         build_ext.build_ext.build_extensions(self)
322
323
324     def _remove_conftest(self):
325         for filename in ("conftest.c", "conftest.o", "conftest.obj"):
326             try:
327                 os.unlink(filename)
328             except EnvironmentError:
329                 pass
330
331
332     def _compile_helper(self, content):
333         conftest = open("conftest.c", "w")
334         try:
335             conftest.write(content)
336             conftest.close()
337
338             try:
339                 self.compiler.compile(["conftest.c"], output_dir='')
340             except CompileError:
341                 return False
342             return True
343         finally:
344             self._remove_conftest()
345
346
347     def _check_header(self, header_name):
348         """
349         Check if the given header can be included by trying to compile a file
350         that contains only an #include line.
351         """
352         self.compiler.announce("checking for %s ..." % header_name, 0)
353         return self._compile_helper("#include <%s>\n" % header_name)
354
355
356
357 def _checkCPython(sys=sys, platform=platform):
358     """
359     Checks if this implementation is CPython.
360
361     On recent versions of Python, will use C{platform.python_implementation}.
362     On 2.5, it will try to extract the implementation from sys.subversion. On
363     older versions (currently the only supported older version is 2.4), checks
364     if C{__pypy__} is in C{sys.modules}, since PyPy is the implementation we
365     really care about. If it isn't, assumes CPython.
366
367     This takes C{sys} and C{platform} kwargs that by default use the real
368     modules. You shouldn't care about these -- they are for testing purposes
369     only.
370
371     @return: C{False} if the implementation is definitely not CPython, C{True}
372         otherwise.
373     """
374     try:
375         return platform.python_implementation() == "CPython"
376     except AttributeError:
377         # For 2.5:
378         try:
379             implementation, _, _ = sys.subversion
380             return implementation == "CPython"
381         except AttributeError:
382             pass
383
384         # Are we on Pypy?
385         if "__pypy__" in sys.modules:
386             return False
387
388         # No? Well, then we're *probably* on CPython.
389         return True
390
391
392 _isCPython = _checkCPython()
393
394
395 def _hasEpoll(builder):
396     """
397     Checks if the header for building epoll (C{sys/epoll.h}) is available.
398
399     @return: C{True} if the header is available, C{False} otherwise.
400     """
401     return builder._check_header("sys/epoll.h")