diff --git a/.gitignore b/.gitignore index 9b1a06b3c..50a05bb03 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,8 @@ build/ cmake-build-*/ .clangd CMakeUserPresets.json +# machine-specific Conan cache paths, generated by scripts/gen-vscode-env.py +.vscode/.env offline/ ## PDF CJK CMap input data (fetched by tools/pdf/generate_cid_data.py) diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..751ec8340 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "ms-vscode.cmake-tools", + "llvm-vs-code-extensions.vscode-clangd", + "vadimcn.vscode-lldb", + "twxs.cmake" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..0cce01e4f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,69 @@ +{ + "version": "0.2.0", + "inputs": [ + { + "id": "gtestFilter", + "type": "promptString", + "description": "GoogleTest --gtest_filter", + "default": "*" + }, + { + "id": "translateInput", + "type": "promptString", + "description": "Input document to translate (absolute path)" + }, + { + "id": "translateOutput", + "type": "promptString", + "description": "Output directory for HTML", + "default": "${workspaceFolder}/cmake-build-relwithdebinfo/output" + }, + { + "id": "metaInput", + "type": "promptString", + "description": "Input document for meta" + } + ], + "configurations": [ + { + "name": "Debug odr_test (filtered)", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/cmake-build-relwithdebinfo/test/odr_test", + "args": ["--gtest_filter=${input:gtestFilter}"], + "cwd": "${workspaceFolder}/cmake-build-relwithdebinfo", + "envFile": "${workspaceFolder}/.vscode/.env", + "preLaunchTask": "build: odr_test" + }, + { + "name": "Debug translate ", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/cmake-build-relwithdebinfo/cli/translate", + "args": ["${input:translateInput}", "${input:translateOutput}"], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.vscode/.env", + "preLaunchTask": "build: translate" + }, + { + "name": "Debug meta ", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/cmake-build-relwithdebinfo/cli/meta", + "args": ["${input:metaInput}"], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.vscode/.env", + "preLaunchTask": "build: all CLI tools" + }, + { + "name": "Debug server", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/cmake-build-relwithdebinfo/cli/server", + "args": [], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.vscode/.env", + "preLaunchTask": "build: all CLI tools" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..81eca727d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,32 @@ +{ + // ---- Build: CMake Tools drives the Conan-generated presets ---- + // Presets come from CMakeUserPresets.json -> cmake-build-*/CMakePresets.json, + // written by `conan install`. Pick the active one from the status bar. + "cmake.useCMakePresets": "always", + "cmake.options.statusBarVisibility": "visible", + + // ---- IntelliSense: clangd (matches the repo's .clangd / .clang-tidy) ---- + // Disable the built-in cpptools engine so it doesn't fight clangd. + "C_Cpp.intelliSenseEngine": "disabled", + "clangd.arguments": [ + "--compile-commands-dir=${workspaceFolder}/cmake-build-relwithdebinfo", + "--background-index", + "--clang-tidy", + "--header-insertion=never", + "--pretty" + ], + + // ---- Formatting: clang-format (.clang-format at repo root) ---- + "editor.formatOnSave": false, + "[cpp]": { + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" + }, + "[c]": { + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" + }, + + "files.associations": { + "*.hpp": "cpp", + "*.ipp": "cpp" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..576b74009 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,120 @@ +{ + "version": "2.0.0", + "options": { + "cwd": "${workspaceFolder}" + }, + "inputs": [ + { + "id": "gtestFilter", + "type": "promptString", + "description": "GoogleTest --gtest_filter", + "default": "*" + }, + { + "id": "translateInput", + "type": "promptString", + "description": "Input document to translate (absolute path)" + }, + { + "id": "translateOutput", + "type": "promptString", + "description": "Output directory for HTML (absolute path)", + "default": "${workspaceFolder}/cmake-build-relwithdebinfo/output" + } + ], + "tasks": [ + { + "label": "build: odr (library)", + "type": "shell", + "command": "cmake --build cmake-build-relwithdebinfo --target odr", + "group": "build", + "problemMatcher": ["$gcc"] + }, + { + "label": "build: odr_test", + "type": "shell", + "command": "cmake --build cmake-build-relwithdebinfo --target odr_test", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": ["$gcc"] + }, + { + "label": "build: translate", + "type": "shell", + "command": "cmake --build cmake-build-relwithdebinfo --target translate", + "group": "build", + "problemMatcher": ["$gcc"] + }, + { + "label": "build: all CLI tools", + "type": "shell", + "command": "cmake --build cmake-build-relwithdebinfo --target translate back_translate meta server", + "group": "build", + "problemMatcher": ["$gcc"] + }, + { + "label": "test: run odr_test (filtered)", + "type": "shell", + "command": "${workspaceFolder}/scripts/run-with-env.sh ./test/odr_test --gtest_filter='${input:gtestFilter}'", + "options": { + "cwd": "${workspaceFolder}/cmake-build-relwithdebinfo" + }, + "dependsOn": ["build: odr_test"], + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "cli: translate ", + "type": "shell", + "command": "scripts/run-with-env.sh ./cmake-build-relwithdebinfo/cli/translate '${input:translateInput}' '${input:translateOutput}'", + "dependsOn": ["build: translate"], + "problemMatcher": [] + }, + { + "label": "compare: reference output (headless)", + "detail": "Runs compare-html in Docker (Firefox headless) against test/data/reference-output. Build + run odr_test first to populate output.", + "type": "shell", + "command": "test/scripts/compare_output.sh", + "problemMatcher": [] + }, + { + "label": "compare: reference output (server on :8000)", + "detail": "Runs compare-html-server in Docker; open http://localhost:8000 to inspect diffs interactively. Ctrl-C in the terminal to stop.", + "type": "shell", + "command": "test/scripts/compare_output_server.sh", + "isBackground": true, + "problemMatcher": [] + }, + { + "label": "env: regenerate .vscode/.env", + "detail": "Re-run after every `conan install` to refresh Conan cache paths.", + "type": "shell", + "command": "python3 scripts/gen-vscode-env.py", + "problemMatcher": [] + }, + { + "label": "profile: Instruments Time Profiler (odr_test, filtered)", + "detail": "Records a .trace with Instruments; opens in Instruments.app when done.", + "type": "shell", + "command": "rm -rf profile.trace && ${workspaceFolder}/scripts/run-with-env.sh xctrace record --template 'Time Profiler' --output profile.trace --launch -- ./test/odr_test --gtest_filter='${input:gtestFilter}' && open profile.trace", + "options": { + "cwd": "${workspaceFolder}/cmake-build-relwithdebinfo" + }, + "dependsOn": ["build: odr_test"], + "problemMatcher": [] + }, + { + "label": "profile: Instruments Time Profiler (translate)", + "detail": "Records a .trace of the translate CLI on one file.", + "type": "shell", + "command": "rm -rf profile.trace && scripts/run-with-env.sh xctrace record --template 'Time Profiler' --output profile.trace --launch -- ./cmake-build-relwithdebinfo/cli/translate '${input:translateInput}' '${input:translateOutput}' && open profile.trace", + "dependsOn": ["build: translate"], + "problemMatcher": [] + } + ] +} diff --git a/scripts/gen-vscode-env.py b/scripts/gen-vscode-env.py new file mode 100755 index 000000000..3dd12b975 --- /dev/null +++ b/scripts/gen-vscode-env.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Extract the runtime environment from the Conan-generated CMake test preset +into a plain KEY=VALUE file that VS Code launch configs (CodeLLDB `envFile`) and +tasks can consume. + +The paths point into the Conan cache and change whenever dependencies are +re-installed, so re-run this after every `conan install` (there is a VS Code task +"env: regenerate .vscode/.env" that does exactly this). + +Usage: scripts/gen-vscode-env.py [BUILD_DIR] + BUILD_DIR defaults to cmake-build-relwithdebinfo. +""" +import json +import os +import re +import sys +from pathlib import Path + +repo = Path(__file__).resolve().parent.parent +build_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else repo / "cmake-build-relwithdebinfo" +preset = build_dir / "CMakePresets.json" + +if not preset.is_file(): + sys.exit(f"no CMakePresets.json in {build_dir} — run `conan install` first") + +data = json.loads(preset.read_text()) +test_presets = data.get("testPresets") or [{}] +env = test_presets[0].get("environment", {}) + + +def resolve(value: str) -> str: + # Resolve `$penv{VAR}` (parent/current environment) references to the value + # present when this script runs; CodeLLDB envFile has no variable expansion. + return re.sub(r"\$penv\{(\w+)\}", lambda m: os.environ.get(m.group(1), ""), value) + + +out = repo / ".vscode" / ".env" +out.parent.mkdir(exist_ok=True) +lines = [f"{k}={resolve(v)}" for k, v in env.items()] +out.write_text("\n".join(lines) + "\n") +print(f"wrote {len(lines)} vars to {out}") +for line in lines: + print(" " + line.split("=", 1)[0]) diff --git a/scripts/run-with-env.sh b/scripts/run-with-env.sh new file mode 100755 index 000000000..3d951b835 --- /dev/null +++ b/scripts/run-with-env.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Load the Conan runtime env from .vscode/.env (see gen-vscode-env.py) and exec +# the given command with it. Used by VS Code tasks so the test binary and CLI +# tools can find pdf2htmlEX / poppler / fontconfig / wvWare / libmagic data. +# +# Usage: scripts/run-with-env.sh [args...] +set -euo pipefail + +repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +env_file="$repo/.vscode/.env" + +if [[ -f "$env_file" ]]; then + set -a + # shellcheck disable=SC1090 + source "$env_file" + set +a +else + echo "warning: $env_file missing — run scripts/gen-vscode-env.py" >&2 +fi + +exec "$@"