1. Ronald Oussoren
  2. py2app
  3. Issues

Issues

Issue #134 new

_copy_file fails when copying a read-only file to an existing destination

Sean Fisk
created an issue

This bug manifests when building the app bundle for our project, which uses PySide. The issue seems to be that py2app copies the QtCore framework twice, and fails the second time when it tries to write to an existing read-only file.

The problem is in the _copy_file function, where the file is copied by reading all the data from the source and writing it all to the destination. This issue exists in 0.8 and above, including the latest development version. It did not exist in 0.7.3, because the option to preserve the file's mode was stubbed out like so:

if preserve_mode:
    # XXX: copy current mode
    pass

This caused the read-only file to be written with write permissions, and the with open(destination, 'wb') call succeeded. In newer versions, where preserve_mode is implemented properly, it fails.

I am not sure why the file is attempted to be copied twice; however, this problem seems orthogonal to the problem that _copy_file fails. The issue of the double copy is probably more deep-seated and more difficult to debug.

Here is the relevant traceback:

copying file /usr/local/Cellar/qt/4.8.5/lib/QtCore.framework/Contents/Info.plist -> /Users/sean/src/personal/better-banner/better-banner/dist/BetterBanner.app/Contents/Frameworks/QtCore.framework/Contents/Info.plist
Traceback (most recent call last):
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/paver/tasks.py", line 195, in _run_task
    return do_task()
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/paver/tasks.py", line 191, in do_task
    task()
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/paver/setuputils.py", line 175, in __call__
    self.distribution.run_command(self.command_name)
  File "/Users/sean/.pyenv/versions/2.7.5/lib/python2.7/distutils/dist.py", line 972, in run_command
    cmd_obj.run()
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/build_app.py", line 651, in run
    self._run()
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/build_app.py", line 857, in _run
    self.run_normal()
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/build_app.py", line 947, in run_normal
    self.create_binaries(py_files, pkgdirs, extensions, loader_files)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/build_app.py", line 1107, in create_binaries
    platfiles = mm.run()
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOStandalone.py", line 105, in run
    mm.run_file(fn)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOGraph.py", line 84, in run_file
    self.scan_node(m)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOGraph.py", line 110, in scan_node
    m = self.load_file(filename, caller=node)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOGraph.py", line 95, in load_file
    return self.load_file(newname, caller=caller)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOGraph.py", line 98, in load_file
    self.scan_node(m)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOGraph.py", line 110, in scan_node
    m = self.load_file(filename, caller=node)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOGraph.py", line 91, in load_file
    m = self.findNode(name)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOGraph.py", line 70, in findNode
    newname = self.locate(name, loader=loader)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOStandalone.py", line 26, in locate
    return self.delegate.locate(newname)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/macholib/MachOStandalone.py", line 65, in locate
    res = self.copy_framework(info)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/build_app.py", line 183, in copy_framework
    destfn = self.appbuilder.copy_framework(info, self.dest)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/build_app.py", line 1248, in copy_framework
    self.copy_versioned_framework(info, dst)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/build_app.py", line 1241, in copy_versioned_framework
    preserve_symlinks=True, condition=condition)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/build_app.py", line 1893, in copy_tree
    condition=condition)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/util.py", line 561, in copy_tree
    dry_run=dry_run, condition=condition))
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/util.py", line 564, in copy_tree
    preserve_times, update, dry_run=dry_run)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/util.py", line 138, in copy_file
    _copy_file(source, destination, preserve_mode, preserve_times, update, dry_run)
  File "/Users/sean/.pyenv/versions/better-banner275/lib/python2.7/site-packages/py2app-0.9-py2.7.egg/py2app/util.py", line 151, in _copy_file
    with open(destination, 'wb') as fp_out:
IOError: [Errno 13] Permission denied: '/Users/sean/src/personal/better-banner/better-banner/dist/BetterBanner.app/Contents/Frameworks/QtCore.framework/Contents/Info.plist'

Comments (9)

  1. Ronald Oussoren repo owner

    I'm about to commit a workaround for this issue: remove the destination before copying it.

    That's a workaround because py2app shouldn't copy QtCore more than once, I'll have to install PySide to determine why this happens.

  2. Simon Wiles

    I am experiencing the same issue. The workaround I've been using was to build the app bundle with sudo (didn't have time to be more creative!), but clearly this is less than ideal. I'm using PySide 1.2.2 (installed from Homebrew), and I would be happy to provide the source of the (still pre-Alpha) PySide app I'm developing if it would help.

  3. Sean Fisk reporter

    Simon Wiles: That would be great from my end. We are still having this issue with our PySide app as well, but we worked around it by using an older version of py2app. Not the best solution, but we are facing bigger issues at the moment.

  4. Sean Fisk reporter

    I had a chance to look at this in more detail, and I have a better idea what's causing it. The error is present even on the py2app PySide example on the py2app tip (for me).

    I have used Homebrew to install Qt 4.8.6. Homebrew has a directory where it stores all its package files (/usr/local/Cellar) and links those files into the correct places in /usr/local. py2app is finding both:

    $ cd examples/PySide && python setup.py py2app | grep -F QtCore.framework/Contents/Info.plist
    copying file /usr/local/lib/QtCore.framework/Contents/Info.plist -> /Users/sean/src/checkouts/py2app/examples/PySide/dist/hello.app/Contents/Frameworks/QtCore.framework/Contents/Info.plist
    copying file /usr/local/Cellar/qt/4.8.6/lib/QtCore.framework/Contents/Info.plist -> /Users/sean/src/checkouts/py2app/examples/PySide/dist/hello.app/Contents/Frameworks/QtCore.framework/Contents/Info.plist
    error: /Users/sean/src/checkouts/py2app/examples/PySide/dist/hello.app/Contents/Frameworks/QtCore.framework/Contents/Info.plist: Permission denied
    

    Those actually point to the same file -- the one in lib is a symlink and the Cellar one is the real file. I believe it has to do with macholib finding both. Not sure exactly what's going on, or why both libraries are detected. Hopefully this will provide a bit more insight into the problem.

    EDIT: It's macholib's locating code that is the problem, not modulegraph.

  5. Sean Fisk reporter

    I finally found the problem:

    $ otool -L /usr/local/lib/libpyside-python2.7.dylib
    /usr/local/lib/libpyside-python2.7.dylib:
            /usr/local/lib/libpyside-python2.7.1.2.dylib (compatibility version 1.2.0, current version 1.2.2)
            /usr/local/lib/libshiboken-python2.7.1.2.dylib (compatibility version 1.2.0, current version 1.2.2)
    ---->   /usr/local/lib/QtCore.framework/Versions/4/QtCore (compatibility version 4.8.0, current version 4.8.6)
            /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.0.0)
            /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)
    $ otool -L /usr/local/lib/QtGui.framework/QtGui
    /usr/local/lib/QtGui.framework/QtGui:
            /usr/local/lib/QtGui.framework/Versions/4/QtGui (compatibility version 4.8.0, current version 4.8.6)
    ---->   /usr/local/Cellar/qt/4.8.6/lib/QtCore.framework/Versions/4/QtCore (compatibility version 4.8.0, current version 4.8.6)
            /System/Library/Frameworks/Carbon.framework/Versions/A/Carbon (compatibility version 2.0.0, current version 157.0.0)
            /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)
            /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 1265.19.0)
            /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.0.0)
            /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)
            /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 855.16.0)
            /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
            /System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 48.0.0)
            /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1056.13.0)
            /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 59.0.0)
    

    Note the distinguished lines.

    Because libpyside-python2.7.dylib depends on QtCore.framework in /usr/local/lib and QtGui.framework depends on QtCore.framework in the Cellar, both versions are being found and copying QtCore.framework (which is in fact the same physical file) fails. I'm attempting to work on a solution.

  6. Log in to comment