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 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 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 _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 _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 = [] 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 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 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_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