diff --git a/README.md b/README.md index b20a8a6..0edcc5c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ $ ./setup.sh The script creates `.env` and installs the Python dependencies listed in `pyproject.toml`. If `uv` is available, it uses `uv venv` and `uv pip install`; otherwise it uses the standard `venv` module and `pip`. +It also builds a local precompiled header under `.env/pch/` to speed up repeated C++ compilations. Add `bin/` to your `PATH` so the tools can be invoked with short command names: diff --git a/bin/build_pch.py b/bin/build_pch.py new file mode 100644 index 0000000..e6af97b --- /dev/null +++ b/bin/build_pch.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +import hashlib +import json +from pathlib import Path +import shutil +import subprocess + +from run import COMPILER, COMPILE_FLAGS, PCH_BUILD_FLAGS, PCH_CONTENT, PCH_GCH, PCH_HEADER + + +def compiler_identity() -> dict[str, str | int]: + compiler_path = shutil.which(COMPILER) + if compiler_path is None: + raise FileNotFoundError(f"Compiler not found: {COMPILER}") + + resolved_compiler = Path(compiler_path).resolve() + cc1plus = subprocess.check_output( + [COMPILER, "-print-prog-name=cc1plus"], text=True + ).strip() + cc1plus_path = Path(cc1plus).resolve() + + return { + "path": str(resolved_compiler), + "mtime_ns": resolved_compiler.stat().st_mtime_ns, + "cc1plus_path": str(cc1plus_path), + "cc1plus_mtime_ns": cc1plus_path.stat().st_mtime_ns, + "version": subprocess.check_output( + [COMPILER, "-dumpfullversion", "-dumpversion"], text=True + ).strip(), + } + + +def pch_signature() -> str: + payload = { + "compiler": compiler_identity(), + "compile_flags": COMPILE_FLAGS, + "pch_build_flags": PCH_BUILD_FLAGS, + "content": PCH_CONTENT, + } + encoded = json.dumps(payload, sort_keys=True).encode() + return hashlib.sha256(encoded).hexdigest() + + +def main() -> int: + PCH_HEADER.parent.mkdir(parents=True, exist_ok=True) + if not PCH_HEADER.exists() or PCH_HEADER.read_text(encoding="utf8") != PCH_CONTENT: + PCH_HEADER.write_text(PCH_CONTENT, encoding="utf8") + + stamp_path = PCH_HEADER.with_suffix(PCH_HEADER.suffix + ".sha256") + signature = pch_signature() + + if PCH_GCH.exists() and stamp_path.exists() and stamp_path.read_text().strip() == signature: + print(f"PCH is up to date: {PCH_GCH}") + return 0 + + command = [ + COMPILER, + *PCH_BUILD_FLAGS, + "-x", + "c++-header", + str(PCH_HEADER), + "-o", + str(PCH_GCH), + ] + + print(f"Building PCH: {PCH_GCH}") + PCH_GCH.unlink(missing_ok=True) + subprocess.run(command, check=True) + if not PCH_GCH.is_file(): + raise RuntimeError(f"Compiler did not create PCH: {PCH_GCH}") + stamp_path.write_text(signature + "\n", encoding="utf8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/bin/run.py b/bin/run.py index 462ba25..2bb7bbd 100644 --- a/bin/run.py +++ b/bin/run.py @@ -1,11 +1,36 @@ import os import subprocess +from pathlib import Path from message import debug_print -from path import BIN_PATH +from path import BIN_PATH, PROJ_PATH import time +COMPILER = "g++-15" +COMPILE_FLAGS = [ + "-std=gnu++23", + "-O2", + "-Wall", + "-Wextra", + "-march=native", + # "-flto=auto", + "-fopenmp", + "-pthread", + "-ftrivial-auto-var-init=zero", + "-fconstexpr-depth=1024", + "-fconstexpr-loop-limit=524288", + "-fconstexpr-ops-limit=2097152", + "-DYSN_DEBUG", +] +PCH_BUILD_FLAGS = COMPILE_FLAGS.copy() +LINK_FLAGS = [ + "-lstdc++exp", +] +PCH_CONTENT = "#include \n" +PCH_HEADER = Path(PROJ_PATH) / ".env" / "pch" / "stdcxx_all.hpp" +PCH_GCH = Path(str(PCH_HEADER) + ".gch") + def exec_path_of(source_path): return os.path.splitext(source_path)[0] + ".exe" @@ -36,25 +61,14 @@ def compile(source_path): debug_print(f"Compiling {source_path} ...") start_time = time.time() + pch_flags = ["-I", str(PCH_HEADER.parent), "-include", PCH_HEADER.name] if PCH_GCH.exists() else [] compilation = subprocess.run([ - "g++-15", - "-std=gnu++23", - "-O2", - "-Wall", - "-Wextra", - "-march=native", - "-flto=auto", - "-fmodules", - "-fopenmp", - "-pthread", - "-lstdc++exp", - "-ftrivial-auto-var-init=zero", - "-fconstexpr-depth=1024", - "-fconstexpr-loop-limit=524288", - "-fconstexpr-ops-limit=2097152", - "-DYSN_DEBUG", + COMPILER, + *COMPILE_FLAGS, + *pch_flags, source_path, - "-o", exec_path + "-o", exec_path, + *LINK_FLAGS, ]) end_time = time.time() duration = end_time - start_time diff --git a/documentation.md b/documentation.md index 4cc68d9..e12cdb3 100644 --- a/documentation.md +++ b/documentation.md @@ -105,6 +105,7 @@ ly `setup.sh` は `.env` を作成し、`pyproject.toml` に記述された Python 依存を導入します。 `uv` が使える環境では `uv venv` と `uv pip install` を使い、なければ標準の `venv` と `pip` にフォールバックします。 +セットアップ時には `.env/pch/` に `bits/stdc++.h` 用のローカル PCH も作成し、以後の C++ コンパイルで利用します。 `ly` や Graphviz 出力で使う `tree` と `dot` コマンドが見つからない場合は、追加で必要なシステムコマンドとして案内します。 C++ 側のテストは `CMakeLists.txt` と `vcpkg.json` で管理されます。 diff --git a/fixg++-15.sh b/fixg++-15.sh new file mode 100644 index 0000000..4e52817 --- /dev/null +++ b/fixg++-15.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -euo pipefail + +# https://github.com/orgs/Homebrew/discussions/6229#discussioncomment-14079111 + +brew update +brew upgrade +brew install binutils +brew reinstall binutils +sudo update-alternatives \ + --install /usr/bin/as as /home/linuxbrew/.linuxbrew/opt/binutils/bin/as 100 \ + --slave /usr/bin/ld ld /home/linuxbrew/.linuxbrew/opt/binutils/bin/ld \ + --slave /usr/bin/nm nm /home/linuxbrew/.linuxbrew/opt/binutils/bin/nm \ + --slave /usr/bin/objdump objdump /home/linuxbrew/.linuxbrew/opt/binutils/bin/objdump \ + --slave /usr/bin/objcopy objcopy /home/linuxbrew/.linuxbrew/opt/binutils/bin/objcopy \ + --slave /usr/bin/strip strip /home/linuxbrew/.linuxbrew/opt/binutils/bin/strip +sudo update-alternatives --config as \ No newline at end of file diff --git a/setup.sh b/setup.sh index 119a4ca..94d7aeb 100644 --- a/setup.sh +++ b/setup.sh @@ -37,6 +37,8 @@ else python -m pip install "${python_dependencies[@]}" fi +.env/bin/python bin/build_pch.py + missing_commands=() if ! command -v tree >/dev/null 2>&1; then