python-script-launcher 0.0.10__py3-none-any.whl → 0.0.12__py3-none-any.whl
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.
- python_script_launcher/__main__.py +21 -10
- python_script_launcher/app.py +28 -5
- python_script_launcher/make_exe.py +70 -25
- python_script_launcher/scripts/multi.py +2 -2
- {python_script_launcher-0.0.10.dist-info → python_script_launcher-0.0.12.dist-info}/METADATA +1 -1
- {python_script_launcher-0.0.10.dist-info → python_script_launcher-0.0.12.dist-info}/RECORD +9 -9
- {python_script_launcher-0.0.10.dist-info → python_script_launcher-0.0.12.dist-info}/WHEEL +0 -0
- {python_script_launcher-0.0.10.dist-info → python_script_launcher-0.0.12.dist-info}/entry_points.txt +0 -0
- {python_script_launcher-0.0.10.dist-info → python_script_launcher-0.0.12.dist-info}/top_level.txt +0 -0
|
@@ -66,14 +66,20 @@ def cmd_run(args):
|
|
|
66
66
|
LAUNCHER_FILES = ["app.py", "client.py", "tasks.py"]
|
|
67
67
|
STATIC_DIR = "static"
|
|
68
68
|
SHIM_PKG = "python_script_launcher"
|
|
69
|
+
# Files placed inside the staged `python_script_launcher/` package so the
|
|
70
|
+
# frozen exe supports both `from python_script_launcher import task` and
|
|
71
|
+
# `from python_script_launcher.tasks import task`.
|
|
72
|
+
SHIM_SUBMODULES = ("tasks.py",)
|
|
69
73
|
SHIM_INIT = (
|
|
70
|
-
'"'*3 + "
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"from
|
|
74
|
+
'"'*3 + "Runtime shim generated by launcher build.\n\n"
|
|
75
|
+
"Re-exports the public API from ``python_script_launcher.tasks`` so both\n"
|
|
76
|
+
" from python_script_launcher import task\n"
|
|
77
|
+
" from python_script_launcher.tasks import task\n"
|
|
78
|
+
"work inside a frozen exe. The submodule is copied verbatim from the\n"
|
|
79
|
+
"installed launcher package.\n"
|
|
74
80
|
+ '"'*3 + "\n"
|
|
75
|
-
"from tasks import * # noqa: F401,F403\n"
|
|
76
|
-
"from tasks import task, run_cli, collect_tasks, TaskSpec, TaskParam # noqa: F401\n"
|
|
81
|
+
"from .tasks import * # noqa: F401,F403\n"
|
|
82
|
+
"from .tasks import task, run_cli, collect_tasks, TaskSpec, TaskParam # noqa: F401\n"
|
|
77
83
|
)
|
|
78
84
|
|
|
79
85
|
|
|
@@ -89,12 +95,17 @@ def _stage_project(scripts_src: Path) -> Path:
|
|
|
89
95
|
static_src = PACKAGE_DIR / STATIC_DIR
|
|
90
96
|
if static_src.exists():
|
|
91
97
|
shutil.copytree(static_src, tmp / STATIC_DIR)
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
98
|
+
# Stage a real `python_script_launcher/` package next to the top-level
|
|
99
|
+
# runtime files. Copying `tasks.py` in as a submodule (plus a small
|
|
100
|
+
# re-export shim in __init__.py) makes both `from python_script_launcher
|
|
101
|
+
# import task` and `from python_script_launcher.tasks import task` work
|
|
102
|
+
# inside the frozen exe, matching what the wheel install exposes.
|
|
96
103
|
shim_dir = tmp / SHIM_PKG
|
|
97
104
|
shim_dir.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
for name in SHIM_SUBMODULES:
|
|
106
|
+
src = PACKAGE_DIR / name
|
|
107
|
+
if src.exists():
|
|
108
|
+
shutil.copy2(src, shim_dir / name)
|
|
98
109
|
(shim_dir / "__init__.py").write_text(SHIM_INIT, encoding="utf-8", newline="\n")
|
|
99
110
|
# Copy user scripts into <tmp>/scripts so both scan_scripts and
|
|
100
111
|
# PyInstaller's --add-data pick them up.
|
python_script_launcher/app.py
CHANGED
|
@@ -42,13 +42,35 @@ def _default_root():
|
|
|
42
42
|
return p.parent if p.name == "scripts" else p
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
# The launcher package always ships a `static/` folder next to app.py
|
|
46
|
+
# (repo root during development, python_script_launcher/ after wheel
|
|
47
|
+
# install). Remember it so `run --scripts <dir>` and `build --scripts
|
|
48
|
+
# <dir>` can serve the frontend even when <dir> has no static/ of its own.
|
|
49
|
+
_LAUNCHER_DIR = Path(__file__).parent.resolve()
|
|
50
|
+
|
|
51
|
+
|
|
45
52
|
def _default_static(root: Path):
|
|
53
|
+
"""Resolve the default static directory.
|
|
54
|
+
|
|
55
|
+
Search order:
|
|
56
|
+
1. sys._MEIPASS/static (PyInstaller frozen bundle),
|
|
57
|
+
2. <launcher package>/static (source repo or wheel install),
|
|
58
|
+
3. <user scripts root>/static (lets users override the UI).
|
|
59
|
+
|
|
60
|
+
Returns the first existing candidate, falling back to the last one
|
|
61
|
+
for backward compatibility when nothing is on disk yet.
|
|
62
|
+
"""
|
|
63
|
+
candidates = []
|
|
46
64
|
if getattr(sys, "frozen", False):
|
|
47
|
-
meipass =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
meipass = getattr(sys, "_MEIPASS", "")
|
|
66
|
+
if meipass:
|
|
67
|
+
candidates.append(Path(meipass) / "static")
|
|
68
|
+
candidates.append(_LAUNCHER_DIR / "static")
|
|
69
|
+
candidates.append(root / "static")
|
|
70
|
+
for c in candidates:
|
|
71
|
+
if c.exists():
|
|
72
|
+
return c
|
|
73
|
+
return candidates[-1]
|
|
52
74
|
|
|
53
75
|
|
|
54
76
|
DEFAULT_ROOT = _default_root()
|
|
@@ -622,6 +644,7 @@ def create_app(
|
|
|
622
644
|
candidates.append(Path(sys._MEIPASS) / "static" / "index.html")
|
|
623
645
|
candidates.extend([
|
|
624
646
|
static / "index.html",
|
|
647
|
+
_LAUNCHER_DIR / "static" / "index.html",
|
|
625
648
|
project_root / "static" / "index.html",
|
|
626
649
|
])
|
|
627
650
|
for f in candidates:
|
|
@@ -21,6 +21,7 @@ import os
|
|
|
21
21
|
import shutil
|
|
22
22
|
import subprocess
|
|
23
23
|
import sys
|
|
24
|
+
import tempfile
|
|
24
25
|
from importlib.metadata import distributions
|
|
25
26
|
from pathlib import Path
|
|
26
27
|
|
|
@@ -37,6 +38,14 @@ SKIP_PACKAGES = {
|
|
|
37
38
|
"python-script-launcher",
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
# Files copied into the on-the-fly `python_script_launcher/` package so user
|
|
42
|
+
# scripts can `from python_script_launcher.tasks import task` inside the
|
|
43
|
+
# frozen exe (mirrors publish.py's wheel layout).
|
|
44
|
+
_STAGE_PACKAGE_FILES = (
|
|
45
|
+
"app.py", "client.py", "tasks.py", "__init__.py", "__main__.py",
|
|
46
|
+
)
|
|
47
|
+
_STAGE_PACKAGE_NAME = "python_script_launcher"
|
|
48
|
+
|
|
40
49
|
|
|
41
50
|
def _norm(name: str) -> str:
|
|
42
51
|
return (name or "").strip().lower().replace("_", "-")
|
|
@@ -75,6 +84,23 @@ def _clean_target(name: str, dist_dir: Path, build_dir: Path, project: Path,
|
|
|
75
84
|
shutil.rmtree(p, ignore_errors=True)
|
|
76
85
|
|
|
77
86
|
|
|
87
|
+
def _stage_launcher_package(project: Path) -> Path:
|
|
88
|
+
"""Materialize a temporary `python_script_launcher/` package next to the
|
|
89
|
+
top-level source files so PyInstaller can pick it up via `--paths`.
|
|
90
|
+
|
|
91
|
+
Returns the *root* directory to add to PyInstaller search paths; the
|
|
92
|
+
caller is responsible for removing it once the build is done.
|
|
93
|
+
"""
|
|
94
|
+
stage_root = Path(tempfile.mkdtemp(prefix="psl-exe-"))
|
|
95
|
+
pkg_dir = stage_root / _STAGE_PACKAGE_NAME
|
|
96
|
+
pkg_dir.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
for name in _STAGE_PACKAGE_FILES:
|
|
98
|
+
src = project / name
|
|
99
|
+
if src.exists():
|
|
100
|
+
shutil.copy2(src, pkg_dir / name)
|
|
101
|
+
return stage_root
|
|
102
|
+
|
|
103
|
+
|
|
78
104
|
def build_exe(entry, name, *, project=None, dist=None, static=None,
|
|
79
105
|
noconsole=False, onefile=False, clean=False):
|
|
80
106
|
"""Build a single PyInstaller exe.
|
|
@@ -128,31 +154,50 @@ def build_exe(entry, name, *, project=None, dist=None, static=None,
|
|
|
128
154
|
if scripts_dir.exists():
|
|
129
155
|
cmd += ["--add-data", f"{scripts_dir}{os.pathsep}scripts"]
|
|
130
156
|
|
|
131
|
-
# Make `tasks` and
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
157
|
+
# Make both `tasks` (top-level) and `python_script_launcher.tasks`
|
|
158
|
+
# (wheel-style dotted import) usable from user scripts inside the exe.
|
|
159
|
+
# - `--paths <project>` lets `from tasks import task` resolve.
|
|
160
|
+
# - We also stage a temporary `python_script_launcher/` package that
|
|
161
|
+
# mirrors publish.py wheel layout and add its parent to `--paths`, so
|
|
162
|
+
# `from python_script_launcher.tasks import task` resolves the same
|
|
163
|
+
# way it does when installed from the wheel.
|
|
164
|
+
# If the caller already staged a python_script_launcher/ package into
|
|
165
|
+
# `project` (e.g. via __main__.cmd_build), reuse it verbatim so we do
|
|
166
|
+
# not end up with two competing copies of the same package on --paths.
|
|
138
167
|
if (project / "python_script_launcher" / "__init__.py").exists():
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
168
|
+
stage_root = None
|
|
169
|
+
else:
|
|
170
|
+
stage_root = _stage_launcher_package(project)
|
|
171
|
+
try:
|
|
172
|
+
cmd += ["--paths", str(project)]
|
|
173
|
+
if stage_root is not None:
|
|
174
|
+
cmd += ["--paths", str(stage_root)]
|
|
175
|
+
if (project / "tasks.py").exists():
|
|
176
|
+
cmd += ["--hidden-import", "tasks"]
|
|
177
|
+
cmd += [
|
|
178
|
+
"--hidden-import", _STAGE_PACKAGE_NAME,
|
|
179
|
+
"--hidden-import", f"{_STAGE_PACKAGE_NAME}.tasks",
|
|
180
|
+
"--collect-submodules", _STAGE_PACKAGE_NAME,
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
for pkg in get_installed_packages():
|
|
184
|
+
cmd += ["--collect-all", pkg]
|
|
185
|
+
|
|
186
|
+
if icon_arg:
|
|
187
|
+
cmd += ["--icon", icon_arg]
|
|
188
|
+
|
|
189
|
+
entry_path = Path(entry)
|
|
190
|
+
if not entry_path.is_absolute():
|
|
191
|
+
entry_path = (project / entry_path).resolve()
|
|
192
|
+
cmd.append(str(entry_path))
|
|
193
|
+
|
|
194
|
+
print(f" entry: {entry_path}")
|
|
195
|
+
subprocess.run(cmd, check=True, cwd=str(project))
|
|
196
|
+
out = f"{dist_dir}/{name}.exe" if onefile else f"{dist_dir}/{name}/{name}.exe"
|
|
197
|
+
print(f" [OK] {name} -> {out}\n")
|
|
198
|
+
finally:
|
|
199
|
+
if stage_root is not None:
|
|
200
|
+
shutil.rmtree(stage_root, ignore_errors=True)
|
|
156
201
|
|
|
157
202
|
|
|
158
203
|
def main():
|
|
@@ -186,4 +231,4 @@ def main():
|
|
|
186
231
|
|
|
187
232
|
|
|
188
233
|
if __name__ == "__main__":
|
|
189
|
-
main()
|
|
234
|
+
main()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Multi-task demo using the wheel-style dotted import."""
|
|
2
2
|
from tasks import task
|
|
3
3
|
|
|
4
4
|
|
|
@@ -21,4 +21,4 @@ def multiline(times: int = 3):
|
|
|
21
21
|
for i in range(times):
|
|
22
22
|
print(f"line {i}", flush=True)
|
|
23
23
|
time.sleep(0.05)
|
|
24
|
-
print("done")
|
|
24
|
+
print("done")
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
python_script_launcher/__init__.py,sha256=XeqJdWwu4-26nBlxeu2urarg3chsKMYYAWKgbUQnUyo,1750
|
|
2
|
-
python_script_launcher/__main__.py,sha256=
|
|
3
|
-
python_script_launcher/app.py,sha256=
|
|
2
|
+
python_script_launcher/__main__.py,sha256=siqi1bFNpbuK2ed-fvJ-E1EcLg9KCurFN8dP3fvP7V8,7349
|
|
3
|
+
python_script_launcher/app.py,sha256=Qr5GAhqiHaKs3eZUkJvsyMInDIPGcJb0m8pJ-D1zuUY,25211
|
|
4
4
|
python_script_launcher/client.py,sha256=pULe9xHv4OuXFAdiFAEHp4uxO8x4D_myyNX6ofqElMg,2964
|
|
5
|
-
python_script_launcher/make_exe.py,sha256=
|
|
5
|
+
python_script_launcher/make_exe.py,sha256=4K8gHnfg7FGgD1WvHaxwp9yk1MhpWGtJOXHYpZgc0Ws,8826
|
|
6
6
|
python_script_launcher/tasks.py,sha256=0f6P88vlUXscnIxXNL5pEQpmr6x-kavAHvDB9Z1qowE,5755
|
|
7
7
|
python_script_launcher/scripts/file_status.py,sha256=D22FwHlsNnJkHz7ICyM3L2xK3vhNaMRl-aJEERrEG8s,1063
|
|
8
8
|
python_script_launcher/scripts/hello.py,sha256=df5gR95rta5J0sDORI0wncCTBS0wGrTX07pEcR9R4_A,517
|
|
9
|
-
python_script_launcher/scripts/multi.py,sha256=
|
|
9
|
+
python_script_launcher/scripts/multi.py,sha256=2HApGhrdt8Xs1WZ9HY7yHXAlabqc1KRC6CHqTUhjWy0,590
|
|
10
10
|
python_script_launcher/scripts/test_modules.py,sha256=AFzAAC2NlYSVcPucNgChguLMEj8m1pmCIB3LIDthOf8,618
|
|
11
11
|
python_script_launcher/static/index.html,sha256=_HTLklrRge6A09l2kb5ucFI4rrm2KMIEgU9xXbPJzT8,15000
|
|
12
|
-
python_script_launcher-0.0.
|
|
13
|
-
python_script_launcher-0.0.
|
|
14
|
-
python_script_launcher-0.0.
|
|
15
|
-
python_script_launcher-0.0.
|
|
16
|
-
python_script_launcher-0.0.
|
|
12
|
+
python_script_launcher-0.0.12.dist-info/METADATA,sha256=qBK3LK0GmI_r9v9Qnpxc3_J4KUDMVATVl2KaKjuRHd4,6378
|
|
13
|
+
python_script_launcher-0.0.12.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
14
|
+
python_script_launcher-0.0.12.dist-info/entry_points.txt,sha256=13ENRmw4BsgWGLDCr_y1dKB91ed1j6rEyks6JDFzOns,66
|
|
15
|
+
python_script_launcher-0.0.12.dist-info/top_level.txt,sha256=Ap6WryIb1YRnr_QVCmfGjdEVLDNA3dQcTl_-gxX11qk,23
|
|
16
|
+
python_script_launcher-0.0.12.dist-info/RECORD,,
|
|
File without changes
|
{python_script_launcher-0.0.10.dist-info → python_script_launcher-0.0.12.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_script_launcher-0.0.10.dist-info → python_script_launcher-0.0.12.dist-info}/top_level.txt
RENAMED
|
File without changes
|