2 Distutils convenience functionality.
4 Don't use this outside of Twisted.
6 Maintainer: Christopher Armstrong
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
19 twisted_subprojects = ["conch", "lore", "mail", "names",
20 "news", "pair", "runner", "web",
25 class ConditionalExtension(Extension):
27 An extension module that will only be compiled if certain conditions are
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.
35 def __init__(self, *args, **kwargs):
36 self.condition = kwargs.pop("condition", lambda builder: True)
37 Extension.__init__(self, *args, **kwargs)
43 An alternative to distutils' setup() which is specially designed
44 for Twisted subprojects.
46 Pass twisted_subproject=projname if you want package and data
47 files to automatically be found for you.
49 @param conditionalExtensions: Extensions to optionally build.
50 @type conditionalExtensions: C{list} of L{ConditionalExtension}
52 return core.setup(**get_setup_args(**kw))
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)
63 kw['packages'] = getPackages(projdir, parent='twisted')
64 kw['version'] = getVersion(projname)
66 plugin = "twisted/plugins/twisted_" + projname + ".py"
67 if os.path.exists(plugin):
68 kw.setdefault('py_modules', []).append(
69 plugin.replace("/", ".")[:-3])
71 kw['data_files'] = getDataFiles(projdir, parent='twisted')
73 del kw['twisted_subproject']
77 for plg in kw['plugins']:
78 py_modules.append("twisted.plugins." + plg)
79 kw.setdefault('py_modules', []).extend(py_modules)
82 if 'cmdclass' not in kw:
84 'install_data': install_data_twisted,
85 'build_scripts': build_scripts_twisted}
87 if "conditionalExtensions" in kw:
88 extensions = kw["conditionalExtensions"]
89 del kw["conditionalExtensions"]
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
102 class my_build_ext(build_ext_twisted):
103 conditionalExtensions = extensions
104 kw.setdefault('cmdclass', {})['build_ext'] = my_build_ext
108 def getVersion(proj, base="twisted"):
110 Extract the version number for a given project.
112 @param proj: the name of the project. Examples are "core",
113 "conch", "words", "mail".
116 @returns: The version number of the project, as a string like
120 vfile = os.path.join(base, '_version.py')
122 vfile = os.path.join(base, proj, '_version.py')
123 ns = {'__name__': 'Nothing to see here'}
125 return ns['version'].base()
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"]
134 def _filterNames(names):
136 Given a list of file names, return those names that should be copied.
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'))]
149 def relativeTo(base, relativee):
151 Gets 'relativee' relative to 'basepath'.
155 >>> relativeTo('/home/', '/home/radix/')
157 >>> relativeTo('.', '/home/radix/Projects/Twisted') # curdir is /home/radix
160 The 'relativee' must be a child of 'basepath'.
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))
172 def getDataFiles(dname, ignore=None, parent=None):
174 Get all the data files that should be included in this distutils Project.
176 'dname' should be the path to the package that you're distributing.
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.
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.
190 parent = parent or "."
191 ignore = ignore or []
193 for directory, subdirectories, filenames in os.walk(dname):
195 for exname in EXCLUDE_NAMES:
196 if exname in subdirectories:
197 subdirectories.remove(exname)
199 if ig in subdirectories:
200 subdirectories.remove(ig)
201 for filename in _filterNames(filenames):
202 resultfiles.append(filename)
204 result.append((relativeTo(parent, directory),
206 os.path.join(directory, filename))
207 for filename in resultfiles]))
211 def getPackages(dname, pkgname=None, results=None, ignore=None, parent=None):
213 Get all packages which are under dname. This is necessary for
214 Python 2.2's distutils. Pretty similar arguments to getDataFiles,
217 parent = parent or ""
221 bname = os.path.basename(dname)
222 ignore = ignore or []
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,
237 res = ['.'.join(result) for result in results]
241 def getScripts(projname, basedir=''):
243 Returns a list of scripts for a Twisted subproject; this works in
244 any of an SVN checkout, a project-specific tarball.
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):
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])
261 ## Helpers and distutil tweaks
263 class build_scripts_twisted(build_scripts.build_scripts):
264 """Renames scripts so they end with '.py' on Windows."""
267 build_scripts.build_scripts.run(self)
268 if not os.name == "nt":
270 for f in os.listdir(self.build_dir):
271 fpath=os.path.join(self.build_dir, f)
272 if not fpath.endswith(".py"):
274 os.unlink(fpath + ".py")
275 except EnvironmentError, e:
276 if e.args[1]=='No such file or directory':
278 os.rename(fpath, fpath + ".py")
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')
288 install_data.install_data.finalize_options(self)
292 class build_ext_twisted(build_ext.build_ext):
294 Allow subclasses to easily detect and customize Extensions to
295 build at install-time.
298 def prepare_extensions(self):
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.
305 # always define WIN32 under Windows
307 self.define_macros = [("WIN32", 1)]
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)
316 def build_extensions(self):
318 Check to see which extension modules to build and then build them.
320 self.prepare_extensions()
321 build_ext.build_ext.build_extensions(self)
324 def _remove_conftest(self):
325 for filename in ("conftest.c", "conftest.o", "conftest.obj"):
328 except EnvironmentError:
332 def _compile_helper(self, content):
333 conftest = open("conftest.c", "w")
335 conftest.write(content)
339 self.compiler.compile(["conftest.c"], output_dir='')
344 self._remove_conftest()
347 def _check_header(self, header_name):
349 Check if the given header can be included by trying to compile a file
350 that contains only an #include line.
352 self.compiler.announce("checking for %s ..." % header_name, 0)
353 return self._compile_helper("#include <%s>\n" % header_name)
357 def _checkCPython(sys=sys, platform=platform):
359 Checks if this implementation is CPython.
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.
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
371 @return: C{False} if the implementation is definitely not CPython, C{True}
375 return platform.python_implementation() == "CPython"
376 except AttributeError:
379 implementation, _, _ = sys.subversion
380 return implementation == "CPython"
381 except AttributeError:
385 if "__pypy__" in sys.modules:
388 # No? Well, then we're *probably* on CPython.
392 _isCPython = _checkCPython()
395 def _hasEpoll(builder):
397 Checks if the header for building epoll (C{sys/epoll.h}) is available.
399 @return: C{True} if the header is available, C{False} otherwise.
401 return builder._check_header("sys/epoll.h")