Digital-Sapphire/PyUpdater

Update Restart Fails on Mac/Linux

syphon1c opened this issue · 9 comments

Hello all,

I have been working with PyUpdater 3.1.1 on Windows which has been working great but recently I have been building some standalone apps to a single file binary (PyInstaller/PyArmor), Python 3.7 on Linux and Mac. The binaries all run just fine once marked as executable (chmod +x), however when an update runs, extracts and replaces the existing binary but as it restarts, it will fail to start/exits.

This is consistent on Mac and Linux and only comes right when I change the newly updated binaries in the folder to be executable again (chmod +x filename). The newly updated binary will then run with no issues, wondering if anyone has come across that before or some pointers in the right direction.

Thanks

@syphon1c That is indeed very strange. I have not seen this behavior and it doesn't show up in our CI. The reason PyUpdater uses tar archives on Linux & Mac is to preserve executable mode.

Maybe it could be useful to write an error check as the last part to the processing stage (after PyUpdater has made the .tar.gz archives), that checks if the "unix executable", is indeed set to be executable.

@NicklasTegner I'm thinking this was caused by using zip archive format on Linux/Mac. If this is indeed the cause, maybe we should block the ability for apps on Linux & Mac to use zip archive format.

I can confirm, that zipping something (in a .zip file) transporting it through the web to another machine (e.g. via download) then unpacking, removes the +x mod on the executable (even on mac, when you generate an .app file)

This seems to be the problem, was using .zip for the Mac and Linux packages, using tar.gz retains the executable attributes and runs updates and restarts perfectly.

so what do you think @JMSwag should we disable .zip on unix based systems?

@NicklasTegner I think so.

@JMSwag @NicklasTegner

Not sure if this is related the issue at hand, but at least it has to do with restarting on MacOS:

As I am not familiar with MacOS, I do not fully understand the reasoning behind the MacOS-related code in AppUpdate._restart(). Perhaps one of you could you explain?

Specifically:

  • The call to os.listdir gets a non-existent path. On Windows, this causes a FileNotFoundError. Even if it would succeed, the output (_file) is never used.

    if not os.path.exists(current_app):
        ...
        mac_app_binary_dir = os.path.join(current_app, "Contents", "MacOS")
        _file = os.listdir(mac_app_binary_dir)
        ...
  • The call to os.path.join gets an absolute path to a python executable as second argument (from sys.executable). On Windows, this just returns the second argument. Is this a special MacOS thing? What would a valid current_app value look like on MacOS?

    current_app = os.path.join(mac_app_binary_dir, sys.executable)

    From the sys.executable docs:

    A string giving the absolute path of the executable binary for the Python interpreter, on systems where this makes sense.

    From the os.path.join docs:

    If a component is an absolute path, all previous components are thrown away and joining continues from the absolute path component.

@dennisvang For Mac application bundles. The code below is the path to the directory of the actual executable. Check out this blog post for more info.

mac_app_binary_dir = os.path.join(current_app, "Contents", "MacOS")