-
Notifications
You must be signed in to change notification settings - Fork 74
/
build.py
179 lines (140 loc) · 7.19 KB
/
build.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
"""
PyInstaller build script
> python3 -m venv env
> source env/bin/activate (env Scripts activate for Windows)
> python3 -m pip install -r requirements.txt
> python3 -m pip install pyinstaller
> python3 build.py
Platform specific libraries that MIGHT be needed for compiling binaries
Linux
- sudo apt install portaudio19-dev
- sudo apt-get install python3-tk python3-dev
MacOS
- brew install portaudio
- if you're using pyenv, you might also need to install tkinter manually.
I followed this guide https://dev.to/xshapira/using-tkinter-with-pyenv-a-simple-two-step-guide-hh5.
NOTES:
1. For use in future projects, note that pyinstaller will print hundreds of unrelated error messages, but to find
the critical error start scrolling upwards from the bottom and find the first error before it starts cleanup and
destroying resources. It will likely be an import or a path error.
2. Extra steps before using multiprocessing might be required
https://www.pyinstaller.org/en/stable/common-issues-and-pitfalls.html#why-is-calling-multiprocessing-freeze-support-required
3. Change file reads accordingly
https://pyinstaller.org/en/stable/runtime-information.html#placing-data-files-at-expected-locations-inside-the-bundle
4. Code signing for MacOS
https://github.com/pyinstaller/pyinstaller/wiki/Recipe-OSX-Code-Signing
https://developer.apple.com/library/archive/technotes/tn2206/_index.html
https://gist.github.com/txoof/0636835d3cc65245c6288b2374799c43
https://github.com/txoof/codesign
https://github.com/The-Nicholas-R-Barrow-Company-LLC/python3-pyinstaller-base-app-codesigning
https://pyinstaller.org/en/stable/feature-notes.html#macos-binary-code-signing
"""
import os
import platform
import sys
import PyInstaller.__main__
from app.version import version
def build(signing_key=None):
input('Did you remember to increment version.py? ' + str(version))
app_name = 'Open\\ Interface'
compile(signing_key)
macos = platform.system() == 'Darwin'
if macos and signing_key:
# Codesign
os.system(
f'codesign --deep --force --verbose --sign "{signing_key}" dist/{app_name}.app --options runtime')
zip_name = zip()
if macos and signing_key:
keychain_profile = signing_key.split('(')[0].strip()
# Notarize
os.system(f'xcrun notarytool submit --wait --keychain-profile "{keychain_profile}" --verbose dist/{zip_name}')
input(f'Check whether notarization was successful using \n\t xcrun notarytool history --keychain-profile {keychain_profile}.\nYou can check debug logs using \n\t xcrun notarytool log --keychain-profile "{keychain_profile}" <run-id>')
# Staple
os.system(f'xcrun stapler staple dist/{app_name}.app')
# Zip the signed, stapled file
zip_name = zip()
def compile(signing_key=None):
# Path to your main application script
app_script = os.path.join('app', 'app.py')
# Common PyInstaller options
pyinstaller_options = [
'--clean',
'--noconfirm',
# Debug
# '--debug=all',
# --- Basics --- #
'--name=Open Interface',
'--icon=app/resources/icon.png',
'--windowed', # Remove this if your application is a console program, also helps to remove this while debugging
# '--onefile', # NOTE: Might not work on Windows. Also discouraged to enable both windowed and one file on Mac.
# Where to find necessary packages to bundle (python3 -m pip show xxx)
'--paths=./env/lib/python3.12/site-packages',
# Packaging fails without explicitly including these modules here as shown by the logs outputted by debug=all
'--hidden-import=pyautogui',
'--hidden-import=appdirs',
'--hidden-import=pyparsing',
'--hidden-import=ttkbootstrap',
# NOTE: speech_recognition is the name of the directory that this package is in within ../site-packages/,
# whereas the pypi name is SpeechRecognition (pip install SpeechRecognition).
# This was hard to pin down and took a long time to debug.
'--hidden-import=speech_recognition',
# Static files and resources --add-data=src:dest
# - File reads change accordingly - https://pyinstaller.org/en/stable/runtime-information.html#placing-data-files-at-expected-locations-inside-the-bundle
'--add-data=app/resources/*:resources',
# Manually including source code and submodules because app doesn't launch without it
'--add-data=app/*.py:.',
'--add-data=app/utils/*.py:utils', # Submodules need to be included manually
'--add-data=app/models/*.py:models', # Submodules need to be included manually
app_script
]
# Platform-specific options
if platform.system() == 'Darwin': # MacOS
if signing_key:
pyinstaller_options.extend([
f'--codesign-identity={signing_key}'
])
# Apple Notarization has a problem because this binary used in speech_recognition is signed with too old an SDK
from PyInstaller.utils.osx import set_macos_sdk_version
set_macos_sdk_version('env/lib/python3.12/site-packages/speech_recognition/flac-mac', 10, 9, 0) # NOTE: Change the path according to where your binary is located
elif platform.system() == 'Linux':
pyinstaller_options.extend([
'--hidden-import=PIL._tkinter_finder',
'--hidden-import=openai',
'--onefile'
])
elif platform.system() == 'Windows':
pyinstaller_options.extend([
'--onefile'
])
# Run PyInstaller with the specified options
PyInstaller.__main__.run(pyinstaller_options)
print('Done. Check dist/ for executables.')
def zip():
# Zip the app
print('Zipping the executables')
app_name = 'Open\\ Interface'
zip_name = 'Open-Interface-v' + str(version)
if platform.system() == 'Darwin': # MacOS
if platform.processor() == 'arm':
zip_name = zip_name + '-MacOS-M-Series' + '.zip'
else:
zip_name = zip_name + '-MacOS-Intel' + '.zip'
# Special zip command for macos to keep the complex directory metadata intact to keep the codesigning valid
zip_cli_command = 'cd dist/; ditto -c -k --sequesterRsrc --keepParent ' + app_name + '.app ' + zip_name
elif platform.system() == 'Linux':
zip_name = zip_name + '-Linux.zip'
zip_cli_command = 'cd dist/; zip -r9 ' + zip_name + ' ' + app_name
elif platform.system() == 'Windows':
zip_name = zip_name + '-Windows.zip'
zip_cli_command = 'cd dist & powershell Compress-Archive -Path \'Open Interface.exe\' -DestinationPath ' + zip_name
# input(f'zip_cli_command - {zip_cli_command} \nExecute?')
os.system(zip_cli_command)
return zip_name
if __name__ == '__main__':
apple_code_signing_key = None
if len(sys.argv) > 1:
apple_code_signing_key = sys.argv[1] # python3 build.py "Developer ID Application: ... (...)"
print("apple_code_signing_key: ", apple_code_signing_key)
elif len(sys.argv) == 1 and platform.system() == 'Darwin':
input("Are you sure you don't wanna sign your code? ")
build(apple_code_signing_key)