| OLD | NEW |
| (Empty) |
| 1 """ | |
| 2 Distutils convenience functionality. | |
| 3 | |
| 4 Don't use this outside of Twisted. | |
| 5 | |
| 6 Maintainer: U{Christopher Armstrong<mailto:radix@twistedmatrix.com>} | |
| 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", "flow", "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 | |
| OLD | NEW |