Fix GTK Namespace Not Found Error in Linux#756
Conversation
|
I gave this a quick go with the default 'Hello World' app and once I added |
|
@nickzoic Thanks for giving it a test run and for all the groundwork you did on this issue! Are you getting an error when you don't update the system_requires? It shouldn't need the extras now because I updated the gtk plugin to use default Ubuntu paths if those development libraries aren't available. |
|
No worries ... yeah, on e15d783 I was getting errors The host is Ubuntu 20.04.4 but the libs were missing from the docker container so not sure if that matters or why it'd work differently since the container is always 18.04. I had to add those -dev packages and redo ... Creating, Deploying, etc ... The |
|
@nickzoic Did you try to delete |
|
I hadn't thought of that! I'd just been deleting the build directories, I'll give it a go ... oh no, it's not downloading correctly for me now, I end up with a linuxdeploy-plugin-gtk.sh full of HTML! Looks like it failed in a way that wasn't an exception, I'll try and work out why. I do think the idea of having state outside the build directory is fraught, though ... it's bound to come back and bite us at a later date. |
|
@nickzoic Whoops, thanks for helping to test and for all your contributions providing the basis of this PR in the first place! I updated the download URL to fix the issue 👍 |
13e7562 to
19fda7e
Compare
freakboy3742
left a comment
There was a problem hiding this comment.
Looks like a good start; I've got a couple of questions inline.
The other "bigger picture" questions I have are:
- What the plugin is getting us that our existing library discovery isn't getting us? I think it's mostly the typelibdir stubs... is there anything else? What happens if someone uses a library that uses typelib stubs that isn't in the "blessed list" that the Gtk plugin captures?
- What's the impact on the Qt story? Or for any other toolkit that we support?
| plugins = [] | ||
| if app.requires: | ||
| for dependency in app.requires: | ||
| if "toga-gtk" in dependency or "PyGObject" in dependency: |
There was a problem hiding this comment.
Will this catch:
toga-gtk>=0.3.0.dev33? (i.e., a version string on the dependency)?pygobject(i.e., case incompatibility?)- An app that uses a different toolkit that wraps GTK, but doesn't directly import pygobject? (not sure I have any specific examples in mind, but I'm sure someone has built one)
?
On the last example - is there a reason you opted for dependency inspection rather than an explicit list of linuxdeploy plugins needed by the app?
There was a problem hiding this comment.
toga-gtk>=0.3.0.dev33? (i.e., a version string on the dependency)?
Yes, I think so, that is what I tested it with a toga-gtk is in that string.
pygobject (i.e., case incompatibility?)
Updated the implementation to support this, good idea!
An app that uses a different toolkit that wraps GTK, but doesn't directly import pygobject? (not sure I have any specific examples in mind, but I'm sure someone has built one)
No, but we could support this in the future if we had something in mind.
On the last example - is there a reason you opted for dependency inspection rather than an explicit list of linuxdeploy plugins needed by the app?
I think the general use case this was implementing for was users either have a Toga or PyGObject app and those will be listed already in the requires. We could add another option for explicit plugins needed, but I am not sure that this is needed at this time unless we implement a much larger plugin system, and if we do that we could add it at that time.
| for dependency in app.requires: | ||
| if "toga-gtk" in dependency or "PyGObject" in dependency: | ||
| self.logger.info("Using linuxdeploy GTK plugin") | ||
| env["DEPLOY_GTK_VERSION"] = "3" |
There was a problem hiding this comment.
Is this envvar something we can reliably set in code? What happens if someone uses GTK4?
There was a problem hiding this comment.
Right now it is hard coded to support our Toga use case. The plugin does do some automatic detection, but our apps are not successfully being detected. This could be a future PR to extend support for GTK4.
There was a problem hiding this comment.
Yeah, it's meant to auto-detect but I found it wasn't ... the version would have to be the correct one for the toga-gtk version I suppose (see previous conversation about mapping libs to requirements)
There was a problem hiding this comment.
The autodetect doesn't work because it uses ldd to look for executables that are linked against the GTK libraries. Our only executable is Python itself which is not linked against GTK.
| raise CorruptToolError("linuxdeploy") | ||
|
|
||
|
|
||
| class LinuxDeployGtkPlugin: |
There was a problem hiding this comment.
Is there any potential for a generic plugin interface here? I noticed there is a Qt plugin - it seems likely we may need to use that for PySide2/6 deployments
There was a problem hiding this comment.
I think there definitely is a potential for something more general in the future if we add additional plugins. 👍
|
Thanks for the review and feedback, I really appreciate it! Overall the scope of this contribution was to solve the immediate issue that many users in Linux are not able to package a Toga app using the tutorial. I agree that we should also discuss our longer term vision of our strategy for Linux packaging going forward.
If an AppImage should be completely self-contained with no guarantee that a user will have certain libraries (besides a working X Windows or Wayland system) installed, then I think our original implementation was missing other things beyond typelibs including:
This solution only supports typelibs for GTK, if there are other libraries that need typelibs, we would have to add plugins for those or use a different option.
There is a plugin we could add for Qt as well with a similar implementation. I think these two are by far the most complex to get working. Why I discussed other solutions I considered is because I think we need to decide:
If our focus is on Toga and Qt, I think we can extend linuxdeploy. If our focus is on any app, then we shouldn't reinvent the wheel and we should make Briefcase a user friendly wrapper over PyInstaller. For Linux, Windows, and macOS, they have solved the analyzing python code and pulling in all required dependencies so that it can then be packaged problem. |
That certainly explains some of the bug reports we've had - in particular the errors around loading images/icons. I guess the follow up question is why are those pieces not being loaded? Is there any way that we could systematically ensure that pieces like this are found? What is the GTK plugin doing that is "special" in this regard? My understanding was that Linuxdeploy essentially followed the linking chain from every .so that it encountered. This fails as an automated technique as soon as dynamic loading occurs - but as long as you manually specify one of the entry points that is dynamically loaded, the link-following approach can continue. It seems like you're suggesting that even that approach has it's limits, because there are additional files and resources that won't be picked up by this link-following process. This does make some sense - if there's a resource file that is required at runtime, it's going to be very difficult/almost impossible to automatically detect that a .so references that resource file. The problem that we face is that we can include a plugin for GTK, and maybe even Qt, which will work for tutorial cases and simple apps - but if a user writes an app that uses some obscure library, they might end up in a situation where they can't package the app simply because there isn't a linuxdeploy plugin that works for their particular library. Are we in a position (however theoretical) where we could reproduce what the GTK plugin is doing as a matter of configuration? Linuxdeploy doesn't have any real way to define a "configuration" - but we're not constrained in that way. We're in a position to provide almost arbitrarily complex lists of files/directories that need to be passed to the linuxdeploy invocation. If necessary, we could define our own "generic" plugin that uses configuration items in the pyproject.toml file - or we could provide a plugin definition as part of the project template, which would give end-users an easy way to customise what is/isn't included in their own apps. Based on what you know, does this approach sound even remotely viable?
Agreed. A quick look at the qt plugin shows that it's a compiled binary, not just a shell script, which makes me question whether the 'custom plugin in the template' approach I described will work...
Addressing point (2) first - The end goal should be "any app, using any library"; however, I'm also comfortable with a 90% solution that only manages Toga, GTK and Qt apps with no "exotic" libraries as an interim step. Ideally, we'd be able to identify when an app hits this "exotic" boundary so we can report the problem to the user at time of build, rather than have the problem be discovered at runtime by end users. As for point (1) - my enthusiasm for AppImage is driven primarily by the fact that it was the one I was able to get to work... for a definition of "work" that appears to be at least partially accidental. The current implementation has clearly missed some of edge cases, and as distributions diverge from what was present on Ubuntu 18.04, these edge cases are becoming more apparent. The biggest downside to AppImage appears to be that it is a passion project of a very small number of developers who... well, let's just say they don't consistently adhere to the standards of conduct that the BeeWare community expects of its members. For me, the question is two-fold: Supporting both AppImage and Flatpak is also an option, but we'd need to make a call as to which one is the default.
I'm not especially enamoured by the idea of using pyinstaller - that would seem to be a bit of an existential question for Briefcase. Why does Briefcase exist if all it is doing is wrapping another python packaging tool? Working out how to package what is needed for a standalone app is kind of the point of Briefcase; if we can't do it, then we should pack up shop and move on :-) There is undeniably some overlap in what pyinstaller does and what Briefcase does/needs to do - to the extent that I'm sure we can learn from each other - but there's also enough different in the core approaches that I don't think we should give up. |
Yes, that's my understanding as well.
Yup, I agree that is the current limitation of our approach.
I think it sounds viable, and I think a more generalized form of the first alternative I considered. However, we probably need to consider the user experience of this approach. It is very difficult figuring out all the things to include to get a certain dependency to work. PyInstaller did this by allowing for users to create "hooks". The hooks are then shared as part of PyInstaller itself and a contrib library, so once a hook for a certain troublesome library is created, then everyone who uses that library gets that solution from then on.
Yes, I think we can get to a 90% solution, and I think this plus a 2nd PR to add Qt support would get us most of the way there.
I agree that Flatpak is the primary alternative, and I would vote that it is even the default.
One of the reasons that Python has had so much success in the data science field is that the community built on top of what already existed. Seaborn is built on top of matplotlib for example. I think of packaging in a few phases:
Briefcase might not have to solve all the problems for all 3 of those, but it could take the best of those things that exist and glue them together and make them easier for the user.
PyInstaller really only solves the 2nd one above, and only on the current platform, no cross-compilation to mobile for example. It also uses a spec format for configuration that isn't particularly user-friendly. |
|
I don't know if there's any way to get from "requires toga-gtk" to "system_requires libgtk-3-dev and librsvg2-dev" automatically ... maybe python libraries should have such a thing, it'd be super handy instead of the traditional README.md declaration of "first, apt install all this crap, then ..." (It seems like this is the kind of metadata that python packages should already have but ... no? Seems weird there's no PEP for this at least ...) We could maybe get from the 90% solution to the 95% solution for briefcase just by having a little table of "if you require this python library we know you'll need those system libraries". The build process could then implicitly require that, or explicitly add it to the pyproject.toml file, or warn the user to add them, or something. |
We are already able to do this using the briefcase project template - if you specify that you want a Toga app, you get a bunch of libraries included as system defaults. If those two libraries are required, it's easy to add to the template; but from what I'm reading in the discussion, @danyeaw didn't need those libraries to get the app working.
Binary requirements for "the whole of PyPI" is probably out of scope. There might be a possibility of defining |
|
Oh, well, I do love a good Astronomically Out Of Scope :-) If there's really no PEP maybe I should write one. What I mean was a bit of a middle ground though: we currently know toga-gtk's requirements, no problem, but are there other common python packages we also should check for? |
I guess it wouldn't be hard to generate a list of the "top N PyPI binary packages", build a map of those packages to their ubuntu 18.04 system package requirements, and then put those requirements in a data file somewhere (say, in the app template). However, I'm also acutely aware that that statement is also a good chunk of what Conda does out of the box, so... maybe it would better to spend that effort working out how to leverage what Conda can provide, rather than trying to reinvent the wheel (pun 100% intended). |
|
PS: It also seems to be what PyInstaller hooks are for ... |
freakboy3742
left a comment
There was a problem hiding this comment.
The testing that I've done around #757 has convinced me that adopting the GTK plugin is definitely worth adding. However, it's highlighted to me that plugins are a key part of the linuxdeploy ecosystem. I wonder if aiming generic might be the better approach here.
This PR makes a special case of the GTK plugin, and autodetects the need for that plugin. This has 2 downsides:
- The autodetection can fail.
- It only allows for the use of the "official" plugin.
The upside is that there's no need for any additional configuration items.
As a counterproposal: We could add an linuxdeploy_plugins configuration item for the AppImage backend:
linuxdeploy_plugins = ['gtk', 'https://example.com/linuxdeploy-plugin-magic.sh']
This would allow end users to specify whatever combination of plugins that are necessary. We can include a "simple" list of shortcodes(like gtk and qt) that includes the officially supported plugins that expands to the official download URLs; but allow users to provide their own URLs (with a user-provided explicit plugin overriding the builtin). This would allow users to provide their own plugins; or their own bug fixed versions of plugins (e.g., your bugfix version of the GTK plugin).
This wouldn't allow us to handle the DEPLOY_GTK_VERSION=3 environment variable; although we could possible handle this by:
- Setting that environment variable for the user as part of the
gtk'shortcode" - Ensure that variables in the environment used to invoke briefcase are passed to linuxdeploy (i.e., you'd need to invoke
DEPLOY_GTK_VERSION=3 briefcase build linux) - Allow for
DEPLOY_GTK_VERSION=3 gtkas a syntax for declaring plugins - i.e., use the shell convention of setting environment variables in the definition.
(3) would be my preferred of those options, but is obviously more complex to implement.
The downside to this approach is that there is more manual configuration. We can slightly mitigate this by auto-adding the plugins in the base project template, but there's no escaping the fact that end-users will need to know a lot more about Linux packaging to be able to drive the tool effectively. Unfortunately, I'm not sure if this is completely avoidable. Linux packaging is... complex... and trying to completely avoid that is a
Another option would be to split the difference - have autodetection of 'known, supported' plugins, but also allow for manual specification. This might also fit into the solution for #757 - if we know that certain packages must be deployed as source, we can provide that assistance as an automation; but we can also open the door to allow manual configuration when things go wrong.
Any thoughts about this proposal?
6ea55e2 to
cf90ca2
Compare
I like your counterproposal! 👍 For the configuration in the pyproject.toml, maybe it would look something like this? [tool.briefcase.app.helloworld.linux]
requires = [
'toga-gtk>=0.3.0.dev34',
]
system_requires = [
'libgirepository1.0-dev',
'libcairo2-dev',
'libpango1.0-dev',
'libwebkitgtk-3.0-0',
'gir1.2-webkit-3.0',
]
linuxdeploy_plugins = [
'gtk',
]Where we could enter 3 things:
I thought of doing both name and URL, but it seems like it would just make things more complex for the user.
I like your idea to support shell variables, but it may not be needed at this time. Ubuntu didn't have GTK4 in the repos until 21.04, and 22.04 is the first LTS version with it. I am assuming we would want to support 18.04 until 2023 and 20.04 until 2025 for AppImage compatibility. So we wouldn't support GTK4 until 2025, unless someone wants to use Docker to build it from source using JHBuild - I have done this, but it is not trivial. Not to pile on AppImage limitations, but GTK4 was released in December 2020, and it will then take 5 years to support the new toolkit. 😦 I would propose will hold off on this piece until we are a little more sure of our packaging strategy for Linux, and we see a need for passing the environmental variables. YAGNI 😄 |
I hadn't thought of "local path", but that's a good idea.
Yeah - I had similar a thought. However, AFAICT, linuxdeploy plugin naming has a required scheme, so you can derive the name of the plugin from the name of the file to be executed; that makes The one additional detail that occurred to me - if the plugin is user-provided, we won't be able to cache it in The docs for linuxdeploy say that one of the locations that is inspected for plugins is the location of linuxdeploy binary, or the location of the AppDir, which we can use to our advantage - depending on the order of resolution. If linuxdeploy looks in the AppDir first, then a URL-specified plugin can be downloaded/copied and stored next to the AppDir as an artefact of the creation process. If the plugin is a system default, it can be downloaded and cached as you've implemented here. If linuxdeploy looks at the same directory as linuxdeploy first, then we'll need to cache the system plugins in
Totally onboard with YAGNI as an analysis; but what will insert the GTK environment variable in the case of a user-customized GTK plugin? From my testing, the plugin crashes on Briefcase projects if the variable isn't set. |
Great point on caching. I'll need to think about this more and to make sure that it works with the 3 different cases of a known plugin, URL, and path. I think for a path we don't need to cache at all because it will already be a local file.
Another option might be to make the cache logic more advanced, while still avoiding downloading the file if unless there is a cache miss. Maybe we could use the pyproject.toml file's checksum as a cache key and redownload the used plugins if the configuration changes.
Ahh yes! I was focused on the known plugin, and if we need environmental variables for URLs and paths, then I guess we support it for all 3. Thanks for the additional great discussion. I should have time to update the PR this weekend. |
…onment variable paths.
|
For anyone looking at this PR - you can start a new project using the template from beeware/briefcase-template#30 or add: to the linux configuration section of an existing project. No changes are needed to the |
|
Worked out of the box on pop_os 22.04 🦾 A few remarks. These may come down to expectations but but given the whole goal is to avoid target system dependencies, looking for clarification. Below is the listed of loaded libraries for hello world with toga created within docker that did not come from the AppImage: Most of these appear to be fine except for
As for those typelibs coming from As a sidenote, briefcase doesn't have much resiliency against the Docker image disappearing. This PR makes that situation worse, though, since it's passing a |
| # environment *inside* the Docker container. | ||
| if self.use_docker: | ||
| echo_cmd = ["/bin/bash", "-c", "echo $PATH"] | ||
| base_path = self.Docker(self, app).check_output(echo_cmd).strip() |
There was a problem hiding this comment.
This can bomb out if the Docker image disappears.
[helloworld] Configuring Linuxdeploy plugins...
>>> Running Command:
>>> docker run --volume /home/russell/tmp/beeware/helloworld/linux:/app:z --volume /home/russell/.cache/briefcase:/home/brutus/.cache/briefcase:z --rm briefcase/com.example.helloworld:py3.10 /bin/bash
-c 'echo $PATH'
Unable to find image 'briefcase/com.example.helloworld:py3.10' locally
docker: Error response from daemon: pull access denied for briefcase/com.example.helloworld, repository does not exist or may require 'docker login': denied: requested access to the resource is denied.
See 'docker run --help'.
>>> Return code: 125
Log saved to /home/russell/tmp/beeware/helloworld/briefcase.2022_07_21-14_19_42.build.log
Traceback (most recent call last):
File "/home/russell/github/danyeaw/briefcase/venv-3.10/bin/briefcase", line 33, in <module>
sys.exit(load_entry_point('briefcase', 'console_scripts', 'briefcase')())
File "/home/russell/github/danyeaw/briefcase/src/briefcase/__main__.py", line 15, in main
command(**options)
File "/home/russell/github/danyeaw/briefcase/src/briefcase/commands/build.py", line 58, in __call__
state = self._build_app(
File "/home/russell/github/danyeaw/briefcase/src/briefcase/commands/build.py", line 39, in _build_app
state = self.build_app(app, **full_options(state, options))
File "/home/russell/github/danyeaw/briefcase/src/briefcase/platforms/linux/appimage.py", line 173, in build_app
base_path = self.Docker(self, app).check_output(echo_cmd).strip()
File "/home/russell/github/danyeaw/briefcase/src/briefcase/integrations/docker.py", line 324, in check_output
return self._subprocess.check_output(
File "/home/russell/github/danyeaw/briefcase/src/briefcase/integrations/subprocess.py", line 297, in check_output
cmd_output = self._subprocess.check_output(
File "/home/russell/.pyenv/versions/3.10.4/lib/python3.10/subprocess.py", line 420, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/home/russell/.pyenv/versions/3.10.4/lib/python3.10/subprocess.py", line 524, in run
raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['docker', 'run', '--volume', '/home/russell/tmp/beeware/helloworld/linux:/app:z', '--volume', '/home/russell/.cache/briefcase:/home/brutus/.cache/briefcase:z', '--rm', 'briefcase/com.example.helloworld:py3.10', '/bin/bash', '-c', 'echo $PATH']' returned non-zero exit status 125.
There was a problem hiding this comment.
I hit this error a couple of times, but I put it down to the fact that I was doing some "off script" rebuilds and cache refreshes. Definitely worth addressing, but I don't think it's a blocker for this PR specifically.
There was a problem hiding this comment.
When used with beeware/briefcase-template#30, it also worked out of the box for me on Debian 10 (Buster), which was released in 2019. I looked through the linuxdeploy GTK plugin output and saw no concerning messages.
| self.hash = hashlib.sha256() | ||
| for part in url_parts: | ||
| self.hash.update(part.encode("utf-8")) |
There was a problem hiding this comment.
Simply hashing the URL directly would be simpler, and would also have the advantage of allowing the hash to be checked using the command-line sha256sum tool. If there's some benefit to doing it by parts, there should be a comment explaining what it is.
I think it's also possible that doing it by parts could cause different URLs to have the same hash, e.g. http://example.com/filename and http://example.com/file?name.
There was a problem hiding this comment.
Having 2 different URLs produce different hashes was deliberate. The following 3 URLs could all produce different versions of a plugin:
http://example.com/release/linuxdeploy-plugin-foobar.shhttp://example.com/dev/linuxdeploy-plugin-foobar.shhttp://example.com/archive/linuxdeploy-plugin-foobar.sh?version=1
Hashing on just the domain/filename would make the three appear to be the same plugin.
Admittedly, this is an edge case that is unlikely to be an issue in practice, but if it ever comes up, it would be very difficult to diagnose, so I figured we should be proactive. Documenting the why is definitely a good idea, though.
There was a problem hiding this comment.
Oh wait - I see what you're saying now - just hash the URL literal, rather than using HMAC. Yeah - that's a good idea - we're not depending on any cryptographic properties of the HMAC. I was overthinking things because I had the parsed URL parts handy.
| @property | ||
| def plugin_id(self): | ||
| return self.file_name.split(".")[0].split("-")[2] |
There was a problem hiding this comment.
plugin_id is defined sometimes as a property and sometimes as a simple class variable. This makes LinuxDeploy.plugins a bit confusing, because it looks up plugin_id on the class, but when you go looking for its definition you might find the base class property, which can only be looked up on an instance, not a class.
I think it would be clearer to remove the plugin_id overrides in the GTK and Qt classes, and instead make LinuxDeploy.plugins set the known plugin names directly in a dict literal.
There was a problem hiding this comment.
Yeah - I went back and forth on this. The inconsistency is potentially confusing; using a literal in LinuxDeploy.plugins means duplication of information. You're probably right that being confusing is a bigger risk than the duplication, though.
Codecov Report
|
freakboy3742
left a comment
There was a problem hiding this comment.
Following reviews from @mhsmith and @rmartin16, I'm going to call his done. Thanks to @danyeaw for the original work on this!
Summary
This PR fixes a portion of #718 and goes along with linuxdeploy/linuxdeploy-plugin-gtk#33.
The issue is caused by missing typelib libraries for GTK which are not copied by linuxdeploy. The solution is to use the linuxdeploy-plugin-gtk to get the proper libraries installed by GTK. Although packaging GTK apps in older distributions worked, I think that was actually a case of AppImage bleed through to the host system libraries like in #662, to be confirmed.
PR Checklist:
Triage of the Issue
When using the helloworld tutorial using a newer version of Linux, executing
briefcase runresults in the following error:Research in to Linuxdeploy
As part of developing this PR, I read the docs for linuxdeploy and added an overview of what it is to the appimage docs. After I pointed out the existence of the linuxdeploy gtk plugin a couple of weeks ago, I investigated how the main bash script works.
@nickzoic confirmed that he got the plugin to work. Although I was successful at getting the GTK plugin loaded, I was still getting the same Namespace Not Available message. I was somewhat expecting this result because the Namespace unavailable message indicates that Python is not able to load PyGObject introspection itself, not that libraries are missing.
Troubleshooting PyGObject
That's not good, gi can't be imported at all. I referenced the environmental variables here: https://docs.python.org/3/using/cmdline.html#environment-variables.
Let's try to add it manually by setting the PYTHONPATH:
This gives a hint that we are missing the typelib because the introspection is not found. Typelibs provide the namespace for GIRepository, so it makes sense that without them that we would be getting a namespace error. This error was being somewhat masked when we use gi.require_version('Gtk', '3.0') when the app normally loads.
The briefcase-linux-appimage-template and the Linux support package that goes along with provide Python and the AppDir structure only, and this is set up when you run
briefcase create. They don't include any of the other system libraries that are added in when you runbriefcase build. So missing typelibs is caused by linuxdeploy not bundling these extra dependencies. The only supported way to add in extra dependencies with linuxdeployis by creating a plugin.
In Linux, pkg-config is used to determine the library location without having to know where each distro installs each library. To find the location of the GTK typelibs for introspection we run:
In Ubuntu, it would be in a different location:
/usr/lib/x86_64-linux-gnu/girepository-1.0/The linuxdeploy gtk plugin already finds other libraries, so to add the typelibs I added a new tree copy of the typelibs that installs them to the right location in the APPDIR.
Other Solutions Considered
Final Solution
You can now add a new
linuxdeploy_pluginsconfiguration in the linux portion of the config in thepyproject.toml, in order to have linuxdeploy load extra resources in to the AppImage bundle. This is needed for applications that depend on libraries that cannot be automatically discovered by linuxdeploy like GTK. For example,Briefcase can take plugins in three different formats:
gtkis supported.pyproject.tomlfile, like 'packaging/linuxdeploy-gtk-plugin.sh'Some plugins need an environmental variable passed to configure it. For example, the
gtkplugin needs theDEPLOY_GTK_VERSIONenvironmental variable set to 3 or 4. If you use thegtkplugin provided by Briefcase,it is set to 3 by default, but you can override it or set it for other plugins by setting the
DEPLOY_GTK_VERSIONenvironmental variable. For example, when using a relative path, you can set it like this: