diff -r 2d6d75eaf98c -r c5692393c621 Lib/packaging/cli.exe Binary file Lib/packaging/cli.exe has changed diff -r 2d6d75eaf98c -r c5692393c621 Lib/packaging/command/build_scripts.py --- a/Lib/packaging/command/build_scripts.py Fri Jul 15 01:13:24 2011 +0200 +++ b/Lib/packaging/command/build_scripts.py Sat Jul 23 22:06:21 2011 +0800 @@ -1,6 +1,7 @@ """Build scripts (copy to build dir and fix up shebang line).""" import os +import sys import re import sysconfig import tokenize @@ -10,10 +11,14 @@ from packaging import logger from packaging.compat import Mixin2to3 +from packaging.errors import PackagingOptionError + # check if Python is called on the first line with this expression first_line_re = re.compile(b'^#!.*python[0-9.]*([ \t].*)?$') +sys_executable = os.path.normpath(sys.executable) + class build_scripts(Command, Mixin2to3): description = "build scripts (copy and fix up shebang line)" @@ -36,6 +41,7 @@ self.use_2to3 = False self.convert_2to3_doctests = None self.use_2to3_fixers = None + self.wrapper_scripts_entries = None def finalize_options(self): self.set_undefined_options('build', @@ -44,17 +50,80 @@ 'convert_2to3_doctests', 'force', 'executable') self.scripts = self.distribution.scripts + self.wrapper_scripts_entries = self.distribution.wrapper_scripts_entries def get_source_files(self): return self.scripts def run(self): - if not self.scripts: - return - copied_files = self.copy_scripts() - if self.use_2to3 and copied_files: - self._run_2to3(copied_files, fixers=self.use_2to3_fixers) + if self.scripts: + copied_files = self.copy_scripts() + if self.use_2to3 and copied_files: + self._run_2to3(copied_files, fixers=self.use_2to3_fixers) + if self.wrapper_scripts_entries: + self._check_entries() + self.install_wrapper_scripts() + def _check_entries(self): + """Check the offered entries is correct or not: dotted path should always + match an existed multilevel directory path, for instance, if an entry + 'test=foo.bar.script' is offered, then there should be a script.py file + under the '{curdir}/foo/bar' path.""" + if self.wrapper_scripts_entries: + for entry in self.wrapper_scripts_entries: + en = entry.split('=') + dotted_string = en[1].strip() + pos = dotted_string.rfind(':') + dotted_path = dotted_string[:pos] + os_dir_path = dotted_path.replace('.', os.sep) + '.py' + if not os.path.exists(os_dir_path): + raise PackagingOptionError("your specific entry '%s' does not exist!" % entry) + def install_wrapper_scripts(self): + """Create wrapper scripts for different entry points which have already + been listed in 'self.wrapper_scripts'. + """ + if self.wrapper_scripts_entries: + for args in self._get_script_args(): + self._write_script(*args) + + def _get_script_args(self, executable=sys_executable, wininst=False): + header = get_script_header("", executable, wininst) + for entry in self.wrapper_scripts_entries: + en = entry.split('=') + script_name = en[0].strip() + dotted_string = en[1].strip() + pos = dotted_string.rfind(':') + from_package = dotted_string[:pos] + main_func = dotted_string[pos+1:] + script_text = ( + "from %(from_package)s import %(main_func)s\n" + "%(main_func)s()\n") % locals() + if sys.platform == 'win32' or wininst: + # should always use this extension if we want to create an .exe on windows + ext = '-script.py' + yield (script_name + ext, header+script_text, 't') + # reuse cli.exe copied from setuptools + yield (script_name + '.exe', get_resource_string('cli.exe'), 'b') + else: + # on other platforms, generate script with no extension + yield(script_name, header+script_text) + + def _write_script(self, script_name, contents, mode="t"): + script_dir = self.build_dir # take build_dir as script installing dir + logger.info("installing %s script to %r", script_name, script_dir) + target = os.path.join(script_dir, script_name) + #add to outputs? + if not self.dry_run: + ensure_directory(target) + with open(target, "w" + mode) as f: + f.write(contents) + f.close() + logger.debug("changing mode of %r to %o", target, mode) + try: + os.chmod(target, 0o755) + except os.error: + logger.debug("changing mode of %r failed", target) + def copy_scripts(self): """Copy each script listed in 'self.scripts'; if it's marked as a Python script in the Unix way (first line matches 'first_line_re', @@ -152,3 +221,48 @@ file, oldmode, newmode) os.chmod(file, newmode) return outfiles + +# The following functions are copied from setuptools + +def get_script_header(script_text, executable=sys_executable, wininst=False): + """Create a #! line, getting options (if any) from script_text""" + first = (script_text.encode()+b'\n').splitlines()[0] + match = first_line_re.match(first) + options = '' + if match: + options = match.group(1).decode() or '' + if options: options = ' '+options + if wininst: + executable = "python.exe" + else: + # executable = nt_quote_arg(executable) + # what's the usage of nt_quote_arg? + pass + hdr = "#!%(executable)s%(options)s\n" % locals() + if options: + if options.strip().startswith('-'): + options = ' -x' + options.strip()[1:] + else: + options = ' -x' + # fix_jython_executable here? + hdr = "#!%(executable)s%(options)s\n" % locals() + return hdr + +def get_resource_string(resource_name): + """Get the resource string from a specific resource.""" + # first get the installed packaging path + import packaging + packaging_path = os.path.dirname(getattr(packaging, '__file__', '')) + # then get the resource path + resource_path = packaging_path + if resource_name: + resource_path = os.path.join(packaging_path, *resource_name.split('/')) + # read the source and return the content + with open(resource_path, 'rb') as f: + return f.read() + +def ensure_directory(path): + """Ensure that the parent directory of 'path' exists.""" + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) \ No newline at end of file diff -r 2d6d75eaf98c -r c5692393c621 Lib/packaging/dist.py --- a/Lib/packaging/dist.py Fri Jul 15 01:13:24 2011 +0200 +++ b/Lib/packaging/dist.py Sat Jul 23 22:06:21 2011 +0800 @@ -193,6 +193,7 @@ self.include_dirs = [] self.extra_path = None self.scripts = [] + self.wrapper_scripts_entries = [] # entries of different wrapper scripts self.data_files = {} self.password = '' self.use_2to3 = False diff -r 2d6d75eaf98c -r c5692393c621 Lib/packaging/gui.exe Binary file Lib/packaging/gui.exe has changed diff -r 2d6d75eaf98c -r c5692393c621 Lib/packaging/tests/test_command_build_scripts.py --- a/Lib/packaging/tests/test_command_build_scripts.py Fri Jul 15 01:13:24 2011 +0200 +++ b/Lib/packaging/tests/test_command_build_scripts.py Sat Jul 23 22:06:21 2011 +0800 @@ -1,12 +1,16 @@ """Tests for distutils.command.build_scripts.""" import os +import re import sys import sysconfig from packaging.dist import Distribution from packaging.command.build_scripts import build_scripts from packaging.tests import unittest, support +from packaging.tests import captured_stdout + +from packaging.errors import PackagingOptionError class BuildScriptsTestCase(support.TempdirManager, @@ -38,9 +42,68 @@ for name in expected: self.assertIn(name, built) - def get_build_scripts_cmd(self, target, scripts): + def test_install_wrapper_scripts(self): + source = self.mkdtemp() + target = self.mkdtemp() + # create a sample module first + tmp_mains_dir = self.create_sample_module(source, 'foo.bar') + + # first check a wrapper entry which doesn't actually exist + wrapper_entries = ['xxx=foo.bar:xxx'] + cmd = self.get_build_scripts_cmd(target, None, wrapper_entries) + cmd.finalize_options() + # should report error here: entries does not exist + self.assertRaises(PackagingOptionError, cmd.run) + + # create sample functions under the sample module + mains = self.create_sample_main_functions(tmp_mains_dir) + wrapper_entries = ["script%d=foo.bar.%s:main"%(i+1,main[:-3]) for i,main in enumerate(mains)] + + cmd = self.get_build_scripts_cmd(target, None, wrapper_entries) + cmd.finalize_options() + cmd.run() + + # scripts should be created and the shebang line of each script should be correct + wrapper_exes = None + if sys.platform == 'win32': + wrapper_scripts = [os.path.join(target, entry[:7] + '-script.py') for entry in wrapper_entries] + wrapper_exes = [os.path.join(target, entry[:7] + '.exe')for entry in wrapper_entries] + else: + # no extension + wrapper_scripts = [os.path.join(target, entry[:7]) for entry in wrapper_entries] + + # we should create a more tolerated regular expression than 'first_line_re' in build_scripts + # in our test, because we may run tests in debug mode + first_line_re = re.compile(b'^#!.*python[0-9.]*.*([ \t].*)?$') + for script in wrapper_scripts: + self.assertTrue(script) + with open(script, "rb") as f: + self.assertIsNotNone(first_line_re.match(f.readline().rstrip())) + + # append the path of our 'source' into sys.path, thus script can import the module normally + sys.path.append(source) + + # check if all of the wrapper scripts are runnable and can output the correct content + for script in wrapper_scripts: + with open(script, "r") as f: + res,std_out = captured_stdout(exec, f.read()) + self.assertEqual(std_out.strip(), "Hello World!") + + # check if packaging also generates .exe files on windows and can generate correct output + env = dict(os.environ) + env["PYTHONPATH"] = source + if wrapper_exes: + import subprocess + for exe in wrapper_exes: + self.assertTrue(os.path.exists(exe)) + p = subprocess.Popen(exe, stdout = subprocess.PIPE, env = env) + out = p.communicate()[0].strip() + self.assertEqual(out, b'Hello World!') + + def get_build_scripts_cmd(self, target, scripts, wrapper_entries = None): dist = Distribution() dist.scripts = scripts + dist.wrapper_scripts_entries = wrapper_entries dist.command_obj["build"] = support.DummyCommand( build_scripts=target, force=True, @@ -51,6 +114,27 @@ ) return build_scripts(dist) + def create_sample_module(self, source, dotted_path): + dirs = dotted_path.split('.') + os.chdir(source) # should always first in source dir + for dir in dirs: + os.mkdir(dir) + os.chdir(dir) + open('__init__.py', 'w').close() + module_path = os.path.realpath(os.curdir) + os.chdir(source) + return module_path + + def create_sample_main_functions(self, dir): + mains = [] + mains.append("main1.py") + self.write_script(dir, "main1.py", + ("def main():\n print('Hello World!')\n")) + mains.append("main2.py") + self.write_script(dir, "main2.py", + ("def main():\n print('Hello World!')\n")) + return mains + def write_sample_scripts(self, dir): expected = [] expected.append("script1.py")