2
Distutils convenience functionality.
4
Don't use this outside of Twisted.
6
Maintainer: Christopher Armstrong
10
from distutils.command import build_scripts, install_data, build_ext, build_py
11
from distutils.errors import CompileError
12
from distutils import core
13
from distutils.core import Extension
15
twisted_subprojects = ["conch", "lore", "mail", "names",
16
"news", "pair", "runner", "web", "web2",
20
class ConditionalExtension(Extension):
22
An extension module that will only be compiled if certain conditions are
25
@param condition: A callable of one argument which returns True or False to
26
indicate whether the extension should be built. The argument is an
27
instance of L{build_ext_twisted}, which has useful methods for checking
28
things about the platform.
30
def __init__(self, *args, **kwargs):
31
self.condition = kwargs.pop("condition", lambda builder: True)
32
Extension.__init__(self, *args, **kwargs)
38
An alternative to distutils' setup() which is specially designed
39
for Twisted subprojects.
41
Pass twisted_subproject=projname if you want package and data
42
files to automatically be found for you.
44
@param conditionalExtensions: Extensions to optionally build.
45
@type conditionalExtensions: C{list} of L{ConditionalExtension}
47
return core.setup(**get_setup_args(**kw))
49
def get_setup_args(**kw):
50
if 'twisted_subproject' in kw:
51
if 'twisted' not in os.listdir('.'):
52
raise RuntimeError("Sorry, you need to run setup.py from the "
53
"toplevel source directory.")
54
projname = kw['twisted_subproject']
55
projdir = os.path.join('twisted', projname)
57
kw['packages'] = getPackages(projdir, parent='twisted')
58
kw['version'] = getVersion(projname)
60
plugin = "twisted/plugins/twisted_" + projname + ".py"
61
if os.path.exists(plugin):
62
kw.setdefault('py_modules', []).append(
63
plugin.replace("/", ".")[:-3])
65
kw['data_files'] = getDataFiles(projdir, parent='twisted')
67
del kw['twisted_subproject']
71
for plg in kw['plugins']:
72
py_modules.append("twisted.plugins." + plg)
73
kw.setdefault('py_modules', []).extend(py_modules)
76
if 'cmdclass' not in kw:
78
'install_data': install_data_twisted,
79
'build_scripts': build_scripts_twisted}
80
if sys.version_info[:3] < (2, 3, 0):
81
kw['cmdclass']['build_py'] = build_py_twisted
83
if "conditionalExtensions" in kw:
84
extensions = kw["conditionalExtensions"]
85
del kw["conditionalExtensions"]
87
if 'ext_modules' not in kw:
88
# This is a workaround for distutils behavior; ext_modules isn't
89
# actually used by our custom builder. distutils deep-down checks
90
# to see if there are any ext_modules defined before invoking
91
# the build_ext command. We need to trigger build_ext regardless
92
# because it is the thing that does the conditional checks to see
93
# if it should build any extensions. The reason we have to delay
94
# the conditional checks until then is that the compiler objects
95
# are not yet set up when this code is executed.
96
kw["ext_modules"] = extensions
98
class my_build_ext(build_ext_twisted):
99
conditionalExtensions = extensions
100
kw.setdefault('cmdclass', {})['build_ext'] = my_build_ext
103
def getVersion(proj, base="twisted"):
105
Extract the version number for a given project.
107
@param proj: the name of the project. Examples are "core",
108
"conch", "words", "mail".
111
@returns: The version number of the project, as a string like
115
vfile = os.path.join(base, '_version.py')
117
vfile = os.path.join(base, proj, '_version.py')
118
ns = {'__name__': 'Nothing to see here'}
120
return ns['version'].base()
123
# Names that are exluded from globbing results:
124
EXCLUDE_NAMES = ["{arch}", "CVS", ".cvsignore", "_darcs",
125
"RCS", "SCCS", ".svn"]
126
EXCLUDE_PATTERNS = ["*.py[cdo]", "*.s[ol]", ".#*", "*~", "*.py"]
130
def _filterNames(names):
131
"""Given a list of file names, return those names that should be copied.
133
names = [n for n in names
134
if n not in EXCLUDE_NAMES]
135
# This is needed when building a distro from a working
136
# copy (likely a checkout) rather than a pristine export:
137
for pattern in EXCLUDE_PATTERNS:
138
names = [n for n in names
139
if (not fnmatch.fnmatch(n, pattern))
140
and (not n.endswith('.py'))]
143
def relativeTo(base, relativee):
145
Gets 'relativee' relative to 'basepath'.
149
>>> relativeTo('/home/', '/home/radix/')
151
>>> relativeTo('.', '/home/radix/Projects/Twisted') # curdir is /home/radix
154
The 'relativee' must be a child of 'basepath'.
156
basepath = os.path.abspath(base)
157
relativee = os.path.abspath(relativee)
158
if relativee.startswith(basepath):
159
relative = relativee[len(basepath):]
160
if relative.startswith(os.sep):
161
relative = relative[1:]
162
return os.path.join(base, relative)
163
raise ValueError("%s is not a subpath of %s" % (relativee, basepath))
166
def getDataFiles(dname, ignore=None, parent=None):
168
Get all the data files that should be included in this distutils Project.
170
'dname' should be the path to the package that you're distributing.
172
'ignore' is a list of sub-packages to ignore. This facilitates
173
disparate package hierarchies. That's a fancy way of saying that
174
the 'twisted' package doesn't want to include the 'twisted.conch'
175
package, so it will pass ['conch'] as the value.
177
'parent' is necessary if you're distributing a subpackage like
178
twisted.conch. 'dname' should point to 'twisted/conch' and 'parent'
179
should point to 'twisted'. This ensures that your data_files are
180
generated correctly, only using relative paths for the first element
181
of the tuple ('twisted/conch/*').
182
The default 'parent' is the current working directory.
184
parent = parent or "."
185
ignore = ignore or []
187
for directory, subdirectories, filenames in os.walk(dname):
189
for exname in EXCLUDE_NAMES:
190
if exname in subdirectories:
191
subdirectories.remove(exname)
193
if ig in subdirectories:
194
subdirectories.remove(ig)
195
for filename in _filterNames(filenames):
196
resultfiles.append(filename)
198
result.append((relativeTo(parent, directory),
200
os.path.join(directory, filename))
201
for filename in resultfiles]))
204
def getPackages(dname, pkgname=None, results=None, ignore=None, parent=None):
206
Get all packages which are under dname. This is necessary for
207
Python 2.2's distutils. Pretty similar arguments to getDataFiles,
210
parent = parent or ""
214
bname = os.path.basename(dname)
215
ignore = ignore or []
222
subfiles = os.listdir(dname)
223
abssubfiles = [os.path.join(dname, x) for x in subfiles]
224
if '__init__.py' in subfiles:
225
results.append(prefix + pkgname + [bname])
226
for subdir in filter(os.path.isdir, abssubfiles):
227
getPackages(subdir, pkgname=pkgname + [bname],
228
results=results, ignore=ignore,
230
res = ['.'.join(result) for result in results]
235
def getScripts(projname, basedir=''):
237
Returns a list of scripts for a Twisted subproject; this works in
238
any of an SVN checkout, a project-specific tarball.
240
scriptdir = os.path.join(basedir, 'bin', projname)
241
if not os.path.isdir(scriptdir):
242
# Probably a project-specific tarball, in which case only this
243
# project's bins are included in 'bin'
244
scriptdir = os.path.join(basedir, 'bin')
245
if not os.path.isdir(scriptdir):
247
thingies = os.listdir(scriptdir)
248
if '.svn' in thingies:
249
thingies.remove('.svn')
250
return filter(os.path.isfile,
251
[os.path.join(scriptdir, x) for x in thingies])
254
## Helpers and distutil tweaks
256
class build_py_twisted(build_py.build_py):
258
Changes behavior in Python 2.2 to support simultaneous specification of
259
`packages' and `py_modules'.
265
self.build_packages()
266
self.byte_compile(self.get_outputs(include_bytecode=0))
270
class build_scripts_twisted(build_scripts.build_scripts):
271
"""Renames scripts so they end with '.py' on Windows."""
274
build_scripts.build_scripts.run(self)
275
if not os.name == "nt":
277
for f in os.listdir(self.build_dir):
278
fpath=os.path.join(self.build_dir, f)
279
if not fpath.endswith(".py"):
281
os.unlink(fpath + ".py")
282
except EnvironmentError, e:
283
if e.args[1]=='No such file or directory':
285
os.rename(fpath, fpath + ".py")
289
class install_data_twisted(install_data.install_data):
290
"""I make sure data files are installed in the package directory."""
291
def finalize_options(self):
292
self.set_undefined_options('install',
293
('install_lib', 'install_dir')
295
install_data.install_data.finalize_options(self)
299
class build_ext_twisted(build_ext.build_ext):
301
Allow subclasses to easily detect and customize Extensions to
302
build at install-time.
305
def prepare_extensions(self):
307
Prepare the C{self.extensions} attribute (used by
308
L{build_ext.build_ext}) by checking which extensions in
309
L{conditionalExtensions} should be built. In addition, if we are
310
building on NT, define the WIN32 macro to 1.
312
# always define WIN32 under Windows
314
self.define_macros = [("WIN32", 1)]
316
self.define_macros = []
317
self.extensions = [x for x in self.conditionalExtensions
318
if x.condition(self)]
319
for ext in self.extensions:
320
ext.define_macros.extend(self.define_macros)
323
def build_extensions(self):
325
Check to see which extension modules to build and then build them.
327
self.prepare_extensions()
328
build_ext.build_ext.build_extensions(self)
331
def _remove_conftest(self):
332
for filename in ("conftest.c", "conftest.o", "conftest.obj"):
335
except EnvironmentError:
339
def _compile_helper(self, content):
340
conftest = open("conftest.c", "w")
342
conftest.write(content)
346
self.compiler.compile(["conftest.c"], output_dir='')
351
self._remove_conftest()
354
def _check_header(self, header_name):
356
Check if the given header can be included by trying to compile a file
357
that contains only an #include line.
359
self.compiler.announce("checking for %s ..." % header_name, 0)
360
return self._compile_helper("#include <%s>\n" % header_name)