def test_custom_executable(self): fn = os.path.join(HERE, 'dummy-0.1-py27-none-any.whl') for executable in 'mypython', None: dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) w = Wheel(fn) paths = {'prefix': dstdir} for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): paths[key] = os.path.join(dstdir, key) maker = ScriptMaker(None, None) maker.variants = set(['']) maker.executable = executable w.install(paths, maker) # On Windows there will be an exe file, and on POSIX a text file. # The test is structured to not care. p = paths['scripts'] # there should be just one file in the directory - dummy.py/dummy.exe p = os.path.join(p, os.listdir(p)[0]) with open(p, 'rb') as f: data = f.read() if executable is None: expected = fsencode(get_executable()) else: expected = executable.encode('utf-8') expected = b'#!' + expected + b' -E' self.assertIn(expected, data)
def test_launcher_run_with_interpreter_args(self): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) maker = ScriptMaker(srcdir, dstdir, add_launchers=True) # add '-O' option to shebang to run in optimized mode with open(os.path.join(HERE, 'scripts', 'script6.py'), 'r') as src: with open(os.path.join(srcdir, 'script6-optimized.py'), 'w') as dst: shebang = src.readline().rstrip() dst.write(shebang + " -O\n") dst.write(src.read()) files = maker.make('script6-optimized.py') self.assertEqual(len(files), 1) p = subprocess.Popen([files[0], 'Test Argument'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate('input'.encode('ascii')) actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(""" script6-optimized.exe ['Test Argument'] 'input' """).lstrip() # 'non-optimized' is not printed this time self.assertEqual(actual, expected)
def test_custom_executable(self): fn = os.path.join(HERE, "dummy-0.1-py27-none-any.whl") for executable in "mypython", None: dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) w = Wheel(fn) paths = {"prefix": dstdir} for key in ("purelib", "platlib", "headers", "scripts", "data"): paths[key] = os.path.join(dstdir, key) maker = ScriptMaker(None, None) maker.variants = set([""]) maker.executable = executable w.install(paths, maker) # On Windows there will be an exe file, and on POSIX a text file. # The test is structured to not care. p = paths["scripts"] # there should be just one file in the directory - dummy.py/dummy.exe p = os.path.join(p, os.listdir(p)[0]) with open(p, "rb") as f: data = f.read() if executable is None: expected = fsencode(get_executable()) else: expected = executable.encode("utf-8") expected = b"#!" + expected + b" -E" if not sysconfig.is_python_build(): self.assertIn(expected, data)
def test_custom_executable(self): fn = os.path.join(HERE, 'dummy-0.1-py27-none-any.whl') for executable in 'mypython', None: dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) w = Wheel(fn) paths = {'prefix': dstdir} for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): paths[key] = os.path.join(dstdir, key) maker = ScriptMaker(None, None) maker.variants = set(['']) maker.executable = executable w.install(paths, maker) # On Windows there will be an exe file, and on POSIX a text file. # The test is structured to not care. p = paths['scripts'] # there should be just one file in the directory - dummy.py/dummy.exe p = os.path.join(p, os.listdir(p)[0]) with open(p, 'rb') as f: data = f.read() if executable is None: expected = fsencode(get_executable()) else: expected = executable.encode('utf-8') expected = b'#!' + expected + b' -E' if not sysconfig.is_python_build(): self.assertIn(expected, data)
def update_shebangs(self, new_path: str) -> None: """Update the shebang lines""" scripts = self.get_paths()["scripts"] maker = ScriptMaker(None, None) maker.executable = new_path shebang = maker._get_shebang("utf-8").rstrip() for child in Path(scripts).iterdir(): if not child.is_file() or child.suffix not in (".exe", ".py", ""): continue child.write_bytes( re.sub(rb"#!.+?python.*?$", shebang, child.read_bytes(), flags=re.M) )
def fix_entry_points(*distribution_names): """Re-make all entry_points for the given distributions. This is a workaround for the fact that conda doesn't support GUI scripts. The filenames on disk remain the same, so conda can still uninstall cleanly - we're just fixing the files in-place""" # There are many places scripts can go, but we're in conda so the main scripts dir # at sysconfig.get_path('scripts') is the only possibility for us: maker = ScriptMaker(None, sysconfig.get_path('scripts')) maker.clobber = True # Overwrite existing scripts maker.variants = {''} # Don't make variants with Python major.minor suffixes for name in distribution_names: # Get the script specs and format them how Scriptmaker wants them distribution = importlib_metadata.Distribution.from_name(name) # I've seen enough bugs with entry_points in conda that I feel the need to # clobber its console_scripts too, even though they are officially supported: console_scripts = [ f'{e.name} = {e.value}' for e in distribution.entry_points if e.group == 'console_scripts' ] gui_scripts = [ f'{e.name} = {e.value}' for e in distribution.entry_points if e.group == 'gui_scripts' ] # Make 'em: maker.make_multiple(console_scripts) maker.make_multiple(gui_scripts, {'gui': True})
def test_version_incompatibility(self): class Warner(object): def __call__(self, wheel_version, file_version): self.wheel_version = wheel_version self.file_version = file_version fn = os.path.join(HERE, 'dummy-0.1-py27-none-any.whl') dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) w = Wheel(fn) paths = {'prefix': dstdir} for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): paths[key] = os.path.join(dstdir, key) warner = Warner() maker = ScriptMaker(None, None) w.install(paths, maker, warner=warner) self.assertEqual(warner.wheel_version, w.wheel_version) self.assertEqual(warner.file_version, (2, 0)) # Now set the wheel's instance to the higher value and ensure # warner isn't called warner = Warner() w.wheel_version = (2, 0) w.install(paths, maker, warner=warner) self.assertFalse(hasattr(warner, 'wheel_version')) self.assertFalse(hasattr(warner, 'file_version'))
def test_custom_shebang(self): # Construct an executable with a space in it self.maker.executable = 'an executable with spaces' filenames = self.maker.make('script1.py') with open(filenames[0], 'rb') as f: first_line = f.readline() second_line = f.readline() third_line = f.readline() self.assertEqual(first_line, b'#!/bin/sh\n') self.assertEqual(second_line, b"'''exec' an executable with " b'spaces "$0" "$@"\n') self.assertEqual(third_line, b"' '''\n") # Python 3.3 cannot create a venv in an existing directory if venv and sys.version_info[:2] >= (3, 4): if sys.platform == 'darwin': # Supposedly 512, but various symlinks mean that temp folder # names get larger than you'd expect ... might vary on different # OS versions, too dlen = 220 else: dlen = 127 dstdir = tempfile.mkdtemp(suffix='cataaaaaa' + 'a' * dlen) self.addCleanup(shutil.rmtree, dstdir) bindir = os.path.join(dstdir, 'bin') maker = ScriptMaker(self.maker.source_dir, bindir, add_launchers=False) venv.create(dstdir) maker.executable = os.path.join(bindir, 'python') filenames = maker.make('script8.py') p = subprocess.Popen(filenames[0], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() self.assertEqual(p.returncode, 0) self.assertEqual(stderr, b'') expected = os.path.realpath(maker.executable) # symlinks on OS X actual = os.path.realpath(stdout.strip()) self.assertEqual(actual, expected.encode('utf-8'))
def test_shebangs_custom_executable(self): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) maker = ScriptMaker(srcdir, dstdir, add_launchers=False) maker.executable = 'this_should_appear_in_the_shebang_line' #let's create the script to be copied. It has a vanilla shebang line. fn = os.path.join(srcdir, 'copied') with open(fn, 'w') as f: f.write(COPIED_SCRIPT) # Let's ask the maker to copy the script, and see what the shebang is # in the copy. filenames = maker.make('copied') with open(filenames[0], 'r') as f: actual = f.readline() self.assertIn(maker.executable, actual) # Now let's make a script from a callable filenames = maker.make(MADE_SCRIPT) with open(filenames[0], 'r') as f: actual = f.readline() self.assertIn(maker.executable, actual)
def do_build_and_install(self, dist): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) paths = install_dist(dist, srcdir) paths['prefix'] = srcdir w = Wheel() w.name = paths.pop('name') w.version = paths.pop('version') w.dirname = srcdir pathname = w.build(paths) self.assertTrue(os.path.exists(pathname)) paths = {'prefix': dstdir} for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): paths[key] = os.path.join(dstdir, key) w = Wheel(pathname) maker = ScriptMaker(None, None, add_launchers=False) maker.executable = os.path.join(paths['scripts'], 'python') dist = w.install(paths, maker) self.assertIsNotNone(dist) self.assertEqual(dist.name, w.name) self.assertEqual(dist.version, w.version) shared = dist.shared_locations self.assertTrue(shared) os.remove(pathname) sm = Manifest(srcdir) sm.findall() sfiles = set([os.path.relpath(p, srcdir) for p in sm.allfiles]) dm = Manifest(dstdir) dm.findall() dfiles = set([os.path.relpath(p, dstdir) for p in dm.allfiles]) omitted = sfiles - dfiles omitted = omitted.pop() endings = os.path.join('.dist-info', 'WHEEL'), '.pyc', '.pyo' self.assertTrue(omitted.endswith(endings))
def do_build_and_install(self, dist): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) paths = install_dist(dist, srcdir) paths["prefix"] = srcdir w = Wheel() w.name = paths.pop("name") w.version = paths.pop("version") w.dirname = srcdir pathname = w.build(paths) self.assertTrue(os.path.exists(pathname)) paths = {"prefix": dstdir} for key in ("purelib", "platlib", "headers", "scripts", "data"): paths[key] = os.path.join(dstdir, key) w = Wheel(pathname) maker = ScriptMaker(None, None, add_launchers=False) maker.executable = os.path.join(paths["scripts"], "python") dist = w.install(paths, maker) self.assertIsNotNone(dist) self.assertEqual(dist.name, w.name) self.assertEqual(dist.version, w.version) shared = dist.shared_locations self.assertTrue(shared) os.remove(pathname) sm = Manifest(srcdir) sm.findall() sfiles = set([os.path.relpath(p, srcdir) for p in sm.allfiles]) dm = Manifest(dstdir) dm.findall() dfiles = set([os.path.relpath(p, dstdir) for p in dm.allfiles]) omitted = sfiles - dfiles omitted = omitted.pop() endings = os.path.join(".dist-info", "WHEEL"), ".pyc", ".pyo" self.assertTrue(omitted.endswith(endings))
def test_shebangs_custom_executable(self): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) maker = ScriptMaker(srcdir, dstdir, add_launchers=False) maker.executable = 'this_should_appear_in_the_shebang_line(中文)' # let's create the script to be copied. It has a vanilla shebang line, # with some Unicode in it. fn = os.path.join(srcdir, 'copied') with open(fn, 'w') as f: f.write(COPIED_SCRIPT) # Let's ask the maker to copy the script, and see what the shebang is # in the copy. filenames = maker.make('copied') with open(filenames[0], 'rb') as f: actual = f.readline().decode('utf-8') self.assertIn(maker.executable, actual) # Now let's make a script from a callable filenames = maker.make(MADE_SCRIPT) with open(filenames[0], 'rb') as f: actual = f.readline().decode('utf-8') self.assertIn(maker.executable, actual)
def _create_console_entry_point(self, name, value, to_folder, version_info): result = [] maker = ScriptMaker(None, str(to_folder)) maker.clobber = True # overwrite maker.variants = {""} # set within patch_distlib_correct_variants maker.set_mode = True # ensure they are executable # calling private until https://bitbucket.org/pypa/distlib/issues/135/expose-_enquote_executable-as-public maker.executable = _enquote_executable(str(self._creator.exe)) specification = "{} = {}".format(name, value) with self.patch_distlib_correct_variants(version_info, maker): new_files = maker.make(specification) result.extend(Path(i) for i in new_files) return result
def _create_console_entry_point(self, name, value, to_folder): result = [] from distlib.scripts import ScriptMaker maker = ScriptMaker(None, str(to_folder)) maker.clobber = True # overwrite maker.variants = {"", "X", "X.Y"} # create all variants maker.set_mode = True # ensure they are executable maker.executable = str(self._creator.exe) specification = "{} = {}".format(name, value) new_files = maker.make(specification) result.extend(Path(i) for i in new_files) return result
def __install_wheel( self, filepath: str, force: bool, upgrade: bool, options: dict, ) -> InstalledDistribution: '''Install wheel to selected paths.''' wheel = Wheel(filepath) try: wheel.verify() return wheel.install( paths=self.distribution_path.paths, maker=ScriptMaker(None, None), # bytecode_hashed_invalidation=True ) except DistlibException: print('wheel did not pass validation')
def _create_console_entry_point(self, name, value, to_folder): result = [] if IS_WIN: # windows doesn't support simple script files, so fallback to more complicated exe generator from distlib.scripts import ScriptMaker maker = ScriptMaker(None, str(to_folder)) maker.clobber = True # overwrite maker.variants = {"", "X", "X.Y"} # create all variants maker.set_mode = True # ensure they are executable maker.executable = str(self._creator.exe) specification = "{} = {}".format(name, value) new_files = maker.make(specification) result.extend(Path(i) for i in new_files) else: module, func = value.split(":") content = ( dedent( """ #!{0} # -*- coding: utf-8 -*- import re import sys from {1} import {2} if __name__ == "__main__": sys.argv[0] = re.sub(r"(-script.pyw?|.exe)?$", "", sys.argv[0]) sys.exit({2}()) """ ) .lstrip() .format(self._creator.exe, module, func) ) version = self._creator.interpreter.version_info for new_name in ( name, "{}{}".format(name, version.major), "{}-{}.{}".format(name, version.major, version.minor), ): exe = to_folder / new_name exe.write_text(content, encoding="utf-8") make_exe(exe) result.append(exe) return result
def install_wheels(wheels_folder, install_folder): logging.getLogger('distlib').setLevel(logging.ERROR) if not os.path.exists(install_folder): os.makedirs(install_folder) from distlib.wheel import Wheel from distlib.scripts import ScriptMaker paths = { 'prefix': '', 'purelib': install_folder, 'platlib': install_folder, 'scripts': '', 'headers': '', 'data': ''} files = os.listdir(wheels_folder) for f in [os.path.join(wheels_folder, f) for f in files]: wheel = Wheel(f) wheel.install(paths, ScriptMaker(None, None), lib_only=True)
def create_production_scripts(self, tool, venv_session): """Create Rez production used binary scripts The binary script will be executed with Python interpreter flag -E, which will ignore all PYTHON* env vars, e.g. PYTHONPATH and PYTHONHOME. """ _log.info("Generating production scripts..") site_packages = venv_session.creator.purelib bin_path = venv_session.creator.bin_dir if tool.edit: egg_link = site_packages / ("%s.egg-link" % tool.name) if not egg_link.is_file(): _log.error("Tool %r installed in edit mode, but unable " "to find egg-link for generating production " "scripts from source. File not exists: %s" % (tool.name, egg_link)) return with open(str(egg_link), "r") as f: package_location = f.readline().strip() path = [str(package_location)] else: path = [str(site_packages)] dists = Distribution.discover(name=tool.name, path=path) specifications = { ep.name: "{ep.name} = {ep.value}".format(ep=ep) for dist in dists for ep in dist.entry_points if ep.group == "console_scripts" } # delete bin files written into virtualenv # this also avoided naming conflict between script 'rez' and dir 'rez' for script_name in specifications.keys(): script_path = bin_path / script_name if script_path.is_file(): os.remove(str(script_path)) venv_name = tool.name if tool.isolation else "rez" prod_bin_path = self._revision.production_bin_dir(venv_name) makedirs(prod_bin_path) maker = ScriptMaker(source_dir=None, target_dir=str(prod_bin_path)) maker.executable = str(venv_session.creator.exe) # Align with wheel # # Ensure we don't generate any variants for scripts because this is # almost never what somebody wants. # See https://bitbucket.org/pypa/distlib/issue/35/ maker.variants = {""} # Ensure old scripts are overwritten. # See https://github.com/pypa/pip/issues/1800 maker.clobber = True # This is required because otherwise distlib creates scripts that are # not executable. # See https://bitbucket.org/pypa/distlib/issue/32/ maker.set_mode = True if self._rez_in_edit: # Allow pre-caching rez_bin_path on script entry if environ var # `REZUP_EDIT_IN_PRODUCTION` is set with non-empty value. # See https://github.com/davidlatwe/rezup/pull/56 maker.script_template = r'''# -*- coding: utf-8 -*- import re import os import sys from %(module)s import %(import_name)s if os.getenv("REZUP_EDIT_IN_PRODUCTION"): from rez.system import system setattr(system, 'rez_bin_path', r'{rez_bin_path}') if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.exit(%(func)s()) '''.format(rez_bin_path=str(prod_bin_path)) scripts = maker.make_multiple( specifications=specifications.values(), options=dict(interpreter_args=list(tool.flags))) return scripts
def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None, pycompile=True, scheme=None): """Install a wheel""" if not scheme: scheme = distutils_scheme(name, user=user, home=home, root=root) if root_is_purelib(name, wheeldir): lib_dir = scheme['purelib'] else: lib_dir = scheme['platlib'] info_dir = [] data_dirs = [] source = wheeldir.rstrip(os.path.sep) + os.path.sep # Record details of the files moved # installed = files copied from the wheel to the destination # changed = files changed while installing (scripts #! line typically) # generated = files newly generated during the install (script wrappers) installed = {} changed = set() generated = [] # Compile all of the pyc files that we're going to be installing if pycompile: compileall.compile_dir(source, force=True, quiet=True) def normpath(src, p): return make_path_relative(src, p).replace(os.path.sep, '/') def record_installed(srcfile, destfile, modified=False): """Map archive RECORD paths to installation RECORD paths.""" oldpath = normpath(srcfile, wheeldir) newpath = normpath(destfile, lib_dir) installed[oldpath] = newpath if modified: changed.add(destfile) def clobber(source, dest, is_base, fixer=None, filter=None): if not os.path.exists(dest): # common for the 'include' path os.makedirs(dest) for dir, subdirs, files in os.walk(source): basedir = dir[len(source):].lstrip(os.path.sep) destdir = os.path.join(dest, basedir) if is_base and basedir.split(os.path.sep, 1)[0].endswith('.data'): continue for s in subdirs: destsubdir = os.path.join(dest, basedir, s) if is_base and basedir == '' and destsubdir.endswith('.data'): data_dirs.append(s) continue elif (is_base and s.endswith('.dist-info') # is self.req.project_name case preserving? and s.lower().startswith(req.project_name.replace('-', '_').lower())): assert not info_dir, 'Multiple .dist-info directories' info_dir.append(destsubdir) for f in files: # Skip unwanted files if filter and filter(f): continue srcfile = os.path.join(dir, f) destfile = os.path.join(dest, basedir, f) # directory creation is lazy and after the file filtering above # to ensure we don't install empty dirs; empty dirs can't be # uninstalled. if not os.path.exists(destdir): os.makedirs(destdir) # use copy2 (not move) to be extra sure we're not moving # directories over; copy2 fails for directories. this would # fail tests (not during released/user execution) shutil.copy2(srcfile, destfile) changed = False if fixer: changed = fixer(destfile) record_installed(srcfile, destfile, changed) clobber(source, lib_dir, True) assert info_dir, "%s .dist-info directory not found" % req # Get the defined entry points ep_file = os.path.join(info_dir[0], 'entry_points.txt') console, gui = get_entrypoints(ep_file) def is_entrypoint_wrapper(name): # EP, EP.exe and EP-script.py are scripts generated for # entry point EP by setuptools if name.lower().endswith('.exe'): matchname = name[:-4] elif name.lower().endswith('-script.py'): matchname = name[:-10] elif name.lower().endswith(".pya"): matchname = name[:-4] else: matchname = name # Ignore setuptools-generated scripts return (matchname in console or matchname in gui) for datadir in data_dirs: fixer = None filter = None for subdir in os.listdir(os.path.join(wheeldir, datadir)): fixer = None if subdir == 'scripts': fixer = fix_script filter = is_entrypoint_wrapper source = os.path.join(wheeldir, datadir, subdir) dest = scheme[subdir] clobber(source, dest, False, fixer=fixer, filter=filter) maker = ScriptMaker(None, scheme['scripts']) # Ensure we don't generate any variants for scripts because this is almost # never what somebody wants. # See https://bitbucket.org/pypa/distlib/issue/35/ maker.variants = set(('', )) # This is required because otherwise distlib creates scripts that are not # executable. # See https://bitbucket.org/pypa/distlib/issue/32/ maker.set_mode = True # Simplify the script and fix the fact that the default script swallows # every single stack trace. # See https://bitbucket.org/pypa/distlib/issue/34/ # See https://bitbucket.org/pypa/distlib/issue/33/ def _get_script_text(entry): return maker.script_template % { "module": entry.prefix, "import_name": entry.suffix.split(".")[0], "func": entry.suffix, } maker._get_script_text = _get_script_text maker.script_template = """# -*- coding: utf-8 -*- import re import sys from %(module)s import %(import_name)s if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.exit(%(func)s()) """ # Special case pip and setuptools to generate versioned wrappers # # The issue is that some projects (specifically, pip and setuptools) use # code in setup.py to create "versioned" entry points - pip2.7 on Python # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into # the wheel metadata at build time, and so if the wheel is installed with # a *different* version of Python the entry points will be wrong. The # correct fix for this is to enhance the metadata to be able to describe # such versioned entry points, but that won't happen till Metadata 2.0 is # available. # In the meantime, projects using versioned entry points will either have # incorrect versioned entry points, or they will not be able to distribute # "universal" wheels (i.e., they will need a wheel per Python version). # # Because setuptools and pip are bundled with _ensurepip and virtualenv, # we need to use universal wheels. So, as a stopgap until Metadata 2.0, we # override the versioned entry points in the wheel and generate the # correct ones. This code is purely a short-term measure until Metadat 2.0 # is available. # # To add the level of hack in this section of code, in order to support # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment # variable which will control which version scripts get installed. # # ENSUREPIP_OPTIONS=altinstall # - Only pipX.Y and easy_install-X.Y will be generated and installed # ENSUREPIP_OPTIONS=install # - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note # that this option is technically if ENSUREPIP_OPTIONS is set and is # not altinstall # DEFAULT # - The default behavior is to install pip, pipX, pipX.Y, easy_install # and easy_install-X.Y. pip_script = console.pop('pip', None) if pip_script: if "ENSUREPIP_OPTIONS" not in os.environ: spec = 'pip = ' + pip_script generated.extend(maker.make(spec)) if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": spec = 'pip%s = %s' % (sys.version[:1], pip_script) generated.extend(maker.make(spec)) spec = 'pip%s = %s' % (sys.version[:3], pip_script) generated.extend(maker.make(spec)) # Delete any other versioned pip entry points pip_ep = [k for k in console if re.match(r'pip(\d(\.\d)?)?$', k)] for k in pip_ep: del console[k] easy_install_script = console.pop('easy_install', None) if easy_install_script: if "ENSUREPIP_OPTIONS" not in os.environ: spec = 'easy_install = ' + easy_install_script generated.extend(maker.make(spec)) spec = 'easy_install-%s = %s' % (sys.version[:3], easy_install_script) generated.extend(maker.make(spec)) # Delete any other versioned easy_install entry points easy_install_ep = [k for k in console if re.match(r'easy_install(-\d\.\d)?$', k)] for k in easy_install_ep: del console[k] # Generate the console and GUI entry points specified in the wheel if len(console) > 0: generated.extend(maker.make_multiple(['%s = %s' % kv for kv in console.items()])) if len(gui) > 0: generated.extend(maker.make_multiple(['%s = %s' % kv for kv in gui.items()], {'gui': True})) record = os.path.join(info_dir[0], 'RECORD') temp_record = os.path.join(info_dir[0], 'RECORD.pip') with open_for_csv(record, 'r') as record_in: with open_for_csv(temp_record, 'w+') as record_out: reader = csv.reader(record_in) writer = csv.writer(record_out) for row in reader: row[0] = installed.pop(row[0], row[0]) if row[0] in changed: row[1], row[2] = rehash(row[0]) writer.writerow(row) for f in generated: h, l = rehash(f) writer.writerow((f, h, l)) for f in installed: writer.writerow((installed[f], '', '')) shutil.move(temp_record, record)
def setUp(self): source_dir = os.path.join(HERE, 'scripts') target_dir = tempfile.mkdtemp() self.maker = ScriptMaker(source_dir, target_dir, add_launchers=False)
class ScriptTestCase(unittest.TestCase): def setUp(self): source_dir = os.path.join(HERE, 'scripts') target_dir = tempfile.mkdtemp() self.maker = ScriptMaker(source_dir, target_dir, add_launchers=False) def tearDown(self): shutil.rmtree(self.maker.target_dir) @unittest.skipIf(sysconfig.is_python_build(), 'Test not appropriate for ' 'Python source builds') def test_shebangs(self): executable = fsencode(get_executable()) for fn in ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh'): files = self.maker.make(fn) self.assertEqual(len(files), 1) d, f = os.path.split(files[0]) self.assertEqual(f, fn) self.assertEqual(d, self.maker.target_dir) if fn.endswith('.py') and fn != 'foo.py': # no shebang in foo.py with open(files[0], 'rb') as f: first_line = f.readline() self.assertIn(executable, first_line) def test_shebangs_custom_executable(self): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) maker = ScriptMaker(srcdir, dstdir, add_launchers=False) maker.executable = 'this_should_appear_in_the_shebang_line' #let's create the script to be copied. It has a vanilla shebang line. fn = os.path.join(srcdir, 'copied') with open(fn, 'w') as f: f.write(COPIED_SCRIPT) # Let's ask the maker to copy the script, and see what the shebang is # in the copy. filenames = maker.make('copied') with open(filenames[0], 'r') as f: actual = f.readline() self.assertIn(maker.executable, actual) # Now let's make a script from a callable filenames = maker.make(MADE_SCRIPT) with open(filenames[0], 'r') as f: actual = f.readline() self.assertIn(maker.executable, actual) def test_multiple(self): specs = ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) expected = set(specs) self.assertEqual(expected, set([os.path.basename(f) for f in files])) ofiles = os.listdir(self.maker.target_dir) self.assertEqual(expected, set(ofiles)) def test_generation(self): self.maker.clobber = True for name in ('main', 'other_main'): for options in (None, {}, {'gui': False}, {'gui': True}): gui = options and options.get('gui', False) spec = 'foo = foo:' + name files = self.maker.make(spec, options) self.assertEqual(len(files), 2) actual = set() for f in files: d, f = os.path.split(f) actual.add(f) if os.name == 'nt': if gui: ext = 'pyw' else: ext = 'py' expected = set(['foo.%s' % ext, 'foo-%s.%s' % (sys.version[:3], ext)]) else: expected = set(['foo', 'foo-%s' % sys.version[:3]]) self.assertEqual(actual, expected) self.assertEqual(d, self.maker.target_dir) for fn in files: with open(fn, 'r') as f: text = f.read() self.assertIn("_resolve('foo', '%s')" % name, text) if options and options['gui'] and os.name == 'nt': first_line, rest = text.split('\n', 1) self.assertIn('pythonw', first_line) def test_clobber(self): files = self.maker.make('foo = foo:main') saved_files = files self.assertGreaterEqual(len(files), 2) # foo, foo-X.Y files = self.maker.make('foo = foo:main') self.assertFalse(files) self.maker.clobber = True files = self.maker.make('foo = foo:main') self.assertEqual(files, saved_files) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_launchers(self): tlauncher = self.maker._get_launcher('t') self.maker.add_launchers = True specs = ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) filenames = set([os.path.basename(f) for f in files]) self.assertEqual(filenames, set(('foo.py', 'script1.exe', 'script2.exe', 'script3.exe', 'shell.sh'))) for fn in files: if not fn.endswith('.exe'): continue with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(tlauncher)) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_windows(self): wlauncher = self.maker._get_launcher('w') tlauncher = self.maker._get_launcher('t') self.maker.add_launchers = True executable = sys.executable.encode('utf-8') wexecutable = executable.replace(b'python.exe', b'pythonw.exe') files = self.maker.make('script4.py') self.assertEqual(len(files), 1) filenames = set([os.path.basename(f) for f in files]) self.assertEqual(filenames, set(['script4.exe'])) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(wlauncher)) self.assertIn(executable, data) # Now test making scripts gui and console files = self.maker.make('foo = foo:main', {'gui': True}) self.assertEqual(len(files), 2) filenames = set([os.path.basename(f) for f in files]) specific = sys.version[:3] self.assertEqual(filenames, set(('foo.exe', 'foo-%s.exe' % specific))) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(wlauncher)) self.assertIn(wexecutable, data) files = self.maker.make('foo = foo:main') self.assertEqual(len(files), 2) filenames = set([os.path.basename(f) for f in files]) self.assertEqual(filenames, set(('foo.exe', 'foo-%s.exe' % specific))) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(tlauncher)) self.assertIn(executable, data) def test_dry_run(self): self.maker.dry_run = True self.maker.variants = set(['']) specs = ('foo.py', 'bar = foo:main') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) if os.name == 'nt': bar = 'bar.py' else: bar = 'bar' self.assertEqual(set(('foo.py', bar)), set([os.path.basename(f) for f in files])) ofiles = os.listdir(self.maker.target_dir) self.assertFalse(ofiles) def test_script_run(self): files = self.maker.make('test = cgi:print_directory') self.assertEqual(len(files), 2) p = subprocess.Popen([sys.executable, files[0]], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() self.assertIn(b'<H3>Current Working Directory:</H3>', stdout) self.assertIn(os.getcwd().encode('utf-8'), stdout) @unittest.skipUnless(os.name == 'posix', 'Test only valid for POSIX') def test_mode(self): files = self.maker.make('foo = foo:main') self.assertEqual(len(files), 2) for f in files: self.assertIn(os.stat(f).st_mode & 0o7777, (0o644, 0o664)) self.maker.set_mode = True files = self.maker.make('bar = bar:main') self.assertEqual(len(files), 2) for f in files: self.assertIn(os.stat(f).st_mode & 0o7777, (0o755, 0o775))
class ScriptTestCase(unittest.TestCase): def setUp(self): source_dir = os.path.join(HERE, 'scripts') target_dir = tempfile.mkdtemp() self.maker = ScriptMaker(source_dir, target_dir, add_launchers=False) def tearDown(self): shutil.rmtree(self.maker.target_dir) @unittest.skipIf(sysconfig.is_python_build(), 'Test not appropriate for ' 'Python source builds') def test_shebangs(self): executable = fsencode(get_executable()) for fn in ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh'): files = self.maker.make(fn) self.assertEqual(len(files), 1) d, f = os.path.split(files[0]) self.assertEqual(f, fn) self.assertEqual(d, self.maker.target_dir) if fn.endswith('.py') and fn != 'foo.py': # no shebang in foo.py with open(files[0], 'rb') as f: first_line = f.readline() self.assertIn(executable, first_line) def test_shebangs_custom_executable(self): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) maker = ScriptMaker(srcdir, dstdir, add_launchers=False) maker.executable = 'this_should_appear_in_the_shebang_line(中文)' # let's create the script to be copied. It has a vanilla shebang line, # with some Unicode in it. fn = os.path.join(srcdir, 'copied') with open(fn, 'w') as f: f.write(COPIED_SCRIPT) # Let's ask the maker to copy the script, and see what the shebang is # in the copy. filenames = maker.make('copied') with open(filenames[0], 'rb') as f: actual = f.readline().decode('utf-8') self.assertIn(maker.executable, actual) # Now let's make a script from a callable filenames = maker.make(MADE_SCRIPT) with open(filenames[0], 'rb') as f: actual = f.readline().decode('utf-8') self.assertIn(maker.executable, actual) def test_multiple(self): specs = ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh', 'uwsgi_part') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) expected = set(specs) self.assertEqual(expected, set([os.path.basename(f) for f in files])) ofiles = os.listdir(self.maker.target_dir) self.assertEqual(expected, set(ofiles)) def test_generation(self): self.maker.clobber = True for name in ('main', 'other_main'): for options in (None, {}, {'gui': False}, {'gui': True}): gui = options and options.get('gui', False) spec = 'foo = foo:' + name files = self.maker.make(spec, options) self.assertEqual(len(files), 2) actual = set() for f in files: d, f = os.path.split(f) actual.add(f) if os.name == 'nt': # pragma: no cover if gui: ext = 'pyw' else: ext = 'py' expected = set( ['foo.%s' % ext, 'foo-%s.%s' % (sys.version[:3], ext)]) else: expected = set(['foo', 'foo-%s' % sys.version[:3]]) self.assertEqual(actual, expected) self.assertEqual(d, self.maker.target_dir) for fn in files: with open(fn, 'r') as f: text = f.read() self.assertIn("_resolve('foo', '%s')" % name, text) if options and options[ 'gui'] and os.name == 'nt': # pragma: no cover first_line, rest = text.split('\n', 1) self.assertIn('pythonw', first_line) def test_clobber(self): files = self.maker.make('foo = foo:main') saved_files = files self.assertGreaterEqual(len(files), 2) # foo, foo-X.Y files = self.maker.make('foo = foo:main') self.assertFalse(files) self.maker.clobber = True files = self.maker.make('foo = foo:main') self.assertEqual(files, saved_files) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_launchers(self): # pragma: no cover tlauncher = self.maker._get_launcher('t') self.maker.add_launchers = True specs = ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) filenames = set([os.path.basename(f) for f in files]) self.assertEqual( filenames, set(('foo.py', 'script1.exe', 'script2.exe', 'script3.exe', 'shell.sh'))) for fn in files: if not fn.endswith('.exe'): continue with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(tlauncher)) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_launcher_run(self): self.maker.add_launchers = True files = self.maker.make('script6.py') self.assertEqual(len(files), 1) p = subprocess.Popen([files[0], 'Test Argument'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate('input'.encode('ascii')) actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(""" script6.exe ['Test Argument'] 'input' non-optimized """).lstrip() self.assertEqual(actual, expected) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_launcher_run_with_interpreter_args(self): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) maker = ScriptMaker(srcdir, dstdir, add_launchers=True) # add '-O' option to shebang to run in optimized mode with open(os.path.join(HERE, 'scripts', 'script6.py'), 'r') as src: with open(os.path.join(srcdir, 'script6-optimized.py'), 'w') as dst: shebang = src.readline().rstrip() dst.write(shebang + " -O\n") dst.write(src.read()) files = maker.make('script6-optimized.py') self.assertEqual(len(files), 1) p = subprocess.Popen([files[0], 'Test Argument'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate('input'.encode('ascii')) actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(""" script6-optimized.exe ['Test Argument'] 'input' """).lstrip() # 'non-optimized' is not printed this time self.assertEqual(actual, expected) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_windows(self): # pragma: no cover wlauncher = self.maker._get_launcher('w') tlauncher = self.maker._get_launcher('t') self.maker.add_launchers = True executable = os.path.normcase(sys.executable).encode('utf-8') wexecutable = executable.replace(b'python.exe', b'pythonw.exe') files = self.maker.make('script4.py') self.assertEqual(len(files), 1) filenames = set([os.path.basename(f) for f in files]) self.assertEqual(filenames, set(['script4.exe'])) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(wlauncher)) self.assertIn(executable, data) # Now test making scripts gui and console files = self.maker.make('foo = foo:main', {'gui': True}) self.assertEqual(len(files), 2) filenames = set([os.path.basename(f) for f in files]) specific = sys.version[:3] self.assertEqual(filenames, set(('foo.exe', 'foo-%s.exe' % specific))) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(wlauncher)) self.assertIn(wexecutable, data) files = self.maker.make('foo = foo:main') self.assertEqual(len(files), 2) filenames = set([os.path.basename(f) for f in files]) self.assertEqual(filenames, set(('foo.exe', 'foo-%s.exe' % specific))) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(tlauncher)) self.assertIn(executable, data) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_windows_run(self): self.maker.add_launchers = True files = self.maker.make('script7.pyw') self.assertEqual(len(files), 1) test_output = os.path.join(self.maker.target_dir, 'test_output.txt') p = subprocess.Popen([files[0], test_output, 'Test Argument'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate() self.assertFalse(stdout) self.assertFalse(stderr) with open(test_output, 'rb') as f: actual = f.read().decode('ascii') self.assertEqual(actual, 'Test Argument') def test_dry_run(self): self.maker.dry_run = True self.maker.variants = set(['']) specs = ('foo.py', 'bar = foo:main') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) if os.name == 'nt': # pragma: no cover bar = 'bar.py' else: bar = 'bar' self.assertEqual(set(('foo.py', bar)), set([os.path.basename(f) for f in files])) ofiles = os.listdir(self.maker.target_dir) self.assertFalse(ofiles) def test_script_run(self): files = self.maker.make('test = cgi:print_directory') self.assertEqual(len(files), 2) p = subprocess.Popen([sys.executable, files[0]], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() self.assertIn(b'<H3>Current Working Directory:</H3>', stdout) self.assertIn(os.getcwd().encode('utf-8'), stdout) @unittest.skipUnless(os.name == 'posix', 'Test only valid for POSIX') def test_mode(self): self.maker.set_mode = False files = self.maker.make('foo = foo:main') self.assertEqual(len(files), 2) for f in files: self.assertIn(os.stat(f).st_mode & 0o7777, (0o644, 0o664)) self.maker.set_mode = True files = self.maker.make('bar = bar:main') self.assertEqual(len(files), 2) for f in files: self.assertIn(os.stat(f).st_mode & 0o7777, (0o755, 0o775)) def test_interpreter_args(self): executable = fsencode(get_executable()) options = {'interpreter_args': ['-E', '"foo bar"', 'baz frobozz']} self.maker.variants = set(['']) files = self.maker.make('foo = bar:baz', options=options) self.assertEqual(len(files), 1) with open(files[0], 'rb') as f: shebang_line = f.readline() self.assertIn(executable, shebang_line) self.assertIn(b' -E "foo bar" baz frobozz', shebang_line) def test_args_on_copy(self): self.maker.variants = set(['']) self.maker.executable = 'mypython' files = self.maker.make('script5.py') with open(files[0]) as f: actual = f.readline().strip() self.assertEqual(actual, '#!mypython -mzippy.activate') self.maker.executable = None os.remove(files[0]) files = self.maker.make('script5.py') with open(files[0]) as f: actual = f.readline().strip() expected = '#!%s -mzippy.activate' % get_executable() self.assertEqual(actual, expected) def test_enquote_executable(self): for executable, expected in (('/no/spaces', '/no/spaces'), ('/i have/space', '"/i have/space"'), ('"/space prequoted"', '"/space prequoted"'), ('/usr/bin/env nospaces', '/usr/bin/env nospaces'), ('/usr/bin/env with spaces', '/usr/bin/env "with spaces"'), ('/usr/bin/env "pre spaced"', '/usr/bin/env "pre spaced"')): self.assertEqual(_enquote_executable(executable), expected)
class ScriptTestCase(unittest.TestCase): def setUp(self): source_dir = os.path.join(HERE, 'scripts') target_dir = tempfile.mkdtemp() self.maker = ScriptMaker(source_dir, target_dir, add_launchers=False) def tearDown(self): shutil.rmtree(self.maker.target_dir) @unittest.skipIf(sysconfig.is_python_build(), 'Test not appropriate for ' 'Python source builds') def test_shebangs(self): executable = fsencode(get_executable()) for fn in ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh'): files = self.maker.make(fn) self.assertEqual(len(files), 1) d, f = os.path.split(files[0]) self.assertEqual(f, fn) self.assertEqual(d, self.maker.target_dir) if fn.endswith('.py') and fn != 'foo.py': # no shebang in foo.py with open(files[0], 'rb') as f: first_line = f.readline() self.assertIn(executable, first_line) def test_shebangs_custom_executable(self): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) maker = ScriptMaker(srcdir, dstdir, add_launchers=False) maker.executable = 'this_should_appear_in_the_shebang_line(中文)' # let's create the script to be copied. It has a vanilla shebang line, # with some Unicode in it. fn = os.path.join(srcdir, 'copied') with open(fn, 'w') as f: f.write(COPIED_SCRIPT) # Let's ask the maker to copy the script, and see what the shebang is # in the copy. filenames = maker.make('copied') with open(filenames[0], 'rb') as f: actual = f.readline().decode('utf-8') self.assertIn(maker.executable, actual) # Now let's make a script from a callable filenames = maker.make(MADE_SCRIPT) with open(filenames[0], 'rb') as f: actual = f.readline().decode('utf-8') self.assertIn(maker.executable, actual) def test_multiple(self): specs = ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh', 'uwsgi_part') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) expected = set(specs) self.assertEqual(expected, set([os.path.basename(f) for f in files])) ofiles = os.listdir(self.maker.target_dir) self.assertEqual(expected, set(ofiles)) def test_generation(self): self.maker.clobber = True for name in ('main', 'other_main'): for options in (None, {}, {'gui': False}, {'gui': True}): gui = options and options.get('gui', False) spec = 'foo = foo:' + name files = self.maker.make(spec, options) self.assertEqual(len(files), 2) actual = set() for f in files: d, f = os.path.split(f) actual.add(f) if os.name == 'nt': # pragma: no cover if gui: ext = 'pyw' else: ext = 'py' expected = set(['foo.%s' % ext, 'foo-%s.%s' % (sys.version[:3], ext)]) else: expected = set(['foo', 'foo-%s' % sys.version[:3]]) self.assertEqual(actual, expected) self.assertEqual(d, self.maker.target_dir) for fn in files: with open(fn, 'r') as f: text = f.read() self.assertIn("_resolve('foo', '%s')" % name, text) if options and options['gui'] and os.name == 'nt': # pragma: no cover first_line, rest = text.split('\n', 1) self.assertIn('pythonw', first_line) def test_clobber(self): files = self.maker.make('foo = foo:main') saved_files = files self.assertGreaterEqual(len(files), 2) # foo, foo-X.Y files = self.maker.make('foo = foo:main') self.assertFalse(files) self.maker.clobber = True files = self.maker.make('foo = foo:main') self.assertEqual(files, saved_files) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_launchers(self): # pragma: no cover tlauncher = self.maker._get_launcher('t') self.maker.add_launchers = True specs = ('foo.py', 'script1.py', 'script2.py', 'script3.py', 'shell.sh') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) filenames = set([os.path.basename(f) for f in files]) self.assertEqual(filenames, set(('foo.py', 'script1.exe', 'script2.exe', 'script3.exe', 'shell.sh'))) for fn in files: if not fn.endswith('.exe'): continue with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(tlauncher)) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_launcher_run(self): self.maker.add_launchers = True files = self.maker.make('script6.py') self.assertEqual(len(files), 1) p = subprocess.Popen([files[0], 'Test Argument'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate('input'.encode('ascii')) actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(""" script6.exe ['Test Argument'] 'input' non-optimized """).lstrip() self.assertEqual(actual, expected) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_launcher_run_with_interpreter_args(self): srcdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, srcdir) dstdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, dstdir) maker = ScriptMaker(srcdir, dstdir, add_launchers=True) # add '-O' option to shebang to run in optimized mode with open(os.path.join(HERE, 'scripts', 'script6.py'), 'r') as src: with open(os.path.join(srcdir, 'script6-optimized.py'), 'w') as dst: shebang = src.readline().rstrip() dst.write(shebang + " -O\n") dst.write(src.read()) files = maker.make('script6-optimized.py') self.assertEqual(len(files), 1) p = subprocess.Popen([files[0], 'Test Argument'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate('input'.encode('ascii')) actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(""" script6-optimized.exe ['Test Argument'] 'input' """).lstrip() # 'non-optimized' is not printed this time self.assertEqual(actual, expected) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_windows(self): # pragma: no cover wlauncher = self.maker._get_launcher('w') tlauncher = self.maker._get_launcher('t') self.maker.add_launchers = True executable = os.path.normcase(sys.executable).encode('utf-8') wexecutable = executable.replace(b'python.exe', b'pythonw.exe') files = self.maker.make('script4.py') self.assertEqual(len(files), 1) filenames = set([os.path.basename(f) for f in files]) self.assertEqual(filenames, set(['script4.exe'])) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(wlauncher)) self.assertIn(executable, data) # Now test making scripts gui and console files = self.maker.make('foo = foo:main', {'gui': True}) self.assertEqual(len(files), 2) filenames = set([os.path.basename(f) for f in files]) specific = sys.version[:3] self.assertEqual(filenames, set(('foo.exe', 'foo-%s.exe' % specific))) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(wlauncher)) self.assertIn(wexecutable, data) files = self.maker.make('foo = foo:main') self.assertEqual(len(files), 2) filenames = set([os.path.basename(f) for f in files]) self.assertEqual(filenames, set(('foo.exe', 'foo-%s.exe' % specific))) for fn in files: with open(fn, 'rb') as f: data = f.read() self.assertTrue(data.startswith(tlauncher)) self.assertIn(executable, data) @unittest.skipIf(os.name != 'nt', 'Test is Windows-specific') def test_windows_run(self): self.maker.add_launchers = True files = self.maker.make('script7.pyw') self.assertEqual(len(files), 1) test_output = os.path.join(self.maker.target_dir, 'test_output.txt') p = subprocess.Popen([files[0], test_output, 'Test Argument'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate() self.assertFalse(stdout) self.assertFalse(stderr) with open(test_output, 'rb') as f: actual = f.read().decode('ascii') self.assertEqual(actual, 'Test Argument') def test_dry_run(self): self.maker.dry_run = True self.maker.variants = set(['']) specs = ('foo.py', 'bar = foo:main') files = self.maker.make_multiple(specs) self.assertEqual(len(specs), len(files)) if os.name == 'nt': # pragma: no cover bar = 'bar.py' else: bar = 'bar' self.assertEqual(set(('foo.py', bar)), set([os.path.basename(f) for f in files])) ofiles = os.listdir(self.maker.target_dir) self.assertFalse(ofiles) def test_script_run(self): files = self.maker.make('test = cgi:print_directory') self.assertEqual(len(files), 2) p = subprocess.Popen([sys.executable, files[0]], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() self.assertIn(b'<H3>Current Working Directory:</H3>', stdout) self.assertIn(os.getcwd().encode('utf-8'), stdout) @unittest.skipUnless(os.name == 'posix', 'Test only valid for POSIX') def test_mode(self): self.maker.set_mode = False files = self.maker.make('foo = foo:main') self.assertEqual(len(files), 2) for f in files: self.assertIn(os.stat(f).st_mode & 0o7777, (0o644, 0o664)) self.maker.set_mode = True files = self.maker.make('bar = bar:main') self.assertEqual(len(files), 2) for f in files: self.assertIn(os.stat(f).st_mode & 0o7777, (0o755, 0o775)) def test_interpreter_args(self): executable = fsencode(get_executable()) options = { 'interpreter_args': ['-E', '"foo bar"', 'baz frobozz'] } self.maker.variants = set(['']) files = self.maker.make('foo = bar:baz', options=options) self.assertEqual(len(files), 1) with open(files[0], 'rb') as f: shebang_line = f.readline() if not sysconfig.is_python_build(): self.assertIn(executable, shebang_line) self.assertIn(b' -E "foo bar" baz frobozz', shebang_line) def test_args_on_copy(self): self.maker.variants = set(['']) self.maker.executable = 'mypython' files = self.maker.make('script5.py') with open(files[0]) as f: actual = f.readline().strip() self.assertEqual(actual, '#!mypython -mzippy.activate') if not sysconfig.is_python_build(): self.maker.executable = None os.remove(files[0]) files = self.maker.make('script5.py') with open(files[0]) as f: actual = f.readline().strip() expected = '#!%s -mzippy.activate' % get_executable() self.assertEqual(actual, expected) def test_enquote_executable(self): for executable, expected in ( ('/no/spaces', '/no/spaces'), ('/i have/space', '"/i have/space"'), ('"/space prequoted"', '"/space prequoted"'), ('/usr/bin/env nospaces', '/usr/bin/env nospaces'), ('/usr/bin/env with spaces', '/usr/bin/env "with spaces"'), ('/usr/bin/env "pre spaced"', '/usr/bin/env "pre spaced"') ): self.assertEqual(_enquote_executable(executable), expected)
def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None, pycompile=True, scheme=None): """Install a wheel""" if not scheme: scheme = distutils_scheme(name, user=user, home=home, root=root) if root_is_purelib(name, wheeldir): lib_dir = scheme['purelib'] else: lib_dir = scheme['platlib'] info_dir = [] data_dirs = [] source = wheeldir.rstrip(os.path.sep) + os.path.sep # Record details of the files moved # installed = files copied from the wheel to the destination # changed = files changed while installing (scripts #! line typically) # generated = files newly generated during the install (script wrappers) installed = {} changed = set() generated = [] # Compile all of the pyc files that we're going to be installing if pycompile: compileall.compile_dir(source, force=True, quiet=True) def normpath(src, p): return make_path_relative(src, p).replace(os.path.sep, '/') def record_installed(srcfile, destfile, modified=False): """Map archive RECORD paths to installation RECORD paths.""" oldpath = normpath(srcfile, wheeldir) newpath = normpath(destfile, lib_dir) installed[oldpath] = newpath if modified: changed.add(destfile) def clobber(source, dest, is_base, fixer=None, filter=None): if not os.path.exists(dest): # common for the 'include' path os.makedirs(dest) for dir, subdirs, files in os.walk(source): basedir = dir[len(source):].lstrip(os.path.sep) destdir = os.path.join(dest, basedir) if is_base and basedir.split(os.path.sep, 1)[0].endswith('.data'): continue for s in subdirs: destsubdir = os.path.join(dest, basedir, s) if is_base and basedir == '' and destsubdir.endswith('.data'): data_dirs.append(s) continue elif (is_base and s.endswith('.dist-info') # is self.req.project_name case preserving? and s.lower().startswith( req.project_name.replace('-', '_').lower())): assert not info_dir, 'Multiple .dist-info directories' info_dir.append(destsubdir) for f in files: # Skip unwanted files if filter and filter(f): continue srcfile = os.path.join(dir, f) destfile = os.path.join(dest, basedir, f) # directory creation is lazy and after the file filtering above # to ensure we don't install empty dirs; empty dirs can't be # uninstalled. if not os.path.exists(destdir): os.makedirs(destdir) # use copy2 (not move) to be extra sure we're not moving # directories over; copy2 fails for directories. this would # fail tests (not during released/user execution) shutil.copy2(srcfile, destfile) changed = False if fixer: changed = fixer(destfile) record_installed(srcfile, destfile, changed) clobber(source, lib_dir, True) assert info_dir, "%s .dist-info directory not found" % req # Get the defined entry points ep_file = os.path.join(info_dir[0], 'entry_points.txt') console, gui = get_entrypoints(ep_file) def is_entrypoint_wrapper(name): # EP, EP.exe and EP-script.py are scripts generated for # entry point EP by setuptools if name.lower().endswith('.exe'): matchname = name[:-4] elif name.lower().endswith('-script.py'): matchname = name[:-10] elif name.lower().endswith(".pya"): matchname = name[:-4] else: matchname = name # Ignore setuptools-generated scripts return (matchname in console or matchname in gui) for datadir in data_dirs: fixer = None filter = None for subdir in os.listdir(os.path.join(wheeldir, datadir)): fixer = None if subdir == 'scripts': fixer = fix_script filter = is_entrypoint_wrapper source = os.path.join(wheeldir, datadir, subdir) dest = scheme[subdir] clobber(source, dest, False, fixer=fixer, filter=filter) maker = ScriptMaker(None, scheme['scripts']) # Ensure we don't generate any variants for scripts because this is almost # never what somebody wants. # See https://bitbucket.org/pypa/distlib/issue/35/ maker.variants = set(('', )) # This is required because otherwise distlib creates scripts that are not # executable. # See https://bitbucket.org/pypa/distlib/issue/32/ maker.set_mode = True # Simplify the script and fix the fact that the default script swallows # every single stack trace. # See https://bitbucket.org/pypa/distlib/issue/34/ # See https://bitbucket.org/pypa/distlib/issue/33/ def _get_script_text(entry): return maker.script_template % { "module": entry.prefix, "import_name": entry.suffix.split(".")[0], "func": entry.suffix, } maker._get_script_text = _get_script_text maker.script_template = """# -*- coding: utf-8 -*- import re import sys from %(module)s import %(import_name)s if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.exit(%(func)s()) """ # Special case pip and setuptools to generate versioned wrappers # # The issue is that some projects (specifically, pip and setuptools) use # code in setup.py to create "versioned" entry points - pip2.7 on Python # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into # the wheel metadata at build time, and so if the wheel is installed with # a *different* version of Python the entry points will be wrong. The # correct fix for this is to enhance the metadata to be able to describe # such versioned entry points, but that won't happen till Metadata 2.0 is # available. # In the meantime, projects using versioned entry points will either have # incorrect versioned entry points, or they will not be able to distribute # "universal" wheels (i.e., they will need a wheel per Python version). # # Because setuptools and pip are bundled with _ensurepip and virtualenv, # we need to use universal wheels. So, as a stopgap until Metadata 2.0, we # override the versioned entry points in the wheel and generate the # correct ones. This code is purely a short-term measure until Metadat 2.0 # is available. # # To add the level of hack in this section of code, in order to support # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment # variable which will control which version scripts get installed. # # ENSUREPIP_OPTIONS=altinstall # - Only pipX.Y and easy_install-X.Y will be generated and installed # ENSUREPIP_OPTIONS=install # - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note # that this option is technically if ENSUREPIP_OPTIONS is set and is # not altinstall # DEFAULT # - The default behavior is to install pip, pipX, pipX.Y, easy_install # and easy_install-X.Y. pip_script = console.pop('pip', None) if pip_script: if "ENSUREPIP_OPTIONS" not in os.environ: spec = 'pip = ' + pip_script generated.extend(maker.make(spec)) if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": spec = 'pip%s = %s' % (sys.version[:1], pip_script) generated.extend(maker.make(spec)) spec = 'pip%s = %s' % (sys.version[:3], pip_script) generated.extend(maker.make(spec)) # Delete any other versioned pip entry points pip_ep = [k for k in console if re.match(r'pip(\d(\.\d)?)?$', k)] for k in pip_ep: del console[k] easy_install_script = console.pop('easy_install', None) if easy_install_script: if "ENSUREPIP_OPTIONS" not in os.environ: spec = 'easy_install = ' + easy_install_script generated.extend(maker.make(spec)) spec = 'easy_install-%s = %s' % (sys.version[:3], easy_install_script) generated.extend(maker.make(spec)) # Delete any other versioned easy_install entry points easy_install_ep = [ k for k in console if re.match(r'easy_install(-\d\.\d)?$', k) ] for k in easy_install_ep: del console[k] # Generate the console and GUI entry points specified in the wheel if len(console) > 0: generated.extend( maker.make_multiple(['%s = %s' % kv for kv in console.items()])) if len(gui) > 0: generated.extend( maker.make_multiple(['%s = %s' % kv for kv in gui.items()], {'gui': True})) record = os.path.join(info_dir[0], 'RECORD') temp_record = os.path.join(info_dir[0], 'RECORD.pip') with open_for_csv(record, 'r') as record_in: with open_for_csv(temp_record, 'w+') as record_out: reader = csv.reader(record_in) writer = csv.writer(record_out) for row in reader: row[0] = installed.pop(row[0], row[0]) if row[0] in changed: row[1], row[2] = rehash(row[0]) writer.writerow(row) for f in generated: h, l = rehash(f) writer.writerow((f, h, l)) for f in installed: writer.writerow((installed[f], '', '')) shutil.move(temp_record, record)
# Ignore setuptools-generated scripts return (matchname in console or matchname in gui) for datadir in data_dirs: fixer = None filter = None for subdir in os.listdir(os.path.join(wheeldir, datadir)): fixer = None if subdir == 'scripts': fixer = fix_script filter = is_entrypoint_wrapper source = os.path.join(wheeldir, datadir, subdir) dest = scheme[subdir] clobber(source, dest, False, fixer=fixer, filter=filter) maker = ScriptMaker(None, scheme['scripts']) # Ensure we don't generate any variants for scripts because this is almost # never what somebody wants. # See https://bitbucket.org/pypa/distlib/issue/35/ maker.variants = set(('', )) # This is required because otherwise distlib creates scripts that are not # executable. # See https://bitbucket.org/pypa/distlib/issue/32/ maker.set_mode = True # Simplify the script and fix the fact that the default script swallows # every single stack trace. # See https://bitbucket.org/pypa/distlib/issue/34/ # See https://bitbucket.org/pypa/distlib/issue/33/
def _install_wheels(need, system): # TODO: if a package is being updated, it should be installed in # the same location as before # Find all packages that should be deleted all = dict([ (d.name, d) for d in installed_core() | installed_tools() | installed_toolboxes() ]) from distlib.database import make_graph import itertools graph = make_graph(itertools.chain(all.values(), need)) l = need[:] # what we started with ordered = [] # ordered by least dependency depend = {} # dependency relationship cache while l: for d in l: for d2 in l: if d2 is d: continue try: dep = depend[(d, d2)] except KeyError: dep = _depends_on(graph, d, d2) depend[(d, d2)] = dep if dep: break else: ordered.append(d) l.remove(d) break else: # This can only happen if there is circular dependencies # in which case we just process the distributions in # given order since its no worse than anything else ordered.extend(l) break remove_list = [] check = set() for d in ordered: if d in remove_list: continue try: rd = all[d.name] except KeyError: pass else: remove_list.append(rd) al = graph.adjacency_list[rd] if al: check.update([ sd for sd, l in al ]) # Repeatedly go through the list of distributions to see whether # they can be removed. It must be iterative. Suppose A and B need # to be removed; C depends on A; D depends on B and C; if we check D # first, it will not be removable since C is not marked for removal # yet; but a second pass will show that D is removable. Iteration # ends when no new packages are marked as removable. while check: any_deletion = False new_check = set() for d in check: for pd in graph.reverse_list[d]: if pd not in remove_list: new_check.add(d) break else: any_deletion = True remove_list.append(d) for sd, l in graph.adjacency_list[d]: if sd not in remove_list and sd not in check: new_check.add(sd) if not any_deletion: break check = new_check removed_location = {} for d in remove_list: removed_location[d.name] = _remove_distribution(d) dl = download_location() default_paths = _install_make_paths(system) from distlib.scripts import ScriptMaker maker = ScriptMaker(None, None) import os.path try: from urllib.request import urlretrieve, URLError except ImportError: from urllib import urlretrieve, URLError from distlib.wheel import Wheel from distlib import DistlibException for d in need: try: old_location = removed_location[d.name] except KeyError: paths = default_paths else: paths = _install_make_paths(system, old_location) url = d.source_url filename = url.split('/')[-1] dloc = os.path.join(dl, filename) if not os.path.isfile(dloc): try: filename, headers = urlretrieve(url, dloc) except URLError as e: show("Warning: cannot fetch %s: %s" % (url, str(e))) continue w = Wheel(dloc) try: w.verify() except DistlibExecption as e: show("Warning: cannot verify %s: %s" % (d.name, str(e))) continue show("installing %s (%s)" % (w.name, w.version)) w.install(paths, maker)