Init
This commit is contained in:
113
ptlinux/launcher_wrapper.py
Normal file
113
ptlinux/launcher_wrapper.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""Render the installed command wrapper with persistent application paths."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
APP_ROOT_TOKEN = "@PTLINUX_APP_ROOT@"
|
||||
INSTALL_DIRECTORY_TOKEN = "@PTLINUX_INSTALL_DIR@"
|
||||
|
||||
|
||||
class LauncherWrapperError(ValueError):
|
||||
"""Raised when a safe launcher wrapper cannot be rendered."""
|
||||
|
||||
|
||||
def quote_shell_word(value: str) -> str:
|
||||
"""Quote one value as a literal POSIX shell word."""
|
||||
|
||||
if not value or any(character in value for character in ("\x00", "\n", "\r")):
|
||||
raise LauncherWrapperError("launcher path contains invalid characters")
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
|
||||
def _absolute_directory(path: Path, label: str) -> Path:
|
||||
directory = path.expanduser()
|
||||
if not directory.is_absolute():
|
||||
raise LauncherWrapperError(f"{label} must be absolute")
|
||||
return directory
|
||||
|
||||
|
||||
def render_launcher_wrapper(
|
||||
template: str,
|
||||
app_root: Path,
|
||||
install_directory: Path,
|
||||
) -> str:
|
||||
"""Embed the selected install layout in one command-wrapper template."""
|
||||
|
||||
for token in (APP_ROOT_TOKEN, INSTALL_DIRECTORY_TOKEN):
|
||||
if template.count(token) != 1:
|
||||
raise LauncherWrapperError(
|
||||
f"launcher template must contain exactly one {token} token"
|
||||
)
|
||||
application = _absolute_directory(app_root, "application directory")
|
||||
installation = _absolute_directory(install_directory, "install directory")
|
||||
if not any(
|
||||
(candidate / "ptlinux").is_dir()
|
||||
for candidate in (
|
||||
application / "current",
|
||||
application / "previous",
|
||||
application,
|
||||
)
|
||||
):
|
||||
raise LauncherWrapperError(f"application files are missing: {application}")
|
||||
rendered = template.replace(APP_ROOT_TOKEN, quote_shell_word(str(application)))
|
||||
rendered = rendered.replace(
|
||||
INSTALL_DIRECTORY_TOKEN,
|
||||
quote_shell_word(str(installation)),
|
||||
)
|
||||
return rendered if rendered.endswith("\n") else rendered + "\n"
|
||||
|
||||
|
||||
def install_launcher_wrapper(
|
||||
template: Path,
|
||||
app_root: Path,
|
||||
install_directory: Path,
|
||||
destination: Path,
|
||||
) -> None:
|
||||
"""Render and atomically install one executable command wrapper."""
|
||||
|
||||
rendered = render_launcher_wrapper(
|
||||
template.read_text(encoding="utf-8"),
|
||||
app_root,
|
||||
install_directory,
|
||||
)
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = destination.with_name(f".{destination.name}.{os.getpid()}.tmp")
|
||||
try:
|
||||
temporary.write_text(rendered, encoding="utf-8")
|
||||
temporary.chmod(0o755)
|
||||
temporary.replace(destination)
|
||||
except BaseException:
|
||||
temporary.unlink(missing_ok=True)
|
||||
raise
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="install the PristonTale EU command wrapper"
|
||||
)
|
||||
parser.add_argument("template", type=Path)
|
||||
parser.add_argument("app_root", type=Path)
|
||||
parser.add_argument("install_directory", type=Path)
|
||||
parser.add_argument("destination", type=Path)
|
||||
arguments = parser.parse_args(argv)
|
||||
try:
|
||||
install_launcher_wrapper(
|
||||
arguments.template,
|
||||
arguments.app_root,
|
||||
arguments.install_directory,
|
||||
arguments.destination,
|
||||
)
|
||||
except (LauncherWrapperError, OSError) as exc:
|
||||
print(f"Could not install command wrapper: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user