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 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
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)
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)
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: