root / trunk / twisted / python / dist.py

Revision 26699, 12.0 kB (checked in by exarkun, 3 months ago)

Merge remove-flow-3726

Author: exarkun
Reviewer: therve
Fixes: #3726

Remove Twisted Flow, long unmaintained and deprecated.

Line 
1 """
2 Distutils convenience functionality.
3
4 Don't use this outside of Twisted.
5
6 Maintainer: Christopher Armstrong
7 """
8
9 import sys, os
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
14
15 twisted_subprojects = ["conch", "lore", "mail", "names",
16                        "news", "pair", "runner", "web", "web2",
17                        "words", "vfs"]
18
19
20 class ConditionalExtension(Extension):
21     """
22     An extension module that will only be compiled if certain conditions are
23     met.
24
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.
29     """
30     def __init__(self, *args, **kwargs):
31         self.condition = kwargs.pop("condition", lambda builder: True)
32         Extension.__init__(self, *args, **kwargs)
33
34
35
36 def setup(**kw):
37     """
38     An alternative to distutils' setup() which is specially designed
39     for Twisted subprojects.
40
41     Pass twisted_subproject=projname if you want package and data
42     files to automatically be found for you.
43
44     @param conditionalExtensions: Extensions to optionally build.
45     @type conditionalExtensions: C{list} of L{ConditionalExtension}
46     """
47     return core.setup(**get_setup_args(**kw))
48
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)
56
57         kw['packages'] = getPackages(projdir, parent='twisted')
58         kw['version'] = getVersion(projname)
59
60         plugin = "twisted/plugins/twisted_" + projname + ".py"
61         if os.path.exists(plugin):
62             kw.setdefault('py_modules', []).append(
63                 plugin.replace("/", ".")[:-3])
64
65         kw['data_files'] = getDataFiles(projdir, parent='twisted')
66
67         del kw['twisted_subproject']
68     else:
69         if 'plugins' in kw:
70             py_modules = []
71             for plg in kw['plugins']:
72                 py_modules.append("twisted.plugins." + plg)
73             kw.setdefault('py_modules', []).extend(py_modules)
74             del kw['plugins']
75
76     if 'cmdclass' not in kw:
77         kw['cmdclass'] = {
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
82
83     if "conditionalExtensions" in kw:
84         extensions = kw["conditionalExtensions"]
85         del kw["conditionalExtensions"]
86
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
97
98         class my_build_ext(build_ext_twisted):
99             conditionalExtensions = extensions
100         kw.setdefault('cmdclass', {})['build_ext'] = my_build_ext
101     return kw
102
103 def getVersion(proj, base="twisted"):
104     """
105     Extract the version number for a given project.
106
107     @param proj: the name of the project. Examples are "core",
108     "conch", "words", "mail".
109
110     @rtype: str
111     @returns: The version number of the project, as a string like
112     "2.0.0".
113     """
114     if proj == 'core':
115         vfile = os.path.join(base, '_version.py')
116     else:
117         vfile = os.path.join(base, proj, '_version.py')
118     ns = {'__name__': 'Nothing to see here'}
119     execfile(vfile, ns)
120     return ns['version'].base()
121
122
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"]
127
128 import fnmatch
129
130 def _filterNames(names):
131     """Given a list of file names, return those names that should be copied.
132     """
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'))]
141     return names
142
143 def relativeTo(base, relativee):
144     """
145     Gets 'relativee' relative to 'basepath'.
146
147     i.e.,
148
149     >>> relativeTo('/home/', '/home/radix/')
150     'radix'
151     >>> relativeTo('.', '/home/radix/Projects/Twisted') # curdir is /home/radix
152     'Projects/Twisted'
153
154     The 'relativee' must be a child of 'basepath'.
155     """
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))
164
165
166 def getDataFiles(dname, ignore=None, parent=None):
167     """
168     Get all the data files that should be included in this distutils Project.
169
170     'dname' should be the path to the package that you're distributing.
171
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.
176
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.
183     """
184     parent = parent or "."
185     ignore = ignore or []
186     result = []
187     for directory, subdirectories, filenames in os.walk(dname):
188         resultfiles = []
189         for exname in EXCLUDE_NAMES:
190             if exname in subdirectories:
191                 subdirectories.remove(exname)
192         for ig in ignore:
193             if ig in subdirectories:
194                 subdirectories.remove(ig)
195         for filename in _filterNames(filenames):
196             resultfiles.append(filename)
197         if resultfiles:
198             result.append((relativeTo(parent, directory),
199                            [relativeTo(parent,
200                                        os.path.join(directory, filename))
201                             for filename in resultfiles]))
202     return result
203
204 def getPackages(dname, pkgname=None, results=None, ignore=None, parent=None):
205     """
206     Get all packages which are under dname. This is necessary for
207     Python 2.2's distutils. Pretty similar arguments to getDataFiles,
208     including 'parent'.
209     """
210     parent = parent or ""
211     prefix = []
212     if parent:
213         prefix = [parent]
214     bname = os.path.basename(dname)
215     ignore = ignore or []
216     if bname in ignore:
217         return []
218     if results is None:
219         results = []
220     if pkgname is None:
221         pkgname = []
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,
229                         parent=parent)
230     res = ['.'.join(result) for result in results]
231     return res
232
233
234
235 def getScripts(projname, basedir=''):
236     """
237     Returns a list of scripts for a Twisted subproject; this works in
238     any of an SVN checkout, a project-specific tarball.
239     """
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):
246             return []
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])
252
253
254 ## Helpers and distutil tweaks
255
256 class build_py_twisted(build_py.build_py):
257     """
258     Changes behavior in Python 2.2 to support simultaneous specification of
259     `packages' and `py_modules'.
260     """
261     def run(self):
262         if self.py_modules:
263             self.build_modules()
264         if self.packages:
265             self.build_packages()
266         self.byte_compile(self.get_outputs(include_bytecode=0))
267
268
269
270 class build_scripts_twisted(build_scripts.build_scripts):
271     """Renames scripts so they end with '.py' on Windows."""
272
273     def run(self):
274         build_scripts.build_scripts.run(self)
275         if not os.name == "nt":
276             return
277         for f in os.listdir(self.build_dir):
278             fpath=os.path.join(self.build_dir, f)
279             if not fpath.endswith(".py"):
280                 try:
281                     os.unlink(fpath + ".py")
282                 except EnvironmentError, e:
283                     if e.args[1]=='No such file or directory':
284                         pass
285                 os.rename(fpath, fpath + ".py")
286
287
288
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')
294         )
295         install_data.install_data.finalize_options(self)
296
297
298
299 class build_ext_twisted(build_ext.build_ext):
300     """
301     Allow subclasses to easily detect and customize Extensions to
302     build at install-time.
303     """
304
305     def prepare_extensions(self):
306         """
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.
311         """
312         # always define WIN32 under Windows
313         if os.name == 'nt':
314             self.define_macros = [("WIN32", 1)]
315         else:
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)
321
322
323     def build_extensions(self):
324         """
325         Check to see which extension modules to build and then build them.
326         """
327         self.prepare_extensions()
328         build_ext.build_ext.build_extensions(self)
329
330
331     def _remove_conftest(self):
332         for filename in ("conftest.c", "conftest.o", "conftest.obj"):
333             try:
334                 os.unlink(filename)
335             except EnvironmentError:
336                 pass
337
338
339     def _compile_helper(self, content):
340         conftest = open("conftest.c", "w")
341         try:
342             conftest.write(content)
343             conftest.close()
344
345             try:
346                 self.compiler.compile(["conftest.c"], output_dir='')
347             except CompileError:
348                 return False
349             return True
350         finally:
351             self._remove_conftest()
352
353
354     def _check_header(self, header_name):
355         """
356         Check if the given header can be included by trying to compile a file
357         that contains only an #include line.
358         """
359         self.compiler.announce("checking for %s ..." % header_name, 0)
360         return self._compile_helper("#include <%s>\n" % header_name)
361
Note: See TracBrowser for help on using the browser.