Skip to content

Commit c63bbe1

Browse files
committed
improved language support
1 parent 5c21353 commit c63bbe1

File tree

1 file changed

+155
-58
lines changed

1 file changed

+155
-58
lines changed

scripts/runall.py

Lines changed: 155 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import argparse
44
import itertools
5-
import json
65
import os
76
import subprocess
87
import time
@@ -14,6 +13,7 @@
1413
from pathlib import Path
1514
from zlib import crc32
1615
import sqlite3
16+
import shutil
1717

1818
RED = "\033[91m"
1919
GREEN = "\033[92m"
@@ -33,17 +33,20 @@
3333

3434
LANGUAGES = {
3535
"Python": "{year}/day{day}/day{day}.py",
36-
"PyPy": "{year}/day{day}/day{day}.py",
3736
"Rust": "{year}/target/release/day{day}",
3837
"C": "{year}/build/day{day}_c",
3938
"C++": "{year}/build/day{day}_cpp",
4039
}
4140

42-
DEFAULT_LANGUAGES = {"Python", "Rust"}
43-
4441
INTERPRETERS = {
45-
"Python": "python3",
46-
"PyPy": "pypy3",
42+
"Python": {
43+
"Python": "python3",
44+
"PyPy": ".pypy3.10/bin/python",
45+
"Py3.10": ".py3.10/bin/python",
46+
"Py3.11": ".py3.11/bin/python",
47+
"Py3.12": ".py3.12/bin/python",
48+
"Py3.13": ".py3.13/bin/python",
49+
}
4750
}
4851

4952

@@ -61,7 +64,13 @@ def get_cache():
6164
" mtime_ns int,"
6265
" elapsed float,"
6366
" status text,"
64-
" answers text);"
67+
" answers text"
68+
");"
69+
"create table if not exists inputs ("
70+
" key text primary key not null,"
71+
" mtime_ns int,"
72+
" crc32 text"
73+
");"
6574
)
6675
cache = {"db": cache_db, "modified": False}
6776
globals()["_cache"] = cache
@@ -78,20 +87,18 @@ def save_cache():
7887
# print(f"{FEINT}{ITALIC}cache commited{RESET}")
7988

8089

81-
def check_cache(key, file_timestamp: Path, no_age_check=False):
90+
def check_cache(key, file_timestamp: Path, table: str, columns: t.Iterable[str], no_age_check=False):
8291
cache = get_cache()
8392
key = str(key)
8493
db = cache["db"]
85-
cursor = db.execute("select mtime_ns,elapsed,status,answers from solutions where key=?", (key,))
94+
db.row_factory = sqlite3.Row
95+
cursor = db.execute(f"select * from `{table}` where key=?", (key,))
8696
row = cursor.fetchone()
8797
if row:
8898
timestamp = file_timestamp.stat().st_mtime_ns
89-
if row[0] == timestamp or no_age_check:
90-
return {
91-
"elapsed": row[1],
92-
"status": row[2],
93-
"answers": row[3].split("\n"),
94-
}
99+
if row["mtime_ns"] == timestamp or no_age_check:
100+
return dict((column, row[column]) for column in columns)
101+
95102
else:
96103
# seconds = round((timestamp - e["timestamp"]) / 1000000000)
97104
# delta = timedelta(seconds=seconds)
@@ -103,34 +110,40 @@ def check_cache(key, file_timestamp: Path, no_age_check=False):
103110
print(f"{FEINT}{ITALIC}missing cache for {key}{RESET}", end=f"{CLEAR_EOL}{CR}")
104111

105112

106-
def update_cache(key, timestamp: Path, elapsed: float, status: str, answers: t.Iterable):
113+
def update_cache(key, timestamp: Path, table: str, row: t.Dict[str, t.Union[str, int]]) -> None:
107114
cache = get_cache()
108115
db = cache["db"]
109-
key = str(key)
110116

111-
db.execute(
112-
"insert or replace into solutions (key,mtime_ns,elapsed,status,answers) values (?,?,?,?,?)",
113-
(key, timestamp.stat().st_mtime_ns, elapsed, status, "\n".join(answers)),
114-
)
117+
sql = f"insert or replace into `{table}` (key,mtime_ns"
118+
values = [str(key), timestamp.stat().st_mtime_ns]
115119

116-
# cache["modified"] = True
117-
db.commit()
120+
for k, v in row.items():
121+
sql += f",`{k}`"
122+
values.append(v)
118123

119-
return {
120-
"elapsed": elapsed,
121-
"status": status,
122-
"answers": answers,
123-
}
124+
sql += ") values (?,?"
125+
sql += ",?" * len(row)
126+
sql += ")"
127+
128+
db.execute(sql, values)
129+
130+
db.commit()
124131

125132

126-
def run(prog: Path, lang: str, file: Path, solution: t.List, warmup: bool) -> t.Dict[str, t.Any]:
133+
def run(
134+
prog: Path,
135+
lang: str,
136+
interpreter: t.Union[None, str],
137+
file: Path,
138+
solution: t.List,
139+
warmup: bool,
140+
) -> t.Dict[str, t.Any]:
127141
if not prog.is_file():
128142
return
129143

130144
cmd = [prog.absolute().as_posix()]
131145

132146
# add the interpreter
133-
interpreter = INTERPRETERS.get(lang)
134147
if interpreter:
135148
cmd.insert(0, interpreter)
136149

@@ -156,7 +169,7 @@ def run(prog: Path, lang: str, file: Path, solution: t.List, warmup: bool) -> t.
156169
else:
157170
status = "unknown"
158171

159-
return {"elapsed": elapsed, "status": status, "answers": answers.split("\n")}
172+
return {"elapsed": elapsed, "status": status, "answers": answers}
160173

161174

162175
def make(year: Path, source: Path, dest: Path, cmd: str):
@@ -211,7 +224,9 @@ def load_data(filter_year, filter_user):
211224

212225
assert len(f.parts) == 4
213226

214-
if filter_user and f.parent.parent.name != filter_user:
227+
user = f.parent.parent.name
228+
229+
if filter_user and user != filter_user:
215230
continue
216231

217232
year = int(f.parent.name)
@@ -220,12 +235,14 @@ def load_data(filter_year, filter_user):
220235
if filter_year != 0 and year != filter_year:
221236
continue
222237

223-
e = check_cache(f, f)
238+
key = f"{year}:{day}:{user}"
239+
240+
e = check_cache(key, f, "inputs", ("crc32",))
224241
if e:
225-
crc = e["status"]
242+
crc = e["crc32"]
226243
else:
227244
crc = hex(crc32(f.read_bytes().strip()) & 0xFFFFFFFF)
228-
update_cache(f, f, 0, crc, [])
245+
update_cache(key, f, "inputs", {"crc32": crc})
229246

230247
if crc not in inputs[year, day]:
231248
inputs[year, day][crc] = f
@@ -239,7 +256,15 @@ def load_data(filter_year, filter_user):
239256

240257

241258
def run_day(
242-
year: int, day: int, mday: str, day_inputs: t.Dict, day_sols: t.Dict, problems: t.Set, filter_lang, refresh, dry_run
259+
year: int,
260+
day: int,
261+
mday: str,
262+
day_inputs: t.Dict,
263+
day_sols: t.Dict,
264+
languages: dict,
265+
problems: t.Set,
266+
refresh: bool,
267+
dry_run: bool,
243268
):
244269
elapsed = defaultdict(list)
245270

@@ -259,14 +284,7 @@ def run_day(
259284

260285
results = set()
261286

262-
for lang, pattern in LANGUAGES.items():
263-
if filter_lang == "all":
264-
pass
265-
elif filter_lang:
266-
if lang.lower() != filter_lang.lower():
267-
continue
268-
elif lang not in DEFAULT_LANGUAGES:
269-
continue
287+
for lang, (pattern, interpreter) in languages.items():
270288

271289
prog = Path(pattern.format(year=year, day=mday))
272290
key = ":".join(map(str, (year, day, crc, prog, lang.lower())))
@@ -275,16 +293,16 @@ def run_day(
275293
e = None
276294
in_cache = False
277295
else:
278-
e = check_cache(key, prog, dry_run)
296+
e = check_cache(key, prog, "solutions", ("elapsed", "status", "answers"), dry_run)
279297
in_cache = e is not None
280298

281299
if not in_cache and not dry_run:
282300

283-
e = run(prog, lang, file, day_sols.get(crc), warmup[lang])
301+
e = run(prog, lang, interpreter, file, day_sols.get(crc), warmup[lang])
284302

285303
if e:
286304
warmup[lang] = False
287-
e = update_cache(key, prog, e["elapsed"], e["status"], e["answers"])
305+
update_cache(key, prog, "solutions", e)
288306

289307
if not e:
290308
continue
@@ -296,14 +314,16 @@ def run_day(
296314

297315
status_color = {"missing": MAGENTA, "unknown": GRAY, "error": RED, "ok": GREEN}[e["status"]]
298316

317+
answers = e["answers"].split("\n")
318+
299319
line = (
300320
f"{CR}{RESET}{CLEAR_EOL}"
301321
f"{prefix}"
302322
f" {YELLOW}{lang:<7}{RESET}:"
303323
f" {status_color}{e['status']:7}{RESET}"
304324
f" {WHITE}{e['elapsed']/1e9:7.3f}s"
305325
f" {GRAY}{'☽' if in_cache else ' '}"
306-
f" {status_color}{str(e['answers']):<40}{RESET}"
326+
f" {status_color}{str(answers):<40}{RESET}"
307327
f"{info}"
308328
)
309329
print(line)
@@ -314,7 +334,7 @@ def run_day(
314334
if not in_cache and e["elapsed"] / 1e9 > 5:
315335
save_cache()
316336

317-
results.add(" ".join(e["answers"]))
337+
results.add(" ".join(answers))
318338

319339
elapsed[lang].append(e["elapsed"] / 1e9)
320340

@@ -330,9 +350,83 @@ def run_day(
330350
return dict((lang, sum(t) / len(t)) for lang, t in elapsed.items()), nb_samples
331351

332352

353+
def get_languages(filter_lang: t.Iterable[str]) -> t.Dict[str, t.Tuple[str, t.Union[str, None]]]:
354+
355+
filter_lang = set(map(str.casefold, filter_lang or ()))
356+
357+
languages = {}
358+
for lang, v in LANGUAGES.items():
359+
360+
if lang in INTERPRETERS:
361+
for lang2, interpreter in INTERPRETERS[lang].items():
362+
363+
if filter_lang and lang2.casefold() not in filter_lang:
364+
continue
365+
366+
if "/" not in interpreter and "\\" not in interpreter:
367+
interpreter = shutil.which(interpreter)
368+
languages[lang2] = (v, interpreter)
369+
else:
370+
interpreter = Path(interpreter).expanduser().absolute()
371+
if interpreter.is_file() and (interpreter.stat().st_mode & os.X_OK) != 0:
372+
languages[lang2] = (v, interpreter.as_posix())
373+
else:
374+
# print(f"language {lang2} : interpreter {interpreter} not found")
375+
pass
376+
else:
377+
378+
if filter_lang and lang.casefold() not in filter_lang:
379+
continue
380+
languages[lang] = (v, None)
381+
382+
return languages
383+
384+
385+
def install_venv(interpreter: Path):
386+
387+
try:
388+
slug = 'import sys;print(((hasattr(sys, "subversion") and getattr(sys, "subversion")) or ("Py",))[0] + f"{sys.version_info.major}.{sys.version_info.minor}")'
389+
390+
slug = subprocess.check_output([interpreter, "-c", slug]).decode().strip()
391+
392+
venv = "." + slug.lower()
393+
394+
# subprocess.check_call([interpreter, "-mensurepip"])
395+
subprocess.check_output([interpreter, "-mvenv", venv])
396+
subprocess.check_call(
397+
[
398+
f"{venv}/bin/python3",
399+
"-mpip",
400+
"install",
401+
"--no-input",
402+
"--quiet",
403+
"--upgrade",
404+
"pip",
405+
]
406+
)
407+
subprocess.check_call(
408+
[
409+
f"{venv}/bin/python3",
410+
"-mpip",
411+
"install",
412+
"--no-input",
413+
"--quiet",
414+
"--upgrade",
415+
"-r",
416+
"scripts/requirements.txt",
417+
]
418+
)
419+
420+
print(f"Virtual environment for {MAGENTA}{interpreter}{RESET} installed into: {GREEN}{venv}{RESET}")
421+
422+
except subprocess.CalledProcessError as e:
423+
print(e)
424+
425+
333426
def main():
334427
parser = argparse.ArgumentParser()
335-
parser.add_argument("-l", "--language", type=str, metavar="LANG", help="filter by language")
428+
parser.add_argument("--venv", type=Path, help="install virtual environment")
429+
parser.add_argument("-l", "--language", type=str, action="append", metavar="LANG", help="filter by language")
336430
parser.add_argument("-r", "--refresh", action="store_true", help="relaunch solutions")
337431
parser.add_argument("-n", "--dry-run", action="store_true", help="do not run")
338432
parser.add_argument("--no-build", action="store_true", help="do not build")
@@ -341,21 +435,24 @@ def main():
341435

342436
args = parser.parse_args()
343437

344-
filter_year = 0 if len(args.n) == 0 else int(args.n.pop(0))
345-
filter_day = set(args.n)
346-
if args.language == "cpp":
347-
args.language = "c++"
348-
349438
try:
350-
os.chdir(Path(__file__).parent.parent)
351-
352439
problems = []
353440
stats_elapsed = dict()
354441

442+
os.chdir(Path(__file__).parent.parent)
443+
444+
if args.venv:
445+
return install_venv(args.venv)
446+
447+
filter_year = 0 if len(args.n) == 0 else int(args.n.pop(0))
448+
filter_day = set(args.n)
449+
355450
if not args.no_build:
356451
build_all(filter_year)
357452
print(end=f"{CR}{CLEAR_EOL}")
358453

454+
languages = get_languages(args.language)
455+
359456
inputs, sols = load_data(filter_year, args.filter_user)
360457

361458
for year in range(2015, 2024):
@@ -375,8 +472,8 @@ def main():
375472
mday,
376473
inputs[year, day],
377474
sols[year, day],
475+
languages,
378476
problems,
379-
args.language,
380477
args.refresh,
381478
args.dry_run,
382479
)

0 commit comments

Comments
 (0)