Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pyi-makespec does not include all spec file options by default. #8230

Open
ElliotGarbus opened this issue Jan 13, 2024 · 12 comments
Open

pyi-makespec does not include all spec file options by default. #8230

ElliotGarbus opened this issue Jan 13, 2024 · 12 comments
Labels
feature Feature request triage Please triage and relabel this issue

Comments

@ElliotGarbus
Copy link

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
When running pyi-makespec, all the spec file options are not included. For example the icon item, and content_directory items are only included if those options are on the pyi-makespec command line.

Describe the solution you'd like
A clear and concise description of what you want to happen.
All of the items that can be included in the spec file should be in the spec with their default options. The command line options should be used for providing the values. Then "entry" should always be provided. This is true for most of the options, but not icon and content_directory. This problem may exist for other options, I have not done a comprehensive test.

@ElliotGarbus ElliotGarbus added feature Feature request triage Please triage and relabel this issue labels Jan 13, 2024
@rokm
Copy link
Member

rokm commented Jan 14, 2024

All of the items that can be included in the spec file should be in the spec with their default options.

I disagree. In my view, the generated spec file should ideally be minimal, without advanced options that were not explicitly set by the user on the command-line; both to avoid clutter and to avoid giving impression that they are mandatory. It also makes it easier to change the default in the future, if necessary. Not to mention that there are options that are not mapped to the CLI at all.

So if anything, I would be pushing for the opposite direction, pruning the template even further and removing for example bootloader_ignore_signals, strip, upx, upx_exclude, runtime_tmpdir, disable_windowed_traceback, argv_emulation, target_arch, codesign_identity, entitlements_file unless they are set.

Although long-term, it might make sense to move to some standard template library to generate the spec file, and then we could perhaps have both a minimal and a verbose/self-descriptive mode for the spec file.

@ElliotGarbus
Copy link
Author

Interesting perspective. The core issue for me was the "incompatible change" to moving all of the data files to the _internal dir.
The addition of the contents_dir in the specfile would have served as an indication of the change. I finally backed into discovering the change, and that by setting the contents_dir I could go back to the old structure.

Perhaps there is now a documentation error: https://pyinstaller.org/en/stable/runtime-information.html#run-time-information
"Your app should run in a bundle exactly as it does when run from source. " This is not true with the default value of the contents_dir. It appears to be a requirement that if the default value is to be used then the current working directory needs to be changed to maintain relative paths for the bundled and unbundled apps.

I do like your suggestion of a verbose/self-descriptive mode of the specfile. Links back to the appropriate sections in the documentation would be really great.

@bwoodsend
Copy link
Member

"Your app should run in a bundle exactly as it does when run from source. " This is not true with the default value of the contents_dir. It appears to be a requirement that if the default value is to be used then the current working directory needs to be changed to maintain relative paths for the bundled and unbundled apps.

No, if you think you need to change working directory to access files then your code is using relative paths, making it broken. You should be using paths anchored to __file__ as per the docs.

@ElliotGarbus
Copy link
Author

ElliotGarbus commented Jan 14, 2024

I added the following code:

p = Path(__file__).parent
os.chdir(p)

This allowed the program to run the same bundled and unbundled. Do you have a different recommendation?

The need to add this code suggests a gap in the documentation that was created with the 6.x release.

@rokm
Copy link
Member

rokm commented Jan 14, 2024

If you need to change current working directory, then your code somewhere else is using relative paths (thus assuming that you know what the current working directory is). Those relative paths should be changed to absolute ones, anchored to __file__, to make the code fully portable and relocatable.

Otherwise, your unfrozen code falls apart when you try to run it from an arbitrary location, and if you tried to freeze it in onefile mode, it would have also fallen apart with PyInstaller < 6.0. What changed in 6.0 is that this now also applies to onedir builds, i.e., the "top-level application directory" (sys._MEIPASS) from PyInstaller's perspective is not the directory with the executable (which, again, was not the case in other build types with PyInstaller < 6.0).

@ElliotGarbus
Copy link
Author

@rokm Yes, my code is using relative paths. This is for single dir builds only.
Lets assume a data file located in the same dir as the executable. In the code is a call to load the file:

load_file('mydata.txt')

It appears there are 3 options for accessing this file:

  1. dynamically change the current working directory at program startup to accommodate the location when bundled to _internal
  2. set the content_directory="."
  3. change the path of all data files to be rooted in file

and that your recommendation is option 3. I am currently using option 2. Is my interpretation correct?
Options 1 and 2 allow the change to be made in one place in the program, rather than every place a relative data file is accessed.

The docs: https://pyinstaller.org/en/stable/runtime-information.html#run-time-information should be more prescriptive. The opening paragraph that suggests that says things will "run in a bundle exactly as it does when run from source", needs to be changed. For example, it could say, "Changes need to be made so that data files that rely on relative paths can be accessed when bundled and not bundled. It is recommended that all data files are accessed relative to the location of the executable use one of the the following techniques...

@bwoodsend
Copy link
Member

Option 3 is the only one that is not invalid. This is not a PyInstaller specific thing (which is why it would be inaccurate for the docs to imply that PyInstaller's runtime is doing anything differently) -- in fact it's not even a Python specific thing. If you write a program containing a relative path to a resource in any programming language, then in a terminal cd to a location that is not your application's root and run it, it'll break in exactly the same way.

@ElliotGarbus
Copy link
Author

ElliotGarbus commented Jan 14, 2024

Isn't this a function of what is being built? A command line utility vs a GUI app shipped in a Windows Installer or a Mac App bundle? i agree with your point if the app is a command line app.

FWIW I've shipped lots of gui apps using relative paths and had no customer issues.

@bwoodsend
Copy link
Member

Only in the sense that some application launchers (including those used by Windows and macOS desktops) set the working directory to that of the executable which meant that a relative path in a PyInstaller onedir application used to work by a happenstance. If instead of using PyInstaller, you'd written a bat file containing python C:\full\path\to\your\application.py then put that bat file on your desktop and ran it, your working directory would have been the desktop instead and the application would have failed.

@ElliotGarbus
Copy link
Author

ElliotGarbus commented Jan 14, 2024

Is it happenstance or convention that working directory is set to the executable by the launcher? Should I be concerned that this could change?

I hear you from this discussion, clearly. Do not use relative paths for data files. There are risks. This guidance is not so clear in the pyinstaller docs.

@rokm
Copy link
Member

rokm commented Jan 14, 2024

Is it happenstance or convention that working directory is set to the executable by the launcher? Should I be concerned that this could change?

I think if you double-click on an exe in Windows, the current working directory will be set to its parent directory (unless explicitly specified in the settings; or maybe that's just for shortcuts?). If you run it from another program (or command prompt), then the current working directory is inherited.

So with old PyInstaller < 6.0 onedir layout, where top-level application directory was in the same directory as the executable itself, it worked by happenstance. Now it doesn't work anymore, and in general, you should not be making assumptions about current working directory, because that is not portable - as I mentioned, top-level application directory is not the same as the executable's directory in onefile builds (and neither in PyInstaller >= 6.0 onedir builds), nor in macOS .app bundle builds.

I hear you from this discussion, clearly. Do not use relative paths for data files. There are risks. This guidance is not so clear in the pyinstaller docs.

Because that's not specific to PyInstaller, really. We shouldn't have to be the ones teaching people to write portable and relocatable code...

@ElliotGarbus
Copy link
Author

ElliotGarbus commented Jan 14, 2024

Thank you for sharing your thoughts and insights.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Feature request triage Please triage and relabel this issue
Projects
None yet
Development

No branches or pull requests

3 participants