[Python-checkins] distutils2: Improve byte-compilation to be independent of -O or -B.

eric.araujo python-checkins at python.org
Mon Nov 14 15:24:07 CET 2011


http://hg.python.org/distutils2/rev/7c0a88497b5c
changeset:   1237:7c0a88497b5c
user:        Éric Araujo <merwok at netwok.org>
date:        Sat Nov 12 02:58:14 2011 +0100
summary:
  Improve byte-compilation to be independent of -O or -B.

All code (util.byte_compile, build_py, install_lib) can now create .pyc
and/or.pyo files according to options given by users, without
interference from the calling Python’s own optimize mode or from the
sys.dont_write_bytecode switch.

The rationale is that packaging gives control over the creation of
.pyc/.pyo files to the user with its own explicit option, and the
behavior should not be changed if the calling Python happens to run with
-B or -O for whatever reason.

This is actually a bug fix, not an improvement: Digging into the early
history of distutils shows that the original author wanted this behavior
(see for example comments in build_py in r12940).

files:
  CHANGES.txt                                   |   2 +
  distutils2/command/build_py.py                |  67 +++-----
  distutils2/command/cmd.py                     |  20 ++-
  distutils2/command/install_lib.py             |  67 ++------
  distutils2/errors.py                          |   4 -
  distutils2/tests/support.py                   |   6 +-
  distutils2/tests/test_command_build_py.py     |  50 ++++--
  distutils2/tests/test_command_install_dist.py |  11 +-
  distutils2/tests/test_command_install_lib.py  |  78 +++++----
  distutils2/tests/test_mixin2to3.py            |   1 +
  distutils2/tests/test_util.py                 |  58 +++---
  distutils2/util.py                            |  25 +-
  12 files changed, 184 insertions(+), 205 deletions(-)


diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -149,6 +149,8 @@
   shlex not supporting unicode in 2.x, fix wrong shutil import [david, éric]
 - #13205: Fix and improve generated setup scripts [david, éric]
 - #11751: Improve test coverage for manifest [justin]
+- Byte compilation is now isolated from the calling Python -B or -O options
+  [éric]
 
 
 1.0a3 - 2010-10-08
diff --git a/distutils2/command/build_py.py b/distutils2/command/build_py.py
--- a/distutils2/command/build_py.py
+++ b/distutils2/command/build_py.py
@@ -1,7 +1,6 @@
 """Build pure Python modules (just copy to build directory)."""
 
 import os
-import sys
 from glob import glob
 
 from distutils2 import logger
@@ -13,10 +12,14 @@
 # marking public APIs
 __all__ = ['build_py']
 
+
 class build_py(Command, Mixin2to3):
 
     description = "build pure Python modules (copy to build directory)"
 
+    # The options for controlling byte compilation are two independent sets;
+    # more info in install_lib or the reST docs
+
     user_options = [
         ('build-lib=', 'd', "directory to build (copy) to"),
         ('compile', 'c', "compile .py to .pyc"),
@@ -28,13 +31,14 @@
         ('use-2to3', None,
          "use 2to3 to make source python 3.x compatible"),
         ('convert-2to3-doctests', None,
-         "use 2to3 to convert doctests in seperate text files"),
+         "use 2to3 to convert doctests in separate text files"),
         ('use-2to3-fixers', None,
          "list additional fixers opted for during 2to3 conversion"),
         ]
 
     boolean_options = ['compile', 'force']
-    negative_opt = {'no-compile' : 'compile'}
+
+    negative_opt = {'no-compile': 'compile'}
 
     def initialize_options(self):
         self.build_lib = None
@@ -108,14 +112,15 @@
             self.run_2to3(self._updated_files, self._doctests_2to3,
                                             self.use_2to3_fixers)
 
-        self.byte_compile(self.get_outputs(include_bytecode=False))
+        self.byte_compile(self.get_outputs(include_bytecode=False),
+                          prefix=self.build_lib)
 
     # -- Top-level worker functions ------------------------------------
 
     def get_data_files(self):
         """Generate list of '(package,src_dir,build_dir,filenames)' tuples.
 
-        Helper function for `finalize_options()`.
+        Helper function for finalize_options.
         """
         data = []
         if not self.packages:
@@ -130,7 +135,7 @@
             # Length of path to strip from found files
             plen = 0
             if src_dir:
-                plen = len(src_dir)+1
+                plen = len(src_dir) + 1
 
             # Strip directory from globbed filenames
             filenames = [
@@ -142,7 +147,7 @@
     def find_data_files(self, package, src_dir):
         """Return filenames for package's data files in 'src_dir'.
 
-        Helper function for `get_data_files()`.
+        Helper function for get_data_files.
         """
         globs = (self.package_data.get('', [])
                  + self.package_data.get(package, []))
@@ -157,7 +162,7 @@
     def build_package_data(self):
         """Copy data files into build directory.
 
-        Helper function for `run()`.
+        Helper function for run.
         """
         # FIXME add tests for this method
         for package, src_dir, build_dir, filenames in self.data_files:
@@ -167,16 +172,17 @@
                 self.mkpath(os.path.dirname(target))
                 outf, copied = self.copy_file(srcfile,
                                target, preserve_mode=False)
-                if copied and srcfile in self.distribution.convert_2to3.doctests:
+                doctests = self.distribution.convert_2to3_doctests
+                if copied and srcfile in doctests:
                     self._doctests_2to3.append(outf)
 
     # XXX - this should be moved to the Distribution class as it is not
     # only needed for build_py. It also has no dependencies on this class.
     def get_package_dir(self, package):
         """Return the directory, relative to the top of the source
-           distribution, where package 'package' should be found
-           (at least according to the 'package_dir' option, if any)."""
-
+        distribution, where package 'package' should be found
+        (at least according to the 'package_dir' option, if any).
+        """
         path = package.split('.')
         if self.package_dir is not None:
             path.insert(0, self.package_dir)
@@ -187,8 +193,7 @@
         return ''
 
     def check_package(self, package, package_dir):
-        """Helper function for `find_package_modules()` and `find_modules()'.
-        """
+        """Helper function for find_package_modules and find_modules."""
         # Empty dir name means current directory, which we can probably
         # assume exists.  Also, os.path.exists and isdir don't know about
         # my "empty string means current dir" convention, so we have to
@@ -208,8 +213,8 @@
             if os.path.isfile(init_py):
                 return init_py
             else:
-                logger.warning(("package init file '%s' not found " +
-                                "(or not a regular file)"), init_py)
+                logger.warning("package init file %r not found "
+                               "(or not a regular file)", init_py)
 
         # Either not in a package at all (__init__.py not expected), or
         # __init__.py doesn't exist -- so don't return the filename.
@@ -217,7 +222,7 @@
 
     def check_module(self, module, module_file):
         if not os.path.isfile(module_file):
-            logger.warning("file %s (for module %s) not found",
+            logger.warning("file %r (for module %r) not found",
                            module_file, module)
             return False
         else:
@@ -238,7 +243,7 @@
                 module = os.path.splitext(os.path.basename(f))[0]
                 modules.append((package, module, f))
             else:
-                logger.debug("excluding %s", setup_script)
+                logger.debug("excluding %r", setup_script)
         return modules
 
     def find_modules(self):
@@ -331,7 +336,7 @@
             if include_bytecode:
                 if self.compile:
                     outputs.append(filename + "c")
-                if self.optimize > 0:
+                if self.optimize:
                     outputs.append(filename + "o")
 
         outputs += [
@@ -359,7 +364,6 @@
     def build_modules(self):
         modules = self.find_modules()
         for package, module, module_file in modules:
-
             # Now "build" the module -- ie. copy the source file to
             # self.build_lib (the build directory for Python source).
             # (Actually, it gets copied to the directory for this package
@@ -368,7 +372,6 @@
 
     def build_packages(self):
         for package in self.packages:
-
             # Get list of (package, module, module_file) tuples based on
             # scanning the package directory.  'package' is only included
             # in the tuple so that 'find_modules()' and
@@ -386,25 +389,3 @@
             for package_, module, module_file in modules:
                 assert package == package_
                 self.build_module(module, module_file, package)
-
-    def byte_compile(self, files):
-        if getattr(sys, 'dont_write_bytecode', False):
-            logger.warning('%s: byte-compiling is disabled, skipping.',
-                           self.get_command_name())
-            return
-
-        from distutils2.util import byte_compile  # FIXME use compileall
-        prefix = self.build_lib
-        if prefix[-1] != os.sep:
-            prefix = prefix + os.sep
-
-        # XXX this code is essentially the same as the 'byte_compile()
-        # method of the "install_lib" command, except for the determination
-        # of the 'prefix' string.  Hmmm.
-
-        if self.compile:
-            byte_compile(files, optimize=0,
-                         force=self.force, prefix=prefix, dry_run=self.dry_run)
-        if self.optimize > 0:
-            byte_compile(files, optimize=self.optimize,
-                         force=self.force, prefix=prefix, dry_run=self.dry_run)
diff --git a/distutils2/command/cmd.py b/distutils2/command/cmd.py
--- a/distutils2/command/cmd.py
+++ b/distutils2/command/cmd.py
@@ -10,7 +10,7 @@
 
 class Command(object):
     """Abstract base class for defining command classes, the "worker bees"
-    of the Packaging.  A useful analogy for command classes is to think of
+    of Packaging.  A useful analogy for command classes is to think of
     them as subroutines with local variables called "options".  The options
     are "declared" in 'initialize_options()' and "defined" (given their
     final values, aka "finalized") in 'finalize_options()', both of which
@@ -386,7 +386,6 @@
         if self.dry_run:
             return  # see if we want to display something
 
-
         return util.copy_tree(infile, outfile, preserve_mode, preserve_times,
             preserve_symlinks, not self.force, dry_run=self.dry_run)
 
@@ -439,3 +438,20 @@
         # Otherwise, print the "skip" message
         else:
             logger.debug(skip_msg)
+
+    def byte_compile(self, files, prefix=None):
+        """Byte-compile files to pyc and/or pyo files.
+
+        This method requires that the calling class define compile and
+        optimize options, like build_py and install_lib.  It also
+        automatically respects the force and dry-run options.
+
+        prefix, if given, is a string that will be stripped off the
+        filenames encoded in bytecode files.
+        """
+        if self.compile:
+            util.byte_compile(files, optimize=False, prefix=prefix,
+                              force=self.force, dry_run=self.dry_run)
+        if self.optimize:
+            util.byte_compile(files, optimize=self.optimize, prefix=prefix,
+                              force=self.force, dry_run=self.dry_run)
diff --git a/distutils2/command/install_lib.py b/distutils2/command/install_lib.py
--- a/distutils2/command/install_lib.py
+++ b/distutils2/command/install_lib.py
@@ -1,8 +1,6 @@
 """Install all modules (extensions and pure Python)."""
 
 import os
-import sys
-import logging
 
 from distutils2 import logger
 from distutils2.command.cmd import Command
@@ -10,25 +8,18 @@
 
 
 # Extension for Python source files.
+# XXX dead code?  most of the codebase checks for literal '.py'
 if hasattr(os, 'extsep'):
     PYTHON_SOURCE_EXTENSION = os.extsep + "py"
 else:
     PYTHON_SOURCE_EXTENSION = ".py"
 
+
 class install_lib(Command):
 
     description = "install all modules (extensions and pure Python)"
 
-    # The byte-compilation options are a tad confusing.  Here are the
-    # possible scenarios:
-    #   1) no compilation at all (--no-compile --no-optimize)
-    #   2) compile .pyc only (--compile --no-optimize; default)
-    #   3) compile .pyc and "level 1" .pyo (--compile --optimize)
-    #   4) compile "level 1" .pyo only (--no-compile --optimize)
-    #   5) compile .pyc and "level 2" .pyo (--compile --optimize-more)
-    #   6) compile "level 2" .pyo only (--no-compile --optimize-more)
-    #
-    # The UI for this is two option, 'compile' and 'optimize'.
+    # The options for controlling byte compilation are two independent sets:
     # 'compile' is strictly boolean, and only decides whether to
     # generate .pyc files.  'optimize' is three-way (0, 1, or 2), and
     # decides both whether to generate .pyo files and what level of
@@ -36,7 +27,7 @@
 
     user_options = [
         ('install-dir=', 'd', "directory to install to"),
-        ('build-dir=','b', "build directory (where to install from)"),
+        ('build-dir=', 'b', "build directory (where to install from)"),
         ('force', 'f', "force installation (overwrite existing files)"),
         ('compile', 'c', "compile .py to .pyc [default]"),
         ('no-compile', None, "don't compile .py files"),
@@ -47,7 +38,8 @@
         ]
 
     boolean_options = ['force', 'compile', 'skip-build']
-    negative_opt = {'no-compile' : 'compile'}
+
+    negative_opt = {'no-compile': 'compile'}
 
     def initialize_options(self):
         # let the 'install_dist' command dictate our installation directory
@@ -65,7 +57,8 @@
         self.set_undefined_options('install_dist',
                                    ('build_lib', 'build_dir'),
                                    ('install_lib', 'install_dir'),
-                                   'force', 'compile', 'optimize', 'skip_build')
+                                   'force', 'compile', 'optimize',
+                                   'skip_build')
 
         if self.compile is None:
             self.compile = True
@@ -89,9 +82,14 @@
         # having a build directory!)
         outfiles = self.install()
 
-        # (Optionally) compile .py to .pyc
+        # (Optionally) compile .py to .pyc and/or .pyo
         if outfiles is not None and self.distribution.has_pure_modules():
-            self.byte_compile(outfiles)
+            # XXX comment from distutils: "This [prefix stripping] is far from
+            # complete, but it should at least generate usable bytecode in RPM
+            # distributions." -> need to find exact requirements for
+            # byte-compiled files and fix it
+            install_root = self.get_finalized_command('install_dist').root
+            self.byte_compile(outfiles, prefix=install_root)
 
     # -- Top-level worker functions ------------------------------------
     # (called from 'run()')
@@ -113,38 +111,6 @@
             return
         return outfiles
 
-    def byte_compile(self, files):
-        if getattr(sys, 'dont_write_bytecode', False):
-            # XXX do we want this?  because a Python runs without bytecode
-            # doesn't mean that the *dists should not contain bytecode
-            #--or does it?
-            logger.warning('%s: byte-compiling is disabled, skipping.',
-                           self.get_command_name())
-            return
-
-        from distutils2.util import byte_compile  # FIXME use compileall
-
-        # Get the "--root" directory supplied to the "install_dist" command,
-        # and use it as a prefix to strip off the purported filename
-        # encoded in bytecode files.  This is far from complete, but it
-        # should at least generate usable bytecode in RPM distributions.
-        install_root = self.get_finalized_command('install_dist').root
-
-        # Temporary kludge until we remove the verbose arguments and use
-        # logging everywhere
-        verbose = logger.getEffectiveLevel() >= logging.DEBUG
-
-        if self.compile:
-            byte_compile(files, optimize=0,
-                         force=self.force, prefix=install_root,
-                         dry_run=self.dry_run)
-        if self.optimize > 0:
-            byte_compile(files, optimize=self.optimize,
-                         force=self.force, prefix=install_root,
-                         verbose=verbose,
-                         dry_run=self.dry_run)
-
-
     # -- Utility methods -----------------------------------------------
 
     def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir):
@@ -173,12 +139,11 @@
                 continue
             if self.compile:
                 bytecode_files.append(py_file + "c")
-            if self.optimize > 0:
+            if self.optimize:
                 bytecode_files.append(py_file + "o")
 
         return bytecode_files
 
-
     # -- External interface --------------------------------------------
     # (called by outsiders)
 
diff --git a/distutils2/errors.py b/distutils2/errors.py
--- a/distutils2/errors.py
+++ b/distutils2/errors.py
@@ -72,10 +72,6 @@
     """Syntax error in a file list template."""
 
 
-class PackagingByteCompileError(PackagingError):
-    """Byte compile error."""
-
-
 class PackagingPyPIError(PackagingError):
     """Any problem occuring during using the indexes."""
 
diff --git a/distutils2/tests/support.py b/distutils2/tests/support.py
--- a/distutils2/tests/support.py
+++ b/distutils2/tests/support.py
@@ -61,7 +61,7 @@
     # misc. functions and decorators
     'fake_dec', 'create_distribution', 'copy_xxmodule_c', 'fixup_build_ext',
     # imported from this module for backport purposes
-    'unittest', 'requires_zlib', 'skip_unless_symlink',
+    'unittest', 'requires_zlib', 'skip_2to3_optimize', 'skip_unless_symlink',
 ]
 
 
@@ -362,6 +362,10 @@
         'requires test.test_support.skip_unless_symlink')
 
 
+skip_2to3_optimize = unittest.skipUnless(__debug__,
+                                         "2to3 doesn't work under -O")
+
+
 requires_zlib = unittest.skipUnless(zlib, 'requires zlib')
 
 
diff --git a/distutils2/tests/test_command_build_py.py b/distutils2/tests/test_command_build_py.py
--- a/distutils2/tests/test_command_build_py.py
+++ b/distutils2/tests/test_command_build_py.py
@@ -54,17 +54,12 @@
         # This makes sure the list of outputs includes byte-compiled
         # files for Python modules but not for package data files
         # (there shouldn't *be* byte-code files for those!).
-        #
         self.assertEqual(len(cmd.get_outputs()), 3)
         pkgdest = os.path.join(destination, "pkg")
         files = os.listdir(pkgdest)
         self.assertIn("__init__.py", files)
         self.assertIn("README.txt", files)
-        # XXX even with -O, distutils writes pyc, not pyo; bug?
-        if getattr(sys , 'dont_write_bytecode', False):
-            self.assertNotIn("__init__.pyc", files)
-        else:
-            self.assertIn("__init__.pyc", files)
+        self.assertIn("__init__.pyc", files)
 
     def test_empty_package_dir(self):
         # See SF 1668596/1720897.
@@ -99,23 +94,44 @@
             os.chdir(cwd)
             sys.stdout = old_stdout
 
-    @unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'),
-                         'sys.dont_write_bytecode not supported')
-    def test_dont_write_bytecode(self):
-        # makes sure byte_compile is not used
-        pkg_dir, dist = self.create_dist()
+    def test_byte_compile(self):
+        project_dir, dist = self.create_dist(py_modules=['boiledeggs'])
+        os.chdir(project_dir)
+        self.write_file('boiledeggs.py', 'import antigravity')
+        cmd = build_py(dist)
+        cmd.compile = True
+        cmd.build_lib = 'here'
+        cmd.finalize_options()
+        cmd.run()
+
+        found = os.listdir(cmd.build_lib)
+        self.assertEqual(sorted(found), ['boiledeggs.py', 'boiledeggs.pyc'])
+
+    def test_byte_compile_optimized(self):
+        project_dir, dist = self.create_dist(py_modules=['boiledeggs'])
+        os.chdir(project_dir)
+        self.write_file('boiledeggs.py', 'import antigravity')
         cmd = build_py(dist)
         cmd.compile = True
         cmd.optimize = 1
+        cmd.build_lib = 'here'
+        cmd.finalize_options()
+        cmd.run()
 
-        old_dont_write_bytecode = sys.dont_write_bytecode
+        found = os.listdir(cmd.build_lib)
+        self.assertEqual(sorted(found),
+                         ['boiledeggs.py', 'boiledeggs.pyc', 'boiledeggs.pyo'])
+
+    @unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'),
+                         'sys.dont_write_bytecode not supported')
+    def test_byte_compile_under_B(self):
+        # make sure byte compilation works under -B (dont_write_bytecode)
+        self.addCleanup(setattr, sys, 'dont_write_bytecode',
+                        sys.dont_write_bytecode)
         sys.dont_write_bytecode = True
-        try:
-            cmd.byte_compile([])
-        finally:
-            sys.dont_write_bytecode = old_dont_write_bytecode
+        self.test_byte_compile()
+        self.test_byte_compile_optimized()
 
-        self.assertIn('byte-compiling is disabled', self.get_logs()[0])
 
 def test_suite():
     return unittest.makeSuite(BuildPyTestCase)
diff --git a/distutils2/tests/test_command_install_dist.py b/distutils2/tests/test_command_install_dist.py
--- a/distutils2/tests/test_command_install_dist.py
+++ b/distutils2/tests/test_command_install_dist.py
@@ -182,9 +182,11 @@
     def test_old_record(self):
         # test pre-PEP 376 --record option (outside dist-info dir)
         install_dir = self.mkdtemp()
-        project_dir, dist = self.create_dist(scripts=['hello'])
+        project_dir, dist = self.create_dist(py_modules=['hello'],
+                                             scripts=['sayhi'])
         os.chdir(project_dir)
-        self.write_file('hello', "print 'o hai'")
+        self.write_file('hello.py', "def main(): print 'o hai'")
+        self.write_file('sayhi', 'from hello import main; main()')
 
         cmd = install_dist(dist)
         dist.command_obj['install_dist'] = cmd
@@ -200,8 +202,9 @@
             f.close()
 
         found = [os.path.basename(line) for line in content.splitlines()]
-        expected = ['hello', 'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD']
-        self.assertEqual(found, expected)
+        expected = ['hello.py', 'hello.pyc', 'sayhi',
+                    'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD']
+        self.assertEqual(sorted(found), sorted(expected))
 
         # XXX test that fancy_getopt is okay with options named
         # record and no-record but unrelated
diff --git a/distutils2/tests/test_command_install_lib.py b/distutils2/tests/test_command_install_lib.py
--- a/distutils2/tests/test_command_install_lib.py
+++ b/distutils2/tests/test_command_install_lib.py
@@ -1,6 +1,6 @@
 """Tests for distutils2.command.install_data."""
+import os
 import sys
-import os
 
 from distutils2.tests import unittest, support
 from distutils2.command.install_lib import install_lib
@@ -16,7 +16,7 @@
     restore_environ = ['PYTHONPATH']
 
     def test_finalize_options(self):
-        pkg_dir, dist = self.create_dist()
+        dist = self.create_dist()[1]
         cmd = install_lib(dist)
 
         cmd.finalize_options()
@@ -33,71 +33,73 @@
         cmd.finalize_options()
         self.assertEqual(cmd.optimize, 2)
 
-    @unittest.skipIf(getattr(sys, 'dont_write_bytecode', False),
-                     'byte-compile disabled')
     def test_byte_compile(self):
-        pkg_dir, dist = self.create_dist()
+        project_dir, dist = self.create_dist()
+        os.chdir(project_dir)
         cmd = install_lib(dist)
         cmd.compile = True
         cmd.optimize = 1
 
-        f = os.path.join(pkg_dir, 'foo.py')
+        f = os.path.join(project_dir, 'foo.py')
         self.write_file(f, '# python file')
         cmd.byte_compile([f])
-        self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'foo.pyc')))
-        self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'foo.pyo')))
+        self.assertTrue(os.path.exists(f + 'c'))
+        self.assertTrue(os.path.exists(f + 'o'))
+
+    @unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'),
+                         'sys.dont_write_bytecode not supported')
+    def test_byte_compile_under_B(self):
+        # make sure byte compilation works under -B (dont_write_bytecode)
+        self.addCleanup(setattr, sys, 'dont_write_bytecode',
+                        sys.dont_write_bytecode)
+        sys.dont_write_bytecode = True
+        self.test_byte_compile()
 
     def test_get_outputs(self):
-        pkg_dir, dist = self.create_dist()
+        project_dir, dist = self.create_dist()
+        os.chdir(project_dir)
+        os.mkdir('spam')
         cmd = install_lib(dist)
 
         # setting up a dist environment
         cmd.compile = True
         cmd.optimize = 1
-        cmd.install_dir = pkg_dir
-        f = os.path.join(pkg_dir, '__init__.py')
+        cmd.install_dir = self.mkdtemp()
+        f = os.path.join(project_dir, 'spam', '__init__.py')
         self.write_file(f, '# python package')
         cmd.distribution.ext_modules = [Extension('foo', ['xxx'])]
-        cmd.distribution.packages = [pkg_dir]
+        cmd.distribution.packages = ['spam']
 
-        # make sure the build_lib is set the temp dir
-        build_dir = os.path.split(pkg_dir)[0]
+        # make sure the build_lib is set the temp dir  # XXX what?  this is not
+        # needed in the same distutils test and should work without manual
+        # intervention
+        build_dir = os.path.split(project_dir)[0]
         cmd.get_finalized_command('build_py').build_lib = build_dir
 
-        # get_output should return 4 elements
-        self.assertEqual(len(cmd.get_outputs()), 4)
+        # get_outputs should return 4 elements: spam/__init__.py, .pyc and
+        # .pyo, foo.so / foo.pyd
+        outputs = cmd.get_outputs()
+        self.assertEqual(len(outputs), 4, outputs)
 
     def test_get_inputs(self):
-        pkg_dir, dist = self.create_dist()
+        project_dir, dist = self.create_dist()
+        os.chdir(project_dir)
+        os.mkdir('spam')
         cmd = install_lib(dist)
 
         # setting up a dist environment
         cmd.compile = True
         cmd.optimize = 1
-        cmd.install_dir = pkg_dir
-        f = os.path.join(pkg_dir, '__init__.py')
+        cmd.install_dir = self.mkdtemp()
+        f = os.path.join(project_dir, 'spam', '__init__.py')
         self.write_file(f, '# python package')
         cmd.distribution.ext_modules = [Extension('foo', ['xxx'])]
-        cmd.distribution.packages = [pkg_dir]
+        cmd.distribution.packages = ['spam']
 
-        # get_input should return 2 elements
-        self.assertEqual(len(cmd.get_inputs()), 2)
-
-    @unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'),
-                         'sys.dont_write_bytecode not supported')
-    def test_dont_write_bytecode(self):
-        # makes sure byte_compile is not used
-        pkg_dir, dist = self.create_dist()
-        cmd = install_lib(dist)
-        cmd.compile = True
-        cmd.optimize = 1
-
-        self.addCleanup(setattr, sys, 'dont_write_bytecode',
-                        sys.dont_write_bytecode)
-        sys.dont_write_bytecode = True
-        cmd.byte_compile([])
-
-        self.assertIn('byte-compiling is disabled', self.get_logs()[0])
+        # get_inputs should return 2 elements: spam/__init__.py and
+        # foo.so / foo.pyd
+        inputs = cmd.get_inputs()
+        self.assertEqual(len(inputs), 2, inputs)
 
 
 def test_suite():
diff --git a/distutils2/tests/test_mixin2to3.py b/distutils2/tests/test_mixin2to3.py
--- a/distutils2/tests/test_mixin2to3.py
+++ b/distutils2/tests/test_mixin2to3.py
@@ -10,6 +10,7 @@
                         unittest.TestCase):
 
     @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
+    @support.skip_2to3_optimize
     def test_convert_code_only(self):
         # used to check if code gets converted properly.
         code = "print 'test'"
diff --git a/distutils2/tests/test_util.py b/distutils2/tests/test_util.py
--- a/distutils2/tests/test_util.py
+++ b/distutils2/tests/test_util.py
@@ -9,7 +9,7 @@
 from StringIO import StringIO
 
 from distutils2.errors import (
-    PackagingPlatformError, PackagingByteCompileError, PackagingFileError,
+    PackagingPlatformError, PackagingFileError,
     PackagingExecError, InstallationException)
 from distutils2 import util
 from distutils2.dist import Distribution
@@ -137,15 +137,8 @@
             self._uname = None
         os.uname = self._get_uname
 
-        # patching POpen
-        self.old_find_executable = util.find_executable
-        util.find_executable = self._find_executable
-        self._exes = {}
-        self.old_popen = subprocess.Popen
-        self.old_stdout = sys.stdout
-        self.old_stderr = sys.stderr
-        FakePopen.test_class = self
-        subprocess.Popen = FakePopen
+    def _get_uname(self):
+        return self._uname
 
     def tearDown(self):
         # getting back the environment
@@ -160,17 +153,24 @@
             os.uname = self.uname
         else:
             del os.uname
+        super(UtilTestCase, self).tearDown()
+
+    def mock_popen(self):
+        self.old_find_executable = util.find_executable
+        util.find_executable = self._find_executable
+        self._exes = {}
+        self.old_popen = subprocess.Popen
+        self.old_stdout = sys.stdout
+        self.old_stderr = sys.stderr
+        FakePopen.test_class = self
+        subprocess.Popen = FakePopen
+        self.addCleanup(self.unmock_popen)
+
+    def unmock_popen(self):
         util.find_executable = self.old_find_executable
         subprocess.Popen = self.old_popen
-        sys.old_stdout = self.old_stdout
-        sys.old_stderr = self.old_stderr
-        super(UtilTestCase, self).tearDown()
-
-    def _set_uname(self, uname):
-        self._uname = uname
-
-    def _get_uname(self):
-        return self._uname
+        sys.stdout = self.old_stdout
+        sys.stderr = self.old_stderr
 
     def test_convert_path(self):
         # linux/mac
@@ -282,6 +282,7 @@
         return None
 
     def test_get_compiler_versions(self):
+        self.mock_popen()
         # get_versions calls distutils.spawn.find_executable on
         # 'gcc', 'ld' and 'dllwrap'
         self.assertEqual(get_compiler_versions(), (None, None, None))
@@ -324,15 +325,12 @@
 
     @unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'),
                          'sys.dont_write_bytecode not supported')
-    def test_dont_write_bytecode(self):
-        # makes sure byte_compile raise a PackagingError
-        # if sys.dont_write_bytecode is True
-        old_dont_write_bytecode = sys.dont_write_bytecode
+    def test_byte_compile_under_B(self):
+        # make sure byte compilation works under -B (dont_write_bytecode)
+        self.addCleanup(setattr, sys, 'dont_write_bytecode',
+                        sys.dont_write_bytecode)
         sys.dont_write_bytecode = True
-        try:
-            self.assertRaises(PackagingByteCompileError, byte_compile, [])
-        finally:
-            sys.dont_write_bytecode = old_dont_write_bytecode
+        byte_compile([])
 
     def test_newer(self):
         self.assertRaises(PackagingFileError, util.newer, 'xxx', 'xxx')
@@ -420,6 +418,7 @@
         self.assertRaises(ImportError, resolve_name, 'a.b.c.Spam')
 
     @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
+    @support.skip_2to3_optimize
     def test_run_2to3_on_code(self):
         content = "print 'test'"
         converted_content = "print('test')"
@@ -434,6 +433,7 @@
         self.assertEqual(new_content, converted_content)
 
     @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
+    @support.skip_2to3_optimize
     def test_run_2to3_on_doctests(self):
         # to check if text files containing doctests only get converted.
         content = ">>> print 'test'\ntest\n"
@@ -451,8 +451,6 @@
     @unittest.skipUnless(os.name in ('nt', 'posix'),
                          'runs only under posix or nt')
     def test_spawn(self):
-        # no patching of Popen here
-        subprocess.Popen = self.old_popen
         tmpdir = self.mkdtemp()
 
         # creating something executable
@@ -549,8 +547,6 @@
         self.assertEqual(args['py_modules'], dist.py_modules)
 
     def test_generate_setup_py(self):
-        # undo subprocess.Popen monkey-patching before using assert_python_*
-        subprocess.Popen = self.old_popen
         os.chdir(self.mkdtemp())
         self.write_file('setup.cfg', textwrap.dedent("""\
             [metadata]
diff --git a/distutils2/util.py b/distutils2/util.py
--- a/distutils2/util.py
+++ b/distutils2/util.py
@@ -24,8 +24,8 @@
 
 from distutils2 import logger
 from distutils2.errors import (PackagingPlatformError, PackagingFileError,
-                               PackagingByteCompileError, PackagingExecError,
-                               InstallationException, PackagingInternalError)
+                               PackagingExecError, InstallationException,
+                               PackagingInternalError)
 from distutils2._backport import sysconfig
 
 __all__ = [
@@ -301,7 +301,7 @@
 
 
 def byte_compile(py_files, optimize=0, force=False, prefix=None,
-                 base_dir=None, verbose=0, dry_run=False, direct=None):
+                 base_dir=None, dry_run=False, direct=None):
     """Byte-compile a collection of Python source files to either .pyc
     or .pyo files in the same directory.
 
@@ -310,6 +310,9 @@
       0 - don't optimize (generate .pyc)
       1 - normal optimization (like "python -O")
       2 - extra optimization (like "python -OO")
+    This function is independent from the running Python's -O or -B options;
+    it is fully controlled by the parameters passed in.
+
     If 'force' is true, all files are recompiled regardless of
     timestamps.
 
@@ -331,10 +334,7 @@
     generated in indirect mode; unless you know what you're doing, leave
     it set to None.
     """
-    # nothing is done if sys.dont_write_bytecode is True
-    # FIXME this should not raise an error
-    if getattr(sys, 'dont_write_bytecode', False):
-        raise PackagingByteCompileError('byte-compiling is disabled.')
+    # FIXME use compileall + remove direct/indirect shenanigans
 
     # First, if the caller didn't force us into direct or indirect mode,
     # figure out which mode we should be in.  We take a conservative
@@ -388,17 +388,13 @@
                 script.write("""
 byte_compile(files, optimize=%r, force=%r,
              prefix=%r, base_dir=%r,
-             verbose=%r, dry_run=False,
+             dry_run=False,
              direct=True)
-""" % (optimize, force, prefix, base_dir, verbose))
+""" % (optimize, force, prefix, base_dir))
             finally:
                 script.close()
 
         cmd = [sys.executable, script_name]
-        if optimize == 1:
-            cmd.insert(1, "-O")
-        elif optimize == 2:
-            cmd.insert(1, "-OO")
 
         env = os.environ.copy()
         env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
@@ -424,8 +420,9 @@
             # Terminology from the py_compile module:
             #   cfile - byte-compiled file
             #   dfile - purported source filename (same as 'file' by default)
-            cfile = file + (__debug__ and "c" or "o")
+            cfile = file + (optimize and 'o' or 'c')
             dfile = file
+
             if prefix:
                 if file[:len(prefix)] != prefix:
                     raise ValueError("invalid prefix: filename %r doesn't "

-- 
Repository URL: http://hg.python.org/distutils2


More information about the Python-checkins mailing list