From 1c580ae5ae23e64bfffd7597a8336d26017765cb Mon Sep 17 00:00:00 2001 From: malthe Date: Tue, 8 Jul 2014 10:48:29 +0200 Subject: Write to temporary file and then rename when using --force. --- CHANGES.txt | 6 ++++++ wheel/install.py | 37 ++++++++++++++++++++++++------------- wheel/test/test_install.py | 21 +++++++++++---------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 26754c9..e6beb32 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,9 @@ +In next release ... + +- When force is given, write to temporary file and rename. This is + necessary because when a file exists, it may already be in use by a + running process. + 0.24.0 ====== - The python tag used for pure-python packages is now .pyN (major version diff --git a/wheel/install.py b/wheel/install.py index 3af6d0c..768fec4 100644 --- a/wheel/install.py +++ b/wheel/install.py @@ -335,25 +335,36 @@ class WheelFile(object): record_name = self.distinfo_name + '/RECORD' for info, (key, target, filename, dest) in name_trans.items(): name = info.filename - source = self.zipfile.open(info) - # Skip the RECORD file - if name == record_name: - continue ddir = os.path.dirname(dest) if not os.path.isdir(ddir): os.makedirs(ddir) - destination = HashingFile(open(dest, 'wb')) - if key == 'scripts': - hashbang = source.readline() - if hashbang.startswith(b'#!python'): - hashbang = b'#!' + exename + binary(os.linesep) - destination.write(hashbang) - shutil.copyfileobj(source, destination) + + if force: + dest = "{0}.tmp".format(dest) + + with self.zipfile.open(info) as source, open(dest, 'wb') as f: + destination = HashingFile(f) + + # Skip the RECORD file + if name == record_name: + continue + + if key == 'scripts': + hashbang = source.readline() + if hashbang.startswith(b'#!python'): + hashbang = b'#!' + exename + binary(os.linesep) + destination.write(hashbang) + shutil.copyfileobj(source, destination) + + if force: + tmp = dest + dest = dest[:-4] + os.rename(tmp, dest) + reldest = os.path.relpath(dest, root) reldest.replace(os.sep, '/') record_data.append((reldest, destination.digest(), destination.length)) - destination.close() - source.close() + # preserve attributes (especially +x bit for scripts) attrs = info.external_attr >> 16 if attrs: # tends to be 0 if Windows. diff --git a/wheel/test/test_install.py b/wheel/test/test_install.py index ddcddf5..ff76c18 100644 --- a/wheel/test/test_install.py +++ b/wheel/test/test_install.py @@ -36,15 +36,16 @@ def test_install(): for key in ('purelib', 'platlib', 'scripts', 'headers', 'data'): locs[key] = os.path.join(tempdir, key) os.mkdir(locs[key]) - whl.install(overrides=locs) - assert len(os.listdir(locs['purelib'])) == 0 - assert check(locs['platlib'], 'hello.pyd') - assert check(locs['platlib'], 'hello', 'hello.py') - assert check(locs['platlib'], 'hello', '__init__.py') - assert check(locs['data'], 'hello.dat') - assert check(locs['headers'], 'hello.dat') - assert check(locs['scripts'], 'hello.sh') - assert check(locs['platlib'], 'test-1.0.dist-info', 'RECORD') + for force in (False, True): + whl.install(force=force, overrides=locs) + assert len(os.listdir(locs['purelib'])) == 0 + assert check(locs['platlib'], 'hello.pyd') + assert check(locs['platlib'], 'hello', 'hello.py') + assert check(locs['platlib'], 'hello', '__init__.py') + assert check(locs['data'], 'hello.dat') + assert check(locs['headers'], 'hello.dat') + assert check(locs['scripts'], 'hello.sh') + assert check(locs['platlib'], 'test-1.0.dist-info', 'RECORD') finally: shutil.rmtree(tempdir) @@ -52,4 +53,4 @@ def test_install_tool(): """Slightly improve coverage of wheel.install""" wheel.tool.install([TESTWHEEL], force=True, dry_run=True) - \ No newline at end of file + -- cgit v1.2.1