-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcli_launcher.py
executable file
·120 lines (96 loc) · 3.68 KB
/
cli_launcher.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/env python3
"""Launcher for AlgoBOWL CLI.
This is a small little launcher script for the AlgoBOWL CLI. It launches the
CLI creating a Python virtual environment and installing it from GitHub. The
launcher will update the CLI on startup every 24 hours. By design, this has no
dependencies other than Python 3.9+ and is a single file. You should be able to
"chmod +x" this script and put it in your PATH, or put it in your team's Git
repo for everyone on your team to use.
This launcher can be downloaded from:
https://raw.githubusercontent.com/jackrosenthal/algobowl/main/cli_launcher.py
All command line arguments are passed as-is to the real AlgoBOWL CLI. It can
minimally be configured via environment variables:
- ALGOBOWL_VENV: Path to the virtual environment to use (by default, create one
in the XDG cache directory).
- ALGOBOWL_FORCE_UPDATE: Set to 1 to force the launcher to re-build the virtual
environment.
- ALGOBOWL_NO_UPDATE: Set to 1 to force the launcher to not update the virtual
environment.
"""
import datetime
import os
import subprocess
import sys
import venv
from pathlib import Path
assert sys.version_info >= (3, 9), "AlgoBOWL CLI requires Python 3.9+"
def quiet_run(argv) -> None:
"""Run a command, staying quiet unless there's an error."""
try:
subprocess.run(
argv,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding="utf-8",
)
except subprocess.CalledProcessError as e:
print(f"Command failed ({argv})!", file=sys.stderr)
sys.stderr.write(e.stdout)
raise
def get_cache_dir() -> Path:
"""Get the XDG-specified cache directory."""
xdg_cache_home = os.environ.get("XDG_CACHE_HOME")
if xdg_cache_home:
return Path(xdg_cache_home)
return Path.home() / ".cache"
def get_venv_dir() -> Path:
"""Get the path to the virtual environment to use."""
venv_dir = os.environ.get("ALGOBOWL_VENV")
if venv_dir:
return Path(venv_dir)
return (
get_cache_dir()
/ "algobowl"
/ f"venv-{sys.version_info.major}.{sys.version_info.minor}"
)
def venv_cmd(executable: str) -> Path:
"""Get the path to a command in the virtual environment."""
scripts_file = get_venv_dir() / "Scripts" / f"{executable}.exe"
if scripts_file.exists():
return scripts_file
return get_venv_dir() / "bin" / executable
def build_venv() -> None:
"""Build the virtual environment."""
venv.EnvBuilder(
system_site_packages=False,
clear=bool(os.environ.get("ALGOBOWL_FORCE_UPDATE")),
symlinks=sys.platform != "win32",
with_pip=True,
).create(get_venv_dir())
quiet_run([venv_cmd("python"), "-m", "pip", "install", "--upgrade", "pip"])
quiet_run([venv_cmd("python"), "-m", "pip", "install", "--upgrade", "algobowl"])
def update_venv() -> None:
"""Build the virtual environment if necessary."""
if os.environ.get("ALGOBOWL_NO_UPDATE"):
return
update_file = get_venv_dir() / "UPDATE"
force_update = os.environ.get("ALGOBOWL_FORCE_UPDATE")
now = datetime.datetime.now()
if not force_update and update_file.exists():
last_update = datetime.datetime.fromisoformat(
update_file.read_text(encoding="ascii")
)
required_update = last_update + datetime.timedelta(hours=24)
if required_update > now:
return
build_venv()
update_file.write_text(now.isoformat(), encoding="ascii")
def main():
"""The main function."""
update_venv()
sys.exit(
subprocess.run([venv_cmd("algobowl")] + sys.argv[1:], check=False).returncode
)
if __name__ == "__main__":
main()