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.
- maketool-0.5.7/PKG-INFO +139 -0
- maketool-0.5.7/README.md +131 -0
- maketool-0.5.7/maketool/__init__.py +5 -0
- maketool-0.5.7/maketool/build.py +118 -0
- maketool-0.5.7/maketool/clean.py +102 -0
- maketool-0.5.7/maketool/compile.py +401 -0
- maketool-0.5.7/maketool/run.py +66 -0
- maketool-0.5.7/maketool/sublime.py +146 -0
- maketool-0.5.7/maketool.egg-info/PKG-INFO +139 -0
- maketool-0.5.7/maketool.egg-info/SOURCES.txt +13 -0
- maketool-0.5.7/maketool.egg-info/dependency_links.txt +1 -0
- maketool-0.5.7/maketool.egg-info/entry_points.txt +6 -0
- maketool-0.5.7/maketool.egg-info/top_level.txt +1 -0
- maketool-0.5.7/pyproject.toml +15 -0
- maketool-0.5.7/setup.cfg +4 -0
maketool-0.5.7/PKG-INFO
ADDED
|
@@ -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
|
maketool-0.5.7/README.md
ADDED
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -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"
|
maketool-0.5.7/setup.cfg
ADDED