Kivy Set Icon in PyInstaller¶
GitHub link¶
AI Transcript provided by KivyWhisper¶
Hello and welcome back to Kivy School. Today we're going to learn how to set a Kivy icon and set a Kivy icon in PyInstaller. Now this first example is going to be an example HelloWorld with a Discord icon set. It's going to be midpoint.py. Then here we have a HelloWorld example and then I will show you that you use self.icon pointing to the icon image in build definition.
So right here in midpoint.py, this is the same HelloWorld that we have been using. And the only thing that's important is this line right here. self.icon equals the icon location. Okay, now I'm just getting away with saying the file name because the icon is in the same spot as the main.py. But if it's in a different spot, you would have to specify it.
So we'll do python midpoint.py and very easily you will see that, okay, this Kivy app has the Discord icon here. It has the Discord icon here, right? Pretty sick. And I can even show you like this. Turn it off. Let's run it again. It will be the Kivy icon, Kivy icon, right? That's just a one line fix. That's how you set the icon. But that's not the real important part.
The real important part is going to be part two, where you will learn how to set the Kivy icon in PyInstaller. That's really important. Now, the steps are install PyInstaller if you haven't already. Go to step three in Kivy School, PyInstaller instructions. And it's going to be python -m PyInstaller --onefile --name helloworldicon main.py
.
So I'm going to go to Kivy School. Then it says go to step three. So step three is choose between one file or one directory. I'm choosing one file because it's a bit more work than one directory. And then here it says enter your virtual environment. We did that inside the virtual environment. You do like this. This is going to be the name of your spec file. So instead of hello world, I said hello world icon right here. And then main.py, which is the py file that is your main.py. It's the one that you're running. So here can be main.py and then run it. It will build the preliminary build. Plus it will create for our case, helloworldicon.spec. So it's going to be right here. It's helloworldicon.spec.
Then in the distribution folder, it has already created midpoint.exe and also helloworldicon.exe. And you can see right here. Pretty cool, right? Change the icon. So now that's done. Let's keep going. So we're still setting the icon from PyInstaller. But keep in mind, we're starting with a midpoint.py.
So executable works, but the icon is not set. Now we have to edit the spec file to add the icon. You're going to add this line to the executable at the end before the last parenthesis. Icon equals this and then add the icon to the data. I will show you in the spec file right here. Add this line right here. Icon 8 discord 48. You also add it to the data. So why am I adding this line in particular? Why am I adding icons to the data?
So why did I add the icon to the data's line? This is because PyInstaller uses a temp folder called MEIPASS for one file applications. Here's the pro. Users just double click your file to start your app. And here's the con. Startup time is a bit longer because PyInstaller must copy to a MEIPASS folder every time you want to run.
So that's a little bit of a cost benefit analysis. If you would like a one file executable, that is the cost for it. I don't think it's that much, especially on the desktop. But if you don't like it, you can always use one folder and just tell the user, OK, you got to look for main.py.exe or whatever in order to run your app. So just letting you know.
Now we're going to add the icon to the data's. And then here it's icons 8 discord 48.png. And then use the double quotes instead of single quotes, because in my test, single quote did not work. It transferred the icon to the PyInstaller temp folder. It didn't work for me. If it works for you, great. Please make a comment in the comment section down below how you got it to work.
And then note this adds the icon to the base folder of the system MEIPASS folder. You want more organization. You should place it in another folder. So this places the icons folder in a resources folder in system MEIPASS. So double quotes, icons, a discord 48.png, double quotes, resources, then double slash. Then you need a double slash because in Windows you need an escape character, which is this slash for a single slash.
So this icon here will be sent to the resources folder in the system MEIPASS folder. And it will be created every time on startup of your PyInstaller app. This still works. But check your current working directory, your CWD. This is because PyInstaller uses the current working directory to find files. If you build from a current working directory, make sure to keep building from the same spot. You can check your current working directory from command prompt using Python import OS OS dot get current working directory.
And this is important because here, as you can see, I just have a raw file. I have not said, you know, see files user. Wow, it's just a raw file. This is because in my current working directory of this 20, 24, 8, 3 folder icon is already here. So it will be found. But if I put it somewhere in like the disk folder in a build folder and a resources folder, it's not in the current working directory.
You got to specify. So let's look at this. Python import OS os.getcwd(). It will say my current working directory is this, you know, see users, the box user desktop, et cetera. And anything in this folder, 20, 24, 8, 3 is going to be found. But if I put it into a resources folder will not be found and I have to specify it. I'll have to go like resources like this. But just letting you know, current working directory really matters for PyInstaller.
So here progress is saved as midpoint.py. Then we're not done yet. You also need to tell Kivy itself to check if it's frozen by PyInstaller. Then find the icon.
You can check if you're running as a standalone EXE by using if has attribute sys and then MEIPASS. Now, this only works for PyInstaller because this is how PyInstaller freezes things. It will set a MEIPASS attribute to sys if you're using something like autopy to exe, Nuitica, something else. But anyways, if you use the other installers. This is not going to work. This is not going to work because they're not guaranteed to use MEIPASS. PyInstaller is the only one that I know for sure that sets the MEIPASS folder.
Otherwise, you have to check their documentation on how you would know if you're running as a standalone executable. Let's go to main.py. As you can see here, I set unbuild. If has attribute sys comma underscore MEIPASS, you set the icon path like this. And then self icon equals MEIPASS icon path. Otherwise, you're not frozen. You just set self icon equals icons aid. Right. That's pretty much it. It's pretty quick.
So what does this line mean? String path, path, sys MEIPASS slash icons aid discord. From the beginning, pathlib.path, sys MEIPASS. This gets you the folder for MEIPASS. And this slash right here is how to combine paths and pathlib. And then icons aid discord848 is just the name of the icon. And at the end, it's going to be path to MEIPASS slash icons aid discord 48.
And it's really good to use pathlib because this is cross platform. You don't have to care if it's a forward slash or a backslash, especially when you're going from Windows to Linux, Mac. So if you use pathlib, you'll be fine. OK. And as you can see right here, Windows uses the backslashes like this. And then because this is an escape character in Python, you'd actually have to double it. That way it only appears as a one slash. Right.
So if you hard coded it, path would be something like this using the backslashes. But on Linux and on Mac, they use these forward slashes like this. And because you hard coded this path, it's going to crash and cry and complain. The PyInstaller would just not compile or worse, it will compile. And then when you turn on the app, the app will crash and then cry and complain. You won't know what's going on. This is the reason why we use pathlib. You can also use os.path to pathlib. This is the newer one. And this is why you shouldn't hard code things, especially like path locations. This is because this will only work on Windows. This will work on Linux and Mac. And just don't suffer. Just use pathlib path and then combine path elements using the forward slash. So that's why we are using pathlib.
Now that we know our icon location in the temp folder, now that we in our dev mode, we can find the file location of our icon. Now let's run the command with the fixed spec file. Python -m, PyInstaller, your spec file. And then I like doing this just so I don't have to think about it. Double dash clean. You don't have to do it. Just delete the old compilation files. Then here are the two fixes you must have in the spec file. Set icon and add the icon in the data's line.
So I'm going to show you the fixes right here. It is like this. So you send the icon to the top level of the MEIPASS folder. And then you set the icon right here of the executable itself using icon equals. Really simple, right? Also, because we're in the same current working directory, you don't need to specify where the location is. If it was in the resources folder, you would go like resources, something like this, or even use, you know, os.path.join or, you know, pathlib slash like this. Either way would work.
OK, so a pathlib, you know, os.getcwd slash icons, it would look something like this. Or os.path.join, os.getcwd, and then icons. It would look something like this if you were to use pathlib or if you were to use a pathlib join instead of hard coding an icon. Actually, you could also say resources. And here you would just do like slash resources. So it would look something like this.
Right, but because it's in the top level, I don't have to do anything special or fancy. I can just say the icon name. OK, and then there's another hint. So for like maybe 30 minutes it was bugging out and I couldn't figure out how to solve it. How I debugged was I used a try accept on the Kivy code. You try running the Kivy app, myapp.run, or accept exception is E, import traceback, print the full exception, and then import time. Now, importing the time is really important because when you're running a PyInstaller executable, if your Kivy loop has closed, nothing is running anymore and the window will instantly exit. But because I have the import exception as E, you will see the traceback and the error and you also have enough time to look at it.
So this is how I debug the code. You know what? Let me actually show you why this is important. I'll be back. OK, so hello. Welcome back. Now, here's an example, the debug example of why I use this try accept block. This is because you will see that the error I made was not turning this pathlib into a string because, let me see if I can move it. Because self icon only takes a string and that string is the path of your icon. And as you can see right there, if you go like this, look at the try accept block, it's OK.
We'll import traceback, right? Here's the error, right? My app icon only accepts string. So that's good. We know the error. But now look at this. It's going to be 10 seconds right here until it exits. If I did not put this time sleep of 10 seconds, it will just run, print the exception for like one nanosecond and then close. It's not fun having to like print screen or like screen record. Don't do that. Don't do extra work. Just do try accept block, add some time and then look at the error, right?
That's a lot safer, simpler, easier and less stressful. So that's why I use this try accept block. That's how I debug. And then let's keep going. So what was the fix? Reminder that pathlib paths are not strings. So to change a path of path to a string, you just put a string around it, right? Pretty simple and easy. Actually, I need to undo that changes well. Okay. And then now works. Both the console and the Kivy app itself have a custom icon.
Actually, there's one thing I forgot to mention, which was that you can also see the error in the old example. Do you see here? Icon is set on this Kivy app. Icon is not set, right? Icon is not and it's bugging out. But here, the one that works, icon is set here, icon is set here. Icon is also set on the Kivy code, right? So pretty neat. And then see here, that Kivy icon py installer now works. Both the console and the Kivy app itself have a custom icon.
And as a recap, you can set the icon in development using sub icon equals path icon. You can also set the icon in py installer. Set the icon in the executable. You update Kivy to check for MEIPASS, check for assist MEIPASS. And send the icon to MEIPASS folder with data, right? And again, I will show you the three line changes here, here, and here. Right? Well, not really three lines, but there's three changes. Okay. So this has been Kivy School. Now you know how to set your icon in py installer, how to set your icon in dev mode. Thank you for watching. Have a great day.
Article Error Reporting¶
Message @BadMetrics on the Kivy Discord.