=== modified file 'setup.py'
--- setup.py	2011-07-04 23:21:42 +0000
+++ setup.py	2012-01-20 20:43:36 +0000
@@ -16,25 +16,6 @@
 import sys, os
 
 
-def getExtensions():
-    """
-    Get all extensions from core and all subprojects.
-    """
-    extensions = []
-
-    if not sys.platform.startswith('java'):
-        for dir in os.listdir("twisted") + [""]:
-            topfiles = os.path.join("twisted", dir, "topfiles")
-            if os.path.isdir(topfiles):
-                ns = {}
-                setup_py = os.path.join(topfiles, "setup.py")
-                execfile(setup_py, ns, ns)
-                if "extensions" in ns:
-                    extensions.extend(ns["extensions"])
-
-    return extensions
-
-
 def main(args):
     """
     Invoke twisted.python.dist with the appropriate metadata about the
@@ -43,8 +24,8 @@
     if os.path.exists('twisted'):
         sys.path.insert(0, '.')
     from twisted import copyright
-    from twisted.python.dist import getDataFiles, getScripts, getPackages, \
-                                    setup, twisted_subprojects
+    from twisted.python.dist import getDataFiles, getExtensions, getScripts, \
+        getPackages, setup, twisted_subprojects
 
     # "" is included because core scripts are directly in bin/
     projects = [''] + [x for x in os.listdir('bin')

=== modified file 'twisted/lore/htmlbook.py'
--- twisted/lore/htmlbook.py	2011-02-14 04:45:15 +0000
+++ twisted/lore/htmlbook.py	2012-01-11 21:50:11 +0000
@@ -1,6 +1,8 @@
 # Copyright (c) Twisted Matrix Laboratories.
 # See LICENSE for details.
 
+from twisted.python.compat import execfile
+
 
 def getNumber(filename):
     return None
@@ -24,7 +26,7 @@
         Index = self.Index
 
         if filename:
-            execfile(filename)
+            execfile(filename, globals())
 
     def getFiles(self):
         return [c[0] for c in self.chapters]

=== modified file 'twisted/names/authority.py'
--- twisted/names/authority.py	2011-02-14 04:45:15 +0000
+++ twisted/names/authority.py	2012-01-11 21:50:11 +0000
@@ -12,6 +12,7 @@
 from twisted.names import dns
 from twisted.internet import defer
 from twisted.python import failure
+from twisted.python.compat import execfile
 
 import common
 

=== modified file 'twisted/python/_release.py'
--- twisted/python/_release.py	2011-11-20 15:23:17 +0000
+++ twisted/python/_release.py	2012-01-11 21:50:11 +0000
@@ -25,6 +25,7 @@
 from twisted.python.versions import Version
 from twisted.python.filepath import FilePath
 from twisted.python.dist import twisted_subprojects
+from twisted.python.compat import execfile
 
 # This import is an example of why you shouldn't use this module unless you're
 # radix

=== modified file 'twisted/python/compat.py'
--- twisted/python/compat.py	2011-06-03 21:55:35 +0000
+++ twisted/python/compat.py	2012-01-11 21:49:26 +0000
@@ -139,12 +139,12 @@
                   'set_connect_state', 'set_accept_state',
                   'connect_ex', 'sendall'):
 
-            exec """def %s(self, *args):
+            exec("""def %s(self, *args):
                 self._lock.acquire()
                 try:
                     return apply(self._ssl_conn.%s, args)
                 finally:
-                    self._lock.release()\n""" % (f, f)
+                    self._lock.release()\n""" % (f, f))
 sys.modules['OpenSSL.tsafe'] = tsafe
 
 import operator
@@ -175,3 +175,40 @@
     from functools import reduce
 except ImportError:
     reduce = reduce
+
+
+try:
+    _execfile = execfile
+except NameError:
+    _execfile = None
+
+
+def execfile(filename, globals, locals=None):
+    """Execute a Python script in the given namespaces.
+
+    Similar to the execfile builtin, but a namespace is mandatory, partly
+    because that's a sensible thing to require, and because otherwise we'd
+    have to do some frame hacking.
+
+    This is a compatibility wrapper for Python 3 porting.
+    """
+    if locals is None:
+        locals = globals
+    if _execfile is None:
+        fin = open(filename, "rb")
+        try:
+            source = fin.read()
+        finally:
+            fin.close()
+        code = compile(source, filename, "exec")
+        exec(code, globals, locals)
+    else:
+        _execfile(filename, globals, locals)
+
+
+__all__ = [
+    "execfile",
+    "frozenset",
+    "reduce",
+    "set",
+    ]

=== modified file 'twisted/python/dist.py'
--- twisted/python/dist.py	2011-11-20 15:19:42 +0000
+++ twisted/python/dist.py	2012-01-20 20:52:23 +0000
@@ -15,6 +15,8 @@
 import platform
 import sys
 
+from twisted.python.compat import execfile
+
 
 twisted_subprojects = ["conch", "lore", "mail", "names",
                        "news", "pair", "runner", "web",
@@ -208,6 +210,25 @@
     return result
 
 
+def getExtensions():
+    """
+    Get all extensions from core and all subprojects.
+    """
+    extensions = []
+
+    if not sys.platform.startswith('java'):
+        for dir in os.listdir("twisted") + [""]:
+            topfiles = os.path.join("twisted", dir, "topfiles")
+            if os.path.isdir(topfiles):
+                ns = {}
+                setup_py = os.path.join(topfiles, "setup.py")
+                execfile(setup_py, ns, ns)
+                if "extensions" in ns:
+                    extensions.extend(ns["extensions"])
+
+    return extensions
+
+
 def getPackages(dname, pkgname=None, results=None, ignore=None, parent=None):
     """
     Get all packages which are under dname. This is necessary for

=== modified file 'twisted/python/test/test_dist.py'
--- twisted/python/test/test_dist.py	2011-09-26 15:28:23 +0000
+++ twisted/python/test/test_dist.py	2012-01-21 14:25:16 +0000
@@ -7,6 +7,7 @@
 
 
 import os
+import shutil
 
 from distutils.core import Distribution
 
@@ -55,6 +56,73 @@
         self.assertEqual(ext.define_macros, [("whatever", 2), ("WIN32", 1)])
 
 
+class GetExtensionsTest(TestCase):
+    """
+    Tests for L{dist.getExtensions}.
+    """
+
+    setup_template = (
+        "from twisted.python.dist import ConditionalExtension\n"
+        "extensions = [\n"
+        "    ConditionalExtension(\n"
+        "        '%s', ['twisted/some/thing.c'],\n"
+        "        condition=lambda builder: True)\n"
+        "    ]\n")
+
+    def setUp(self):
+        super(GetExtensionsTest, self).setUp()
+        self.basedir = os.path.abspath("twisted")
+        # The following will fail if this is is executed with a test runner
+        # that does not chdir to a test-specific temporary working directory.
+        os.mkdir(self.basedir)
+        self.addCleanup(shutil.rmtree, self.basedir)
+
+    def writeSetup(self, name, *path):
+        """
+        Write out a C{setup.py} file to a location determined by
+        L{self.basedir} and L{path}. L{self.setup_template} is used to
+        generate its contents.
+        """
+        outdir = os.path.join(self.basedir, *path)
+        os.makedirs(outdir)
+        open(os.path.join(outdir, "setup.py"), "wb").write(
+            self.setup_template % name)
+
+    def assertExtensions(self, expected):
+        """
+        Assert that the given names match the (sorted) names of discovered
+        extensions.
+        """
+        extensions = dist.getExtensions()
+        extension_names = [extension.name for extension in extensions]
+        self.assertEqual(sorted(extension_names), expected)
+
+    def test_getExtensions(self):
+        """
+        Files named C{setup.py} in C{twisted/topfiles} and
+        C{twisted/*/topfiles} are L{execfile}ed in order to discover the
+        extensions they declare.
+        """
+        self.writeSetup("twisted.transmutate", "topfiles")
+        self.writeSetup("twisted.tele.port", "tele", "topfiles")
+        self.assertExtensions(["twisted.tele.port", "twisted.transmutate"])
+
+    def test_getExtensions_too_deep(self):
+        """
+        Files named C{setup.py} in C{topfiles} directories are not considered
+        if they are too deep in the directory hierarchy.
+        """
+        self.writeSetup("twisted.trans.mog.rify", "trans", "mog", "topfiles")
+        self.assertExtensions([])
+
+    def test_getExtensions_not_topfiles(self):
+        """
+        The folder in which C{setup.py} is discovered must be called
+        C{topfiles} otherwise it is ignored.
+        """
+        self.writeSetup("twisted.metamorphosis", "notfiles")
+        self.assertExtensions([])
+
 
 class GetVersionTest(TestCase):
     """

=== modified file 'twisted/python/test/test_release.py'
--- twisted/python/test/test_release.py	2011-07-14 18:05:14 +0000
+++ twisted/python/test/test_release.py	2012-01-11 21:51:37 +0000
@@ -20,7 +20,7 @@
 
 from twisted.trial.unittest import TestCase
 
-from twisted.python.compat import set
+from twisted.python.compat import execfile, set
 from twisted.python.procutils import which
 from twisted.python import release
 from twisted.python.filepath import FilePath

=== modified file 'twisted/test/test_compat.py'
--- twisted/test/test_compat.py	2011-07-14 18:05:14 +0000
+++ twisted/test/test_compat.py	2012-01-11 22:44:32 +0000
@@ -6,11 +6,11 @@
 Tests for L{twisted.python.compat}.
 """
 
-import types, socket
+import os, tempfile, types, socket
 
 from twisted.trial import unittest
 
-from twisted.python.compat import set, frozenset, reduce
+from twisted.python.compat import set, frozenset, reduce, execfile
 
 
 
@@ -197,3 +197,38 @@
         """
         self.assertEqual(15, reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]))
         self.assertEqual(16, reduce(lambda x, y: x + y, [1, 2, 3, 4, 5], 1))
+
+
+class ExecfileCompatTestCase(unittest.TestCase):
+    """Tests for the L{execfile} compatibility wrapper."""
+
+    def setUp(self):
+        super(ExecfileCompatTestCase, self).setUp()
+        fd, self.script = tempfile.mkstemp(".py")
+        fout = os.fdopen(fd, "wb")
+        try:
+            fout.write("foo += 1\n".encode("ascii"))
+        finally:
+            fout.close()
+
+    def tearDown(self):
+        super(ExecfileCompatTestCase, self).tearDown()
+        os.unlink(self.script)
+
+    def test_execfileGlobals(self):
+        """
+        L{execfile} executes the specified file in the given global namespace.
+        """
+        ns_global = {"foo": 1}
+        execfile(self.script, ns_global)
+        self.assertEqual(2, ns_global["foo"])
+
+    def test_execfileGlobalsAndLocals(self):
+        """
+        L{execfile} executes the specified file in the given global and local
+        namespaces.
+        """
+        ns_global, ns_local = {"foo": 10}, {"foo": 20}
+        execfile(self.script, ns_global, ns_local)
+        self.assertEqual(10, ns_global["foo"])
+        self.assertEqual(21, ns_local["foo"])

=== added file 'twisted/topfiles/5129.misc'
--- twisted/topfiles/5129.misc	1970-01-01 00:00:00 +0000
+++ twisted/topfiles/5129.misc	2012-01-11 21:50:34 +0000
@@ -0,0 +1,3 @@
+Replace usage of execfile() with t.p.compat.execfile(), a new function
+that wraps execfile() on Python 2.x and provides equivalent
+functionality on Python 3.x.

=== modified file 'twisted/web/script.py'
--- twisted/web/script.py	2011-02-14 04:45:15 +0000
+++ twisted/web/script.py	2012-01-11 21:50:11 +0000
@@ -14,6 +14,7 @@
     import StringIO
 
 from twisted import copyright
+from twisted.python.compat import execfile
 from twisted.web import http, server, static, resource, html
 
 

