maketool 0.5.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: maketool
3
+ Version: 0.5.7
4
+ Summary: Python Automation tool for building PySide6 UI and PyInstaller EXE.
5
+ Author-email: Alan Lilly <panofish@gmail.com>
6
+ Requires-Python: >=3.7
7
+ Description-Content-Type: text/markdown
8
+
9
+ # ๐Ÿ› ๏ธ maketool
10
+
11
+ **maketool** is a command-line utility and helper module that simplifies execution and building [PySide6](https://doc.qt.io/qtforpython/) Python 3.7+ applications into executables using **PyInstaller 5.8.0**.
12
+
13
+ It's especially useful for GUI projects that need embedded resources (e.g., `.ui`, `.qrc`) and aim to produce portable `.exe` builds with minimal setup.
14
+
15
+ ---
16
+
17
+ ## โค๏ธ Why Use maketool?
18
+
19
+ If you build windows desktop apps using PySide6 or PyQt, `maketool`:
20
+
21
+ - Saves time when converting `.ui` and `.qrc` files
22
+ - Simplifies PyInstaller builds
23
+ - Packages your application with minimal commands
24
+ - Automates repetitive tasks like building and cleanup
25
+ - Integrates easily with `.bat` scripts or shell commands
26
+ - Is great for both beginners and advanced developers using PySide6
27
+
28
+ ---
29
+
30
+ ## ๐Ÿ”ง Commands
31
+
32
+ ### 1. `run`
33
+
34
+ Executes the Python program after recursively builds all `.ui` and `.qrc` files in the current directory and subdirectories using `pyside6-uic.exe` and `pyside6-rcc.exe`. Then run python source code.
35
+
36
+ Note: it will ONLY rebuild pyside components that are out of date, so that execution is fast as possible, yet always includes any changes.
37
+ FYI: python program is run with pythonw.exe so that the terminal window does not appear.
38
+
39
+ ```cli
40
+ usage: maketool-run [-h] file
41
+
42
+ positional parameters:
43
+ file python file to run
44
+
45
+ parameters:
46
+ -h, --help show this help message and exit
47
+ ```
48
+
49
+ ### 2. `clean`
50
+
51
+ Removes temporary files and build artifacts, including:
52
+
53
+ - `__pycache__` folders
54
+ - `*_ui.py`, `*_rc.py`, `*.pyc`, `*.pyo`
55
+ - PyInstaller `build/` and `dist/` folders
56
+ - `.spec` files in the current directory
57
+
58
+ Run it like this:
59
+
60
+ ```cli
61
+ maketool-clean
62
+ ```
63
+
64
+ ### 3. `compile`
65
+
66
+ Builds your python app into an executable using PyInstaller.
67
+
68
+ - Compiles `.ui` and `.qrc` and image resources
69
+ - Generates a PyInstaller `.spec` file
70
+ - Runs PyInstaller with the provided options
71
+
72
+ ```cli
73
+ usage: maketool-compile [-h] --file FILE --type {onefile,onedir,console} [--icon="myicon.ico"] [--embed="sqlite3.dll"]
74
+
75
+ parameters:
76
+ -h, --help show this help message and exit
77
+ --file FILE python file to compile to exe
78
+ --type {onefile,onedir,console}
79
+ type of exe to build
80
+ --icon ICON ico file
81
+ --embed EMBED comma delimited list of files to embed
82
+ --version VERSION program version (otherwise use __version__ in py source)
83
+ ```
84
+
85
+ ### 4. `sublime`
86
+
87
+ Update local `Sublime Text 4` installation with these useful shortcuts:
88
+
89
+ F1 - insert print statement for variable where cursor is located
90
+ CTRL+BACKSPACE - delete line
91
+ CTRL+ALT+E - open windows explorer where current file is located
92
+ F5 - run pyflakes on current python source
93
+ CTRL+0 - reset font size
94
+ F10 - insert current date/day
95
+ F7 - maketool-run - build and run current python script
96
+
97
+ ```cli
98
+ usage: maketool-sublime [-h] --path PATH
99
+
100
+ parameters:
101
+ -h, --help show this help message and exit
102
+ --path PATH sublime text - data/package/user path
103
+ ```
104
+
105
+ ---
106
+
107
+ ## โœจ Features
108
+
109
+ - ๐Ÿ’ก Simple command-line interface for `.bat` or `.sh` workflows
110
+ - ๐Ÿ”„ Converts all `.ui` and `.qrc` files recursively
111
+ - ๐Ÿงน Cleans up Python build artifacts
112
+ - ๐Ÿ“ฆ Packages apps using PyInstaller
113
+ - ๐Ÿ› ๏ธ Supports custom `.ico` icons
114
+ - ๐Ÿ“Ž Supports optional resource embedding
115
+
116
+ ---
117
+
118
+ ## ๐Ÿ“ฆ Requirements
119
+
120
+ - ๐Ÿ [**Python 3.7+**](https://www.python.org/downloads/)
121
+ - ๐ŸชŸ [**PySide6**](https://pypi.org/project/PySide6/)
122
+ - ๐Ÿ“ฆ [**PyInstaller 5.8**](https://pypi.org/project/pyinstaller/)
123
+
124
+ ---
125
+
126
+ ## ๐Ÿ“ฅ Installation
127
+
128
+ Install maketool helper from PyPI with following command:
129
+
130
+ ```cli
131
+ pip install maketool
132
+ ```
133
+
134
+ ---
135
+
136
+ ## ๐Ÿ“ License
137
+
138
+ MIT License
139
+ ยฉ 2025 Alan Lilly
@@ -0,0 +1,131 @@
1
+ # ๐Ÿ› ๏ธ maketool
2
+
3
+ **maketool** is a command-line utility and helper module that simplifies execution and building [PySide6](https://doc.qt.io/qtforpython/) Python 3.7+ applications into executables using **PyInstaller 5.8.0**.
4
+
5
+ It's especially useful for GUI projects that need embedded resources (e.g., `.ui`, `.qrc`) and aim to produce portable `.exe` builds with minimal setup.
6
+
7
+ ---
8
+
9
+ ## โค๏ธ Why Use maketool?
10
+
11
+ If you build windows desktop apps using PySide6 or PyQt, `maketool`:
12
+
13
+ - Saves time when converting `.ui` and `.qrc` files
14
+ - Simplifies PyInstaller builds
15
+ - Packages your application with minimal commands
16
+ - Automates repetitive tasks like building and cleanup
17
+ - Integrates easily with `.bat` scripts or shell commands
18
+ - Is great for both beginners and advanced developers using PySide6
19
+
20
+ ---
21
+
22
+ ## ๐Ÿ”ง Commands
23
+
24
+ ### 1. `run`
25
+
26
+ Executes the Python program after recursively builds all `.ui` and `.qrc` files in the current directory and subdirectories using `pyside6-uic.exe` and `pyside6-rcc.exe`. Then run python source code.
27
+
28
+ Note: it will ONLY rebuild pyside components that are out of date, so that execution is fast as possible, yet always includes any changes.
29
+ FYI: python program is run with pythonw.exe so that the terminal window does not appear.
30
+
31
+ ```cli
32
+ usage: maketool-run [-h] file
33
+
34
+ positional parameters:
35
+ file python file to run
36
+
37
+ parameters:
38
+ -h, --help show this help message and exit
39
+ ```
40
+
41
+ ### 2. `clean`
42
+
43
+ Removes temporary files and build artifacts, including:
44
+
45
+ - `__pycache__` folders
46
+ - `*_ui.py`, `*_rc.py`, `*.pyc`, `*.pyo`
47
+ - PyInstaller `build/` and `dist/` folders
48
+ - `.spec` files in the current directory
49
+
50
+ Run it like this:
51
+
52
+ ```cli
53
+ maketool-clean
54
+ ```
55
+
56
+ ### 3. `compile`
57
+
58
+ Builds your python app into an executable using PyInstaller.
59
+
60
+ - Compiles `.ui` and `.qrc` and image resources
61
+ - Generates a PyInstaller `.spec` file
62
+ - Runs PyInstaller with the provided options
63
+
64
+ ```cli
65
+ usage: maketool-compile [-h] --file FILE --type {onefile,onedir,console} [--icon="myicon.ico"] [--embed="sqlite3.dll"]
66
+
67
+ parameters:
68
+ -h, --help show this help message and exit
69
+ --file FILE python file to compile to exe
70
+ --type {onefile,onedir,console}
71
+ type of exe to build
72
+ --icon ICON ico file
73
+ --embed EMBED comma delimited list of files to embed
74
+ --version VERSION program version (otherwise use __version__ in py source)
75
+ ```
76
+
77
+ ### 4. `sublime`
78
+
79
+ Update local `Sublime Text 4` installation with these useful shortcuts:
80
+
81
+ F1 - insert print statement for variable where cursor is located
82
+ CTRL+BACKSPACE - delete line
83
+ CTRL+ALT+E - open windows explorer where current file is located
84
+ F5 - run pyflakes on current python source
85
+ CTRL+0 - reset font size
86
+ F10 - insert current date/day
87
+ F7 - maketool-run - build and run current python script
88
+
89
+ ```cli
90
+ usage: maketool-sublime [-h] --path PATH
91
+
92
+ parameters:
93
+ -h, --help show this help message and exit
94
+ --path PATH sublime text - data/package/user path
95
+ ```
96
+
97
+ ---
98
+
99
+ ## โœจ Features
100
+
101
+ - ๐Ÿ’ก Simple command-line interface for `.bat` or `.sh` workflows
102
+ - ๐Ÿ”„ Converts all `.ui` and `.qrc` files recursively
103
+ - ๐Ÿงน Cleans up Python build artifacts
104
+ - ๐Ÿ“ฆ Packages apps using PyInstaller
105
+ - ๐Ÿ› ๏ธ Supports custom `.ico` icons
106
+ - ๐Ÿ“Ž Supports optional resource embedding
107
+
108
+ ---
109
+
110
+ ## ๐Ÿ“ฆ Requirements
111
+
112
+ - ๐Ÿ [**Python 3.7+**](https://www.python.org/downloads/)
113
+ - ๐ŸชŸ [**PySide6**](https://pypi.org/project/PySide6/)
114
+ - ๐Ÿ“ฆ [**PyInstaller 5.8**](https://pypi.org/project/pyinstaller/)
115
+
116
+ ---
117
+
118
+ ## ๐Ÿ“ฅ Installation
119
+
120
+ Install maketool helper from PyPI with following command:
121
+
122
+ ```cli
123
+ pip install maketool
124
+ ```
125
+
126
+ ---
127
+
128
+ ## ๐Ÿ“ License
129
+
130
+ MIT License
131
+ ยฉ 2025 Alan Lilly
@@ -0,0 +1,5 @@
1
+ from .build import main as build
2
+ from .clean import main as clean
3
+ from .compile import main as compile
4
+ from .run import main as run
5
+ from .sublime import main as sublime
@@ -0,0 +1,118 @@
1
+ import sys
2
+ import subprocess
3
+ import os
4
+
5
+ # build .ui and .qrc files in current folder and all sub folders
6
+ # this is typically called from maketool-run or maketool-compile
7
+
8
+ def processfile(fullpath):
9
+
10
+ python3path = os.path.dirname(sys.executable)
11
+ pyrcc = os.path.join(python3path,"Scripts/pyside6-rcc.exe")
12
+ pyuic = os.path.join(python3path,"Scripts/pyside6-uic.exe")
13
+
14
+ path,file = os.path.split(fullpath)
15
+ basename, ext = os.path.splitext(file)
16
+
17
+ infile = path + "/" + file
18
+
19
+ if ext.lower() != ".qrc" and ext.lower() != ".ui": return
20
+
21
+ # process .qrc files
22
+ if ext.lower() == ".qrc":
23
+
24
+ outfile = path + "/" + basename + '_rc.py'
25
+
26
+ if comparefiles(infile, outfile):
27
+ sys.stdout.flush()
28
+
29
+ command = [sys.executable, # path to python interpreter
30
+ pyrcc,
31
+ infile,
32
+ '-o', outfile ]
33
+
34
+ p = subprocess.Popen(command,shell=True) #,stdout = subprocess.PIPE, stderr= subprocess.PIPE) #, startupinfo=startupinfo)
35
+ rc = p.wait()
36
+ # output,error = p.communicate()
37
+
38
+ # process .ui files
39
+ if ext.lower() == ".ui":
40
+
41
+ outfile = path + "/" + basename + '_ui.py'
42
+
43
+ if comparefiles(infile, outfile):
44
+ sys.stdout.flush()
45
+
46
+ command = [sys.executable, # path to python interpreter
47
+ pyuic,
48
+ infile,
49
+ '-o', outfile ]
50
+
51
+ p = subprocess.Popen(command,shell=True) #,stdout = subprocess.PIPE, stderr= subprocess.PIPE) #, startupinfo=startupinfo)
52
+ rc = p.wait()
53
+
54
+ def walk(top, maxdepth):
55
+ dirs, nondirs = [], []
56
+ for name in os.listdir(top):
57
+ (dirs if os.path.isdir(os.path.join(top, name)) else nondirs).append(name)
58
+ yield top, dirs, nondirs
59
+ if maxdepth > 1:
60
+ for name in dirs:
61
+ for x in walk(os.path.join(top, name), maxdepth-1):
62
+ yield x
63
+
64
+ def comparefiles(infile,outfile):
65
+ """
66
+ compare file dates
67
+ return true if infile is newer or if outfile doesn't exist
68
+ """
69
+
70
+ if not os.path.isfile(outfile): # outfile does not exist
71
+ return True
72
+ else: # outfile does exist
73
+ # get infile date and outfile date
74
+ # infile_date = os.path.getmtime(infile)
75
+ # outfile_date = os.path.getmtime(outfile)
76
+
77
+ infile_date = os.stat(infile).st_mtime # quicker than getmtime
78
+ outfile_date = os.stat(outfile).st_mtime # quicker than getmtime
79
+
80
+ # if outfile date < infile date then flag = True
81
+ if (outfile_date < infile_date):
82
+ return True
83
+
84
+ return False
85
+
86
+ def main():
87
+
88
+ python3path = os.path.dirname(sys.executable)
89
+ pyrcc = os.path.join(python3path,"Scripts/pyside6-rcc.exe")
90
+ pyuic = os.path.join(python3path,"Scripts/pyside6-uic.exe")
91
+
92
+ if not os.path.isfile(pyrcc):
93
+ print("Warning: pyrcc not found: {}".format(pyrcc), file=sys.stderr)
94
+ sys.exit(1)
95
+
96
+ if not os.path.isfile(pyuic):
97
+ print("Warning: pyuic not found: {}".format(pyuic), file=sys.stderr)
98
+ sys.exit(1)
99
+
100
+ #-----------------------------------------------------------------
101
+ # build .ui and .qrc files in current folder and all sub folders
102
+ #-----------------------------------------------------------------
103
+
104
+ cwd = os.getcwd() # e.g. cwd = E:\EDMS\python3\MUD
105
+
106
+ # traverse but only to a max depth of 2
107
+ depth = 2
108
+ for path, subdirs, files in walk(cwd, depth):
109
+ path = path.replace("\\", "/") # flip all backslashes to forward slashes because python prefers
110
+ for file in files:
111
+ fullpath = os.path.join(path,file)
112
+ processfile(fullpath)
113
+
114
+ sys.stdout.flush()
115
+
116
+ if __name__ == "__main__":
117
+
118
+ main()
@@ -0,0 +1,102 @@
1
+ # delete __pycache__ folder
2
+ # delete *_ui.py"
3
+ # delete *_rc.py"
4
+ # delete *_pyc.py"
5
+ # delete *_pyo.py"
6
+ # delete pyinstaller build folder
7
+ # delete pyinstaller dist folder
8
+ # delete .spec files in current folder
9
+
10
+ # -----------------------------------------------------------------
11
+ # cleanup pyside6 temp files
12
+ # -----------------------------------------------------------------
13
+
14
+ import sys
15
+ import os
16
+ import shutil
17
+
18
+ # -----------------------------------------------------------------
19
+ # traverse current directory and all subfolders
20
+ # -----------------------------------------------------------------
21
+
22
+ def main():
23
+
24
+ cwd = os.getcwd()
25
+ cwd = cwd.replace("\\", "/")
26
+
27
+ print("")
28
+ print("Cleaning %s" % cwd)
29
+ sys.stdout.flush()
30
+
31
+ for path, subdirs, files in os.walk(cwd):
32
+
33
+ parent, lastfolder = os.path.split(path) # get last folder in path
34
+
35
+ # python prefers forward slashes
36
+ path = path.replace("\\", "/") # flip all backslashes to forward slashes
37
+
38
+ if lastfolder == "__pycache__":
39
+ if os.path.exists(path):
40
+ print(" deleting %s" % path)
41
+ sys.stdout.flush()
42
+ shutil.rmtree(path) # recursively delete folder
43
+ continue
44
+
45
+ for file in files:
46
+
47
+ if file.lower().endswith("_ui.py"):
48
+ print(" removing %s" % file)
49
+ sys.stdout.flush()
50
+ os.remove(path+'/'+file)
51
+
52
+ if file.lower().endswith("_rc.py"):
53
+ print(" removing %s" % file)
54
+ sys.stdout.flush()
55
+ os.remove(path+'/'+file)
56
+
57
+ if file.lower().endswith(".pyc"):
58
+ print(" removing %s" % file)
59
+ sys.stdout.flush()
60
+ os.remove(path+'/'+file)
61
+
62
+ if file.lower().endswith(".pyo"):
63
+ print(" removing %s" % file)
64
+ sys.stdout.flush()
65
+ os.remove(path+'/'+file)
66
+
67
+ # -----------------------------------------------------------------
68
+ # cleanup pyinstaller build and dist folders
69
+ # -----------------------------------------------------------------
70
+
71
+ build_folder = "%s/build" % cwd
72
+ if os.path.exists(build_folder):
73
+ print(" removing %s" % build_folder)
74
+ sys.stdout.flush()
75
+ shutil.rmtree(build_folder) # recursively delete folder
76
+
77
+ dist_folder = "%s/dist" % cwd
78
+ if os.path.exists(dist_folder):
79
+ print(" removing %s" % dist_folder)
80
+ sys.stdout.flush()
81
+ shutil.rmtree(dist_folder) # recursively delete folder
82
+
83
+ # -----------------------------------------------------------------
84
+ # remove .spec files in current folder
85
+ # -----------------------------------------------------------------
86
+
87
+ files = os.listdir(cwd) # get files in current directory
88
+
89
+ for file in files:
90
+
91
+ base, ext = os.path.splitext(file)
92
+
93
+ if ext.lower() == ".spec":
94
+ fullpath = "%s/%s" % (cwd, file)
95
+ print(" removing %s" % fullpath)
96
+ sys.stdout.flush()
97
+ os.remove(fullpath)
98
+
99
+
100
+ if __name__ == "__main__":
101
+
102
+ main()
@@ -0,0 +1,401 @@
1
+ #---------------------------------------------------------------------
2
+ # console app to compile python source to exe using pyinstaller
3
+ #
4
+ # 1. read commandline params
5
+ # 2. build spec file for desired compiletype
6
+ # 3. call pyinstaller build
7
+ #---------------------------------------------------------------------
8
+
9
+ import sys
10
+ import os
11
+ import argparse # commandline option parsing
12
+ import subprocess
13
+ import re
14
+ import ast
15
+
16
+ import maketool
17
+
18
+ def read_version_from_py(pyfile):
19
+ """Safely extract __version__ = 'x.y.z' from a python file."""
20
+ try:
21
+ with open(pyfile, "r", encoding="utf-8") as f:
22
+ tree = ast.parse(f.read(), filename=pyfile)
23
+
24
+ for node in tree.body:
25
+ if isinstance(node, ast.Assign):
26
+ for target in node.targets:
27
+ if isinstance(target, ast.Name) and target.id == "__version__":
28
+ if isinstance(node.value, ast.Str):
29
+ return node.value.s
30
+ return None
31
+ except Exception:
32
+ return None
33
+
34
+ def normalize_version(version):
35
+ """Normalize to 4-part Windows version."""
36
+ if not version:
37
+ return "0.0.0.0"
38
+
39
+ parts = re.split(r"[.\-]", version)
40
+ parts = [p for p in parts if p.isdigit()]
41
+
42
+ while len(parts) < 4:
43
+ parts.append("0")
44
+
45
+ return ".".join(parts[:4])
46
+
47
+ def main():
48
+
49
+ #---------------------------------------------------------------------
50
+ # process commandline options
51
+ #---------------------------------------------------------------------
52
+
53
+ parser = argparse.ArgumentParser(description='Compile python source into exe using pyinstaller')
54
+
55
+ parser.add_argument('--file',
56
+ dest='file',
57
+ required=True,
58
+ help='python file to compile to exe')
59
+
60
+ parser.add_argument('--type',
61
+ dest='type',
62
+ required=True,
63
+ choices=['onefile', 'onedir', 'console'],
64
+ help='type of exe to build')
65
+
66
+ parser.add_argument('--icon',
67
+ dest='icon',
68
+ default='',
69
+ help='ico file')
70
+
71
+ parser.add_argument('--embed',
72
+ dest='embed',
73
+ default='',
74
+ help='comma delimited list of files to embed')
75
+
76
+ parser.add_argument('--version',
77
+ dest='version',
78
+ default='',
79
+ help='product version (overrides internal __version__)')
80
+
81
+ parser._positionals.title = "positional parameters"
82
+ parser._optionals.title = "parameters"
83
+
84
+ argcount = len(sys.argv)
85
+
86
+ # if no args supplied, then display help
87
+ if argcount == 1:
88
+ parser.print_help()
89
+ sys.exit()
90
+
91
+ args = parser.parse_args() # will exit here if required parms are not provided
92
+
93
+ pythonfile = args.file
94
+ iconfile = args.icon
95
+ compiletype = args.type
96
+ embedlist = args.embed
97
+ version = args.version
98
+
99
+ # strip possible quotes and spaces
100
+ pythonfile = pythonfile.strip("\"' ")
101
+ iconfile = iconfile.strip("\"' ")
102
+ compiletype = compiletype.strip("\"' ")
103
+ embedlist = embedlist.strip("\"' ")
104
+ version = version.strip("\"' ")
105
+
106
+ # file must be a file and must exist
107
+ if not os.path.isfile(pythonfile): # use unicode instead of str to convert unicode qstring to python string
108
+ print ( f"ERROR: Python file does not exist: {pythonfile}")
109
+ #sys.stdout.flush()
110
+ sys.exit()
111
+
112
+ # file must be a file and must exist
113
+ if iconfile != "":
114
+ if not os.path.isfile(iconfile): # use unicode instead of str to convert unicode qstring to python string
115
+ print ( f"ERROR: Icon file does not exist: {iconfile}")
116
+ sys.exit()
117
+
118
+ pythonfile = os.path.abspath(pythonfile) # convert to absolute path (spec file expects absolute path)
119
+ pythonfile = pythonfile.replace("\\", "/")
120
+
121
+ py_version = read_version_from_py(pythonfile)
122
+
123
+ raw_version = (
124
+ version
125
+ or py_version
126
+ or "0.0.0.0"
127
+ )
128
+
129
+ version = normalize_version(raw_version)
130
+
131
+ pgm_path = os.path.dirname(pythonfile)
132
+ pgm_filename = os.path.basename(pythonfile)
133
+ pgm_basename, pgm_ext = os.path.splitext(pgm_filename)
134
+
135
+ print ("\n__________________ Compile __________________")
136
+
137
+ print ("----------- Settings -----------")
138
+ print (f" python file: {pgm_filename}")
139
+ print (f" icon file: {iconfile}")
140
+ print (f" compile type: {compiletype}")
141
+ print (f" embed list: {embedlist}")
142
+ print (f" version: {version}")
143
+
144
+ sys.stdout.flush()
145
+
146
+ #-------------------------------------------------------------------
147
+ # build .ui and .qrc files in current folder and all sub folders
148
+ #-------------------------------------------------------------------
149
+
150
+ maketool.build()
151
+
152
+ #-------------------------------------------------------------------
153
+ # create pyinstaller version resource file
154
+ #-------------------------------------------------------------------
155
+
156
+ version_file = os.path.join(pgm_path, f"{pgm_basename}_version.txt")
157
+ major, minor, patch, build = version.split(".")
158
+
159
+ version_resource = f"""
160
+ VSVersionInfo(
161
+ ffi=FixedFileInfo(
162
+ filevers=({major},{minor},{patch},{build}),
163
+ prodvers=({major},{minor},{patch},{build}),
164
+ mask=0x3f,
165
+ flags=0x0,
166
+ OS=0x4,
167
+ fileType=0x1,
168
+ subtype=0x0,
169
+ date=(0,0)
170
+ ),
171
+ kids=[
172
+ StringFileInfo([
173
+ StringTable(
174
+ '040904B0',
175
+ [
176
+ StringStruct('CompanyName', 'GM'),
177
+ StringStruct('ProductName', '{pgm_basename}'),
178
+ StringStruct('ProductVersion', '{version}'),
179
+ StringStruct('FileVersion', '{version}'),
180
+ StringStruct('OriginalFilename', '{pgm_basename}.exe'),
181
+ ]
182
+ )
183
+ ]),
184
+ VarFileInfo([VarStruct('Translation', [1033, 1200])])
185
+ ]
186
+ )
187
+ """.strip()
188
+
189
+ with open(version_file, "w", encoding="utf-8") as vf:
190
+ vf.write(version_resource)
191
+
192
+ print(f"version resource: {version_file}")
193
+
194
+ #-------------------------------------------------------------------
195
+ # build spec file data list for embedding files
196
+ # typically for embedding dll, exe, chm ... etc.
197
+ #-------------------------------------------------------------------
198
+
199
+ # Example a.datas line for file Excel2MySQL.chm
200
+ # a.datas += [('Excel2MySQL.chm', 'Excel2MySQL.chm', 'DATA')]
201
+
202
+ if embedlist == "":
203
+ data = ""
204
+ else:
205
+ data = ""
206
+ embedlist = embedlist.split(',') # convert comma delimited list into a python list using split method
207
+ for item in embedlist:
208
+ filename = os.path.basename(item)
209
+ data += f"a.datas += [('{filename}', '{item}', 'DATA')]\n"
210
+ data = data.strip() # remove leading and trailing blank lines
211
+
212
+ # these files appear unused in many python programs and would therefore only bloat the dist exe.
213
+ # HOWEVER, if your exe crashes or is strangely missing functionality that otherwise appears when you run .py
214
+ # then maybe you need to no longer exclude 1 or more of the following excluded files
215
+ excludeDLLs = """
216
+ to_keep = []
217
+ to_exclude = ['opengl32sw.dll',
218
+ 'Qt6Network.dll',
219
+ 'Qt6Pdf.dll',
220
+ 'Qt6Pdf.dll.dll',
221
+ 'Qt6QmlModels.dll',
222
+ 'Qt6Qml.dll',
223
+ 'libssl-3.dll',
224
+ 'Qt6Quick.dll',
225
+ 'Qt6Svg.dll',
226
+ 'libcrypto-3.dll',
227
+ 'QtNetwork.pyd',
228
+ 'Qt6VirtualKeyboard.dll',
229
+ 'qtuiotouchplugin.dll',
230
+ 'qsvgicon.dll',
231
+ 'qnetworklistmanager.dll',
232
+ 'qtvirtualkeyboardplugin.dll',
233
+ 'qwindowsvistastyle.dll',
234
+ 'qcertonlybackend.dll',
235
+ 'qopensslbackend.dll',
236
+ 'qschannelbackend.dll',
237
+ 'Qt6OpenGL.dll']
238
+ for (dest, source, kind) in a.binaries: # Iterate through the list of included binaries.
239
+ if os.path.split(dest)[1] in to_exclude: continue
240
+ to_keep.append((dest, source, kind))
241
+ a.binaries = to_keep # Replace list of data files with filtered one.
242
+ """
243
+
244
+ # 'unicodedata.pyd',
245
+
246
+ #-------------------------------------------------------------------
247
+ # define iconspec
248
+ #-------------------------------------------------------------------
249
+
250
+ # examples:
251
+ # iconfile = f"myicon.ico"
252
+ # iconfile = f"./icons/myicon.ico"
253
+
254
+ if iconfile != "":
255
+ iconspec = f", icon='{iconfile}'"
256
+ else:
257
+ iconspec = ""
258
+
259
+ #-------------------------------------------------------------------
260
+ # create spec file name
261
+ #-------------------------------------------------------------------
262
+
263
+ specfile = pgm_path + "/" + pgm_basename + ".spec"
264
+ print (f"specfile={specfile}")
265
+ sys.stdout.flush()
266
+
267
+ #-------------------------------------------------------------------
268
+ # build specdata for console or onefile
269
+ #-------------------------------------------------------------------
270
+
271
+ if compiletype != "onedir":
272
+
273
+ if compiletype == "console":
274
+ value = "True"
275
+ elif compiletype == "onefile":
276
+ value = "False"
277
+
278
+ specdata = f"""
279
+ # -*- mode: python -*-
280
+ a = Analysis(['{pythonfile}'],
281
+ pathex=['./lib',
282
+ '../lib',
283
+ '../../lib',
284
+ '../../../lib',
285
+ './widgets',
286
+ './dialogs',
287
+ './views',
288
+ '{pgm_path}',
289
+ ],
290
+ hiddenimports=[])
291
+ {data}
292
+
293
+ {excludeDLLs}
294
+
295
+ for d in a.datas:
296
+ if 'pyconfig' in d[0]:
297
+ a.datas.remove(d)
298
+ break
299
+ pyz = PYZ(a.pure)
300
+ exe = EXE(pyz,
301
+ a.scripts,
302
+ a.binaries,
303
+ a.zipfiles,
304
+ a.datas,
305
+ name='{pgm_basename}.exe',
306
+ debug=False,
307
+ strip=None,
308
+ upx=False,
309
+ version='{version_file}',
310
+ console={value} {iconspec})
311
+ """
312
+
313
+ #-------------------------------------
314
+ # build specdata for onedir
315
+ #-------------------------------------
316
+
317
+ if compiletype == "onedir":
318
+
319
+ specdata = f"""
320
+ # -*- mode: python -*-
321
+ a = Analysis(['{pythonfile}'],
322
+ pathex=['./lib',
323
+ '../lib',
324
+ '../../lib',
325
+ '../../../lib',
326
+ './widgets',
327
+ './dialogs',
328
+ './views',
329
+ '{pgm_path}',
330
+ ],
331
+ hiddenimports=[])
332
+ {data}
333
+
334
+ {excludeDLLs}
335
+
336
+ pyz = PYZ(a.pure)
337
+ exe = EXE(pyz,
338
+ a.scripts,
339
+ exclude_binaries=True,
340
+ name='{pgm_basename}.exe',
341
+ debug=False,
342
+ strip=None,
343
+ upx=False,
344
+ version='{version_file}',
345
+ console=False {iconspec})
346
+ coll = COLLECT(exe,
347
+ a.binaries,
348
+ a.datas,
349
+ strip=None,
350
+ upx=False,
351
+ name='{pgm_basename}')
352
+ """
353
+
354
+ #-------------------------------------------------------------------
355
+ # write specdata to file
356
+ #-------------------------------------------------------------------
357
+
358
+ specdata = specdata.strip() # remove leading and trailing blank lines caused by """ notation
359
+
360
+ fout = open(specfile, 'w') # open file for write
361
+ fout.write(specdata)
362
+ fout.close()
363
+
364
+ print (f"Generated specfile = {specfile}")
365
+
366
+ #-------------------------------------------------------------------
367
+ # calls pyinstaller build
368
+ # c:\python27\python.exe -OO c:\pyinstaller\utils\build.py excel2mysqlCLI.spec
369
+ #-------------------------------------------------------------------
370
+
371
+ print ("Calling Pyinstaller Build...")
372
+ sys.stdout.flush()
373
+
374
+ #sys.stdout.flush() # this will flush and force the print statements to appear immediately.
375
+
376
+ # hide the command window
377
+ startupinfo = subprocess.STARTUPINFO()
378
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
379
+
380
+ # run with basic optimizations
381
+ # python -O -m PyInstaller myscript.py
382
+ # OR discard docstrings
383
+ # python -OO -m PyInstaller myscript.py
384
+
385
+ # command = [sys.executable,'-OO','c:/python37/scripts/pyinstaller-script.py', specfile ] # removes docstrings, so it is slightly smaller than above
386
+ # command = [sys.executable, '-OO', '-m', 'PyInstaller', specfile]
387
+ command = [sys.executable, '-m', 'PyInstaller', specfile] # had to disable -OO because winevt and pycparser has a known bug which crashes pyinstaller
388
+
389
+ p = subprocess.Popen(command,shell=True) #,stdout = subprocess.PIPE, stderr= subprocess.PIPE) #, startupinfo=startupinfo)
390
+
391
+ p.wait()
392
+ output,error = p.communicate()
393
+
394
+ # print ("Compile Done.")
395
+ print("___________________________________________________")
396
+ print("")
397
+ sys.stdout.flush()
398
+
399
+ if __name__ == "__main__":
400
+
401
+ main()
@@ -0,0 +1,66 @@
1
+ import sys
2
+ import os
3
+ import argparse
4
+ import subprocess
5
+ import maketool
6
+
7
+ # read commandline parameters
8
+ # call build.run so we can prebuild pyside6 components before we execute the main script
9
+ # run the main script via pythonw.exe
10
+
11
+ def main():
12
+
13
+ #===================================================================
14
+ # process commandline options
15
+ #===================================================================
16
+
17
+ parser = argparse.ArgumentParser(description='Build ui and qrc files, then run python source')
18
+
19
+ parser.add_argument('file',help='python file to run')
20
+
21
+ parser._positionals.title = "positional parameters"
22
+ parser._optionals.title = "parameters"
23
+
24
+ argcount = len(sys.argv)
25
+
26
+ if argcount == 1:
27
+ parser.print_help()
28
+ print('ERROR: No arguments supplied for PythonRun', file=sys.stderr)
29
+ sys.exit(1)
30
+
31
+ args = parser.parse_args() # will exit here if required parms are not provided
32
+
33
+ pythonfile = args.file
34
+ pythonfile = pythonfile.strip("\"' ") # strip possible quotes and spaces
35
+ pythonfile = os.path.abspath(pythonfile) # convert to absolute path
36
+ pythonfile = pythonfile.replace("\\", "/") # flip all backslashes to forward slashes because python prefers
37
+
38
+ if not os.path.isfile(pythonfile): # use unicode instead of str to convert unicode qstring to python string
39
+ print(f"Python file does not exist: {pythonfile}", file=sys.stderr)
40
+ sys.exit(1)
41
+
42
+ #===================================================================
43
+ # build .ui and .qrc files in current folder and all sub folders
44
+ #===================================================================
45
+
46
+ maketool.build()
47
+
48
+ #===================================================================
49
+ # call pythonw
50
+ #===================================================================
51
+
52
+ command = ['pythonw.exe', '-u', pythonfile] # this is the new python launcher (pyw.exe) which looks for shebang at top of python source and is installed with python3 but will run python2
53
+ p = subprocess.Popen(command,shell=True) #,stdout = subprocess.PIPE, stderr= subprocess.PIPE) #, startupinfo=startupinfo)
54
+
55
+ sys.stdout.flush()
56
+
57
+ rc = p.wait()
58
+
59
+ output,error = p.communicate()
60
+
61
+ sys.stdout.flush()
62
+
63
+
64
+ if __name__ == "__main__":
65
+
66
+ main()
@@ -0,0 +1,146 @@
1
+ import os
2
+ import sys
3
+ import argparse
4
+ import json
5
+ import shutil
6
+
7
+
8
+ def process(user_packages_dir: str):
9
+ """
10
+ Update Pythonw.sublime-build in a Sublime Text 4 installation so that maketool python build will work when F7 pressed.
11
+
12
+ Parameters:
13
+ user_packages_dir (str): Path to the 'Packages/User' folder.
14
+ Example: r"C:/alan/app/sublime text 4/Data/Packages/User"
15
+ """
16
+
17
+ if user_packages_dir is None:
18
+ print("Error: sublime_text user_packages_dir must be provided.")
19
+ sys.exit()
20
+
21
+ keymap_file_path = os.path.join(user_packages_dir, "Default (Windows).sublime-keymap")
22
+ keymap_config = [
23
+ {
24
+ "keys": ["f1"],
25
+ "command": "run_macro_file",
26
+ "args": {"file": "Packages/User/print-python.sublime-macro"},
27
+ "context": [{"key": "selector", "operator": "equal", "operand": "source.python"}]
28
+ },
29
+ {
30
+ "keys": ["ctrl+backspace"],
31
+ "command": "run_macro_file",
32
+ "args": {"file": "res://Packages/Default/Delete Line.sublime-macro"}
33
+ },
34
+ {
35
+ "keys": ["ctrl+alt+e"],
36
+ "command": "open_dir",
37
+ "args": {"dir": "$file_path", "file": "$file_name"}
38
+ },
39
+ {
40
+ "keys": ["f5"],
41
+ "command": "build",
42
+ "args": {"variant": "pyflakes"}
43
+ },
44
+ {
45
+ "keys": ["ctrl+0"],
46
+ "command": "reset_font_size"
47
+ },
48
+
49
+ {
50
+ "keys": ["f10"],
51
+ "command": "insert_date"
52
+ }
53
+ ]
54
+
55
+
56
+ # Backup existing keymap if it exists
57
+ if os.path.exists(keymap_file_path):
58
+ backup_path = keymap_file_path + ".bak"
59
+ shutil.copy2(keymap_file_path, backup_path)
60
+ print(f"Backed up existing keymap to {backup_path}")
61
+
62
+ # Write the new keymap file
63
+ with open(keymap_file_path, "w", encoding="utf-8") as f:
64
+ json.dump(keymap_config, f, indent=4)
65
+
66
+ print(f"Updated Sublime Text: {keymap_file_path}")
67
+
68
+
69
+
70
+
71
+ build_file_path = os.path.join(user_packages_dir, "Pythonw.sublime-build")
72
+ maketool_build_config = {
73
+ "selector": "source.python",
74
+ "shell": True,
75
+ "working_dir": "${file_path}",
76
+ "cmd": ["maketool-run", "$file"],
77
+ "file_regex": "File \"(.*)\", line (.*)",
78
+ "variants": [
79
+ {
80
+ "cmd": ["maketool-build", "${file}"],
81
+ "name": "build",
82
+ "shell": True
83
+ },
84
+ {
85
+ "cmd": ["maketool-clean"],
86
+ "name": "clean",
87
+ "shell": True
88
+ },
89
+ {
90
+ "cmd": ["pyflakes", "${file}"],
91
+ "file_regex": "^(.*?):(\\d+):(\\d+): ([^\\n]+)",
92
+ "name": "pyflakes",
93
+ "shell": True
94
+ },
95
+ {
96
+ "name": "print",
97
+ "command": "run_macro_file",
98
+ "args": {"file": "Packages/User/print.sublime-macro"}
99
+ }
100
+ ]
101
+ }
102
+
103
+ # Backup existing file if it exists
104
+ if os.path.exists(build_file_path):
105
+ backup_path = build_file_path + ".bak"
106
+ shutil.copy2(build_file_path, backup_path)
107
+ print(f"Backed up existing file to {backup_path}")
108
+
109
+ # Ensure directory exists
110
+ print(f"user_packages_dir = {user_packages_dir}")
111
+ os.makedirs(user_packages_dir, exist_ok=True)
112
+
113
+ # Write the new build file
114
+ with open(build_file_path, "w", encoding="utf-8") as f:
115
+ json.dump(maketool_build_config, f, indent=4)
116
+
117
+ print(f"Updated Sublime Text: {build_file_path}")
118
+
119
+
120
+
121
+ def main():
122
+
123
+ #------------------------------------
124
+ # parse command line for path argument and value
125
+ #------------------------------------
126
+ # parser = argparse.ArgumentParser(
127
+ # description="Setup Sublime Text for maketool integration."
128
+ # )
129
+ # parser.add_argument(
130
+ # "--path",
131
+ # required=True,
132
+ # help=r"Path to Sublime Text folder, e.g. C:\alan\app\sublime text 4",
133
+ # )
134
+ # args = parser.parse_args()
135
+ # user_packages_dir = os.path.normpath(args.path)
136
+
137
+ user_packages_dir = os.path.normpath(r"C:\alan\app\sublime text 4")
138
+
139
+ process(user_packages_dir)
140
+
141
+ print("Sublime Text setup complete!")
142
+
143
+
144
+ if __name__ == "__main__":
145
+
146
+ main()
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: maketool
3
+ Version: 0.5.7
4
+ Summary: Python Automation tool for building PySide6 UI and PyInstaller EXE.
5
+ Author-email: Alan Lilly <panofish@gmail.com>
6
+ Requires-Python: >=3.7
7
+ Description-Content-Type: text/markdown
8
+
9
+ # ๐Ÿ› ๏ธ maketool
10
+
11
+ **maketool** is a command-line utility and helper module that simplifies execution and building [PySide6](https://doc.qt.io/qtforpython/) Python 3.7+ applications into executables using **PyInstaller 5.8.0**.
12
+
13
+ It's especially useful for GUI projects that need embedded resources (e.g., `.ui`, `.qrc`) and aim to produce portable `.exe` builds with minimal setup.
14
+
15
+ ---
16
+
17
+ ## โค๏ธ Why Use maketool?
18
+
19
+ If you build windows desktop apps using PySide6 or PyQt, `maketool`:
20
+
21
+ - Saves time when converting `.ui` and `.qrc` files
22
+ - Simplifies PyInstaller builds
23
+ - Packages your application with minimal commands
24
+ - Automates repetitive tasks like building and cleanup
25
+ - Integrates easily with `.bat` scripts or shell commands
26
+ - Is great for both beginners and advanced developers using PySide6
27
+
28
+ ---
29
+
30
+ ## ๐Ÿ”ง Commands
31
+
32
+ ### 1. `run`
33
+
34
+ Executes the Python program after recursively builds all `.ui` and `.qrc` files in the current directory and subdirectories using `pyside6-uic.exe` and `pyside6-rcc.exe`. Then run python source code.
35
+
36
+ Note: it will ONLY rebuild pyside components that are out of date, so that execution is fast as possible, yet always includes any changes.
37
+ FYI: python program is run with pythonw.exe so that the terminal window does not appear.
38
+
39
+ ```cli
40
+ usage: maketool-run [-h] file
41
+
42
+ positional parameters:
43
+ file python file to run
44
+
45
+ parameters:
46
+ -h, --help show this help message and exit
47
+ ```
48
+
49
+ ### 2. `clean`
50
+
51
+ Removes temporary files and build artifacts, including:
52
+
53
+ - `__pycache__` folders
54
+ - `*_ui.py`, `*_rc.py`, `*.pyc`, `*.pyo`
55
+ - PyInstaller `build/` and `dist/` folders
56
+ - `.spec` files in the current directory
57
+
58
+ Run it like this:
59
+
60
+ ```cli
61
+ maketool-clean
62
+ ```
63
+
64
+ ### 3. `compile`
65
+
66
+ Builds your python app into an executable using PyInstaller.
67
+
68
+ - Compiles `.ui` and `.qrc` and image resources
69
+ - Generates a PyInstaller `.spec` file
70
+ - Runs PyInstaller with the provided options
71
+
72
+ ```cli
73
+ usage: maketool-compile [-h] --file FILE --type {onefile,onedir,console} [--icon="myicon.ico"] [--embed="sqlite3.dll"]
74
+
75
+ parameters:
76
+ -h, --help show this help message and exit
77
+ --file FILE python file to compile to exe
78
+ --type {onefile,onedir,console}
79
+ type of exe to build
80
+ --icon ICON ico file
81
+ --embed EMBED comma delimited list of files to embed
82
+ --version VERSION program version (otherwise use __version__ in py source)
83
+ ```
84
+
85
+ ### 4. `sublime`
86
+
87
+ Update local `Sublime Text 4` installation with these useful shortcuts:
88
+
89
+ F1 - insert print statement for variable where cursor is located
90
+ CTRL+BACKSPACE - delete line
91
+ CTRL+ALT+E - open windows explorer where current file is located
92
+ F5 - run pyflakes on current python source
93
+ CTRL+0 - reset font size
94
+ F10 - insert current date/day
95
+ F7 - maketool-run - build and run current python script
96
+
97
+ ```cli
98
+ usage: maketool-sublime [-h] --path PATH
99
+
100
+ parameters:
101
+ -h, --help show this help message and exit
102
+ --path PATH sublime text - data/package/user path
103
+ ```
104
+
105
+ ---
106
+
107
+ ## โœจ Features
108
+
109
+ - ๐Ÿ’ก Simple command-line interface for `.bat` or `.sh` workflows
110
+ - ๐Ÿ”„ Converts all `.ui` and `.qrc` files recursively
111
+ - ๐Ÿงน Cleans up Python build artifacts
112
+ - ๐Ÿ“ฆ Packages apps using PyInstaller
113
+ - ๐Ÿ› ๏ธ Supports custom `.ico` icons
114
+ - ๐Ÿ“Ž Supports optional resource embedding
115
+
116
+ ---
117
+
118
+ ## ๐Ÿ“ฆ Requirements
119
+
120
+ - ๐Ÿ [**Python 3.7+**](https://www.python.org/downloads/)
121
+ - ๐ŸชŸ [**PySide6**](https://pypi.org/project/PySide6/)
122
+ - ๐Ÿ“ฆ [**PyInstaller 5.8**](https://pypi.org/project/pyinstaller/)
123
+
124
+ ---
125
+
126
+ ## ๐Ÿ“ฅ Installation
127
+
128
+ Install maketool helper from PyPI with following command:
129
+
130
+ ```cli
131
+ pip install maketool
132
+ ```
133
+
134
+ ---
135
+
136
+ ## ๐Ÿ“ License
137
+
138
+ MIT License
139
+ ยฉ 2025 Alan Lilly
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ maketool/__init__.py
4
+ maketool/build.py
5
+ maketool/clean.py
6
+ maketool/compile.py
7
+ maketool/run.py
8
+ maketool/sublime.py
9
+ maketool.egg-info/PKG-INFO
10
+ maketool.egg-info/SOURCES.txt
11
+ maketool.egg-info/dependency_links.txt
12
+ maketool.egg-info/entry_points.txt
13
+ maketool.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ maketool-build = maketool.build:main
3
+ maketool-clean = maketool.clean:main
4
+ maketool-compile = maketool.compile:main
5
+ maketool-run = maketool.run:main
6
+ maketool-sublime = maketool.sublime:main
@@ -0,0 +1 @@
1
+ maketool
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "maketool"
3
+ version = "0.5.7"
4
+ description = "Python Automation tool for building PySide6 UI and PyInstaller EXE."
5
+ authors = [{ name = "Alan Lilly", email = "panofish@gmail.com" }]
6
+ readme = "README.md"
7
+ requires-python = ">=3.7"
8
+ dependencies = []
9
+
10
+ [project.scripts]
11
+ maketool-build = "maketool.build:main"
12
+ maketool-clean = "maketool.clean:main"
13
+ maketool-compile = "maketool.compile:main"
14
+ maketool-run = "maketool.run:main"
15
+ maketool-sublime = "maketool.sublime:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+