changeset: 1237:7c0a88497b5c user: Éric Araujo date: Sat Nov 12 02:58:14 2011 +0100 files: CHANGES.txt distutils2/command/build_py.py distutils2/command/cmd.py distutils2/command/install_lib.py distutils2/errors.py distutils2/tests/support.py distutils2/tests/test_command_build_py.py distutils2/tests/test_command_install_dist.py distutils2/tests/test_command_install_lib.py distutils2/tests/test_mixin2to3.py distutils2/tests/test_util.py distutils2/util.py description: 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). diff -r 18aa8ff947f4 -r 7c0a88497b5c CHANGES.txt --- a/CHANGES.txt Sat Nov 12 00:42:41 2011 +0100 +++ b/CHANGES.txt Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/command/build_py.py --- a/distutils2/command/build_py.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/command/build_py.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/command/cmd.py --- a/distutils2/command/cmd.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/command/cmd.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/command/install_lib.py --- a/distutils2/command/install_lib.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/command/install_lib.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/errors.py --- a/distutils2/errors.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/errors.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/tests/support.py --- a/distutils2/tests/support.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/tests/support.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/tests/test_command_build_py.py --- a/distutils2/tests/test_command_build_py.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/tests/test_command_build_py.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/tests/test_command_install_dist.py --- a/distutils2/tests/test_command_install_dist.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/tests/test_command_install_dist.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/tests/test_command_install_lib.py --- a/distutils2/tests/test_command_install_lib.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/tests/test_command_install_lib.py Sat Nov 12 02:58:14 2011 +0100 @@ -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] - - # get_input should return 2 elements - self.assertEqual(len(cmd.get_inputs()), 2) + cmd.distribution.packages = ['spam'] - @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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/tests/test_mixin2to3.py --- a/distutils2/tests/test_mixin2to3.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/tests/test_mixin2to3.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/tests/test_util.py --- a/distutils2/tests/test_util.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/tests/test_util.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 - 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 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 _get_uname(self): - return self._uname + def unmock_popen(self): + util.find_executable = self.old_find_executable + subprocess.Popen = self.old_popen + 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 -r 18aa8ff947f4 -r 7c0a88497b5c distutils2/util.py --- a/distutils2/util.py Sat Nov 12 00:42:41 2011 +0100 +++ b/distutils2/util.py Sat Nov 12 02:58:14 2011 +0100 @@ -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 "