From b6a8b04d155d1c74fde34d920cb7b632c5949a69 Mon Sep 17 00:00:00 2001 From: Alejandro Perez Gancedo Date: Thu, 27 Feb 2025 10:35:55 +0000 Subject: [PATCH 1/5] docs: added docs on debugging using breakpoints --- guide/src/debugging.md | 225 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/guide/src/debugging.md b/guide/src/debugging.md index 00c22631c3b..34f208914d7 100644 --- a/guide/src/debugging.md +++ b/guide/src/debugging.md @@ -45,3 +45,228 @@ The best start to investigate a crash such as an segmentation fault is a backtra ```console rust-gdb --args python -c "import my_package; my_package.sum_to_string(1, 2)" ``` + +## Setting breakpoints in your Rust code + +One of the preferred ways by developers to debug their code is by setting breakpoints. This can be achieved in PyO3 by using a debugger like `rust-gdb` or `lldb` with your Python interpreter. +### Using rust-gdb + +1. Compile your extension with debug symbols: + ```bash + RUSTFLAGS="-g" maturin develop + ``` + +2. Launch rust-gdb with the Python interpreter: + ```bash + rust-gdb --args python + ``` + +3. Once in gdb, set a breakpoint in your Rust code: + ``` + (gdb) break your_module.rs:42 + ``` + +4. Run your Python script that imports and uses your Rust extension: + ``` + (gdb) run -c "import your_module; your_module.your_function()" + ``` + +5. When the breakpoint is hit, you can: + - Print variables: `print variable_name` + - Step through code: `next` (or `n`) for stepping over, `step` (or `s`) for stepping into + - Continue execution: `continue` (or `c`) + - Show the current stack frame: `frame` + - Show the backtrace: `bt` + + +### Using rust-lldb (for macOS users) + +On macOS, LLDB is the preferred debugger: + +1. Compile with debug symbols: + ```bash + RUSTFLAGS="-g" maturin develop + ``` + +2. Start rust-lldb with Python: + ```bash + rust-lldb -- python + ``` + +3. Set breakpoints in your Rust code: + ``` + (lldb) breakpoint set --file your_module.rs --line 42 + ``` + +4. Run your Python script: + ``` + (lldb) run -c "import your_module; your_module.your_function()" + ``` + +### Using VS Code + +VS Code with the Rust and Python extensions provides an integrated debugging experience: + +1. Create a `.vscode/launch.json` file with a configuration that uses the LLDB Debug Launcher: + + ```json + { + "version": "0.2.0", + "configurations": [ + { + "name": "Debug PyO3", + "type": "lldb", + "request": "attach", + "program": "${workspaceFolder}/.venv/bin/python", + "pid": "${command:pickProcess}", + "sourceLanguages": [ + "rust" + ] + }, + { + "name": "Launch Python with PyO3", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/.venv/bin/python", + "args": ["${file}"], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + } + ] + } + ``` + + This configuration supports both attaching to a running Python process (useful for Jupyter notebooks) and launching a Python script with debugging enabled. + + For Jupyter Notebooks run from VS Code, you'll need to attach to the Jupyter kernel process. Here's an improved version of the helper functions to automate the launch configuration: + + ```python + from pathlib import Path + import os + import subprocess + import json + import sys + + + def update_launch_json(vscode_config_file_path=None): + """Update VSCode launch.json with the correct Jupyter kernel PID. + + Args: + vscode_config_file_path (str, optional): Path to the .vscode/launch.json file. + If not provided, will use the current working directory. + """ + pid = get_jupyter_kernel_pid() + if not pid: + print("Could not determine Jupyter kernel PID.") + return + + # Determine launch.json path + if vscode_config_file_path: + launch_json_path = vscode_config_file_path + else: + launch_json_path = os.path.join(Path(os.getcwd()), ".vscode", "launch.json") + + # Get Python interpreter path + python_path = sys.executable + + # Default debugger config + debug_config = { + "version": "0.2.0", + "configurations": [ + { + "name": "Debug PyO3 (Jupyter)", + "type": "lldb", + "request": "attach", + "program": python_path, + "pid": pid, + "sourceLanguages": ["rust"], + }, + { + "name": "Launch Python with PyO3", + "type": "lldb", + "request": "launch", + "program": python_path, + "args": ["${file}"], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + } + ], + } + + # Create .vscode directory if it doesn't exist + try: + os.makedirs(os.path.dirname(launch_json_path), exist_ok=True) + + # If launch.json already exists, try to update it instead of overwriting + if os.path.exists(launch_json_path): + try: + with open(launch_json_path, "r") as f: + existing_config = json.load(f) + + # Check if our configuration already exists + config_exists = False + for config in existing_config.get("configurations", []): + if config.get("name") == "Debug PyO3 (Jupyter)": + config["pid"] = pid + config["program"] = python_path + config_exists = True + + if not config_exists: + existing_config.setdefault("configurations", []).append(debug_config["configurations"][0]) + + debug_config = existing_config + except Exception: + # If reading fails, we'll just overwrite with our new configuration + pass + + with open(launch_json_path, "w") as f: + json.dump(debug_config, f, indent=4) + print(f"Updated launch.json with PID: {pid} at {launch_json_path}") + except Exception as e: + print(f"Error updating launch.json: {e}") + + + def get_jupyter_kernel_pid(): + """Find the process ID (PID) of the running Jupyter kernel. + + Returns: + int: The process ID of the Jupyter kernel, or None if not found. + """ + # Check if we're running in a Jupyter environment + if 'ipykernel' in sys.modules: + pid = os.getpid() + print(f"Jupyter kernel PID: {pid}") + return pid + else: + print("Not running in a Jupyter environment.") + return None + ``` + +2. Set breakpoints in your Rust code by clicking in the gutter next to line numbers. + +3. Start debugging: + - For attaching to a running Jupyter notebook: First run a cell to ensure the kernel is active, then select the "Debug PyO3 (Jupyter)" configuration and click Start Debugging. + - For launching a Python script: Open your Python script, select the "Launch Python with PyO3" configuration and click Start Debugging. + +4. When debugging PyO3 code: + - You can inspect Rust variables and data structures + - Use the debug console to evaluate expressions + - Step through Rust code line by line using the step controls + - Set conditional breakpoints for more complex debugging scenarios + +For advanced debugging scenarios, you might also want to add environment variables or enable specific Rust debug flags: + +```json +{ + "name": "Debug PyO3 with Environment", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/.venv/bin/python", + "args": ["${file}"], + "env": { + "RUST_BACKTRACE": "1", + "PYTHONPATH": "${workspaceFolder}" + }, + "sourceLanguages": ["rust"] +} +``` \ No newline at end of file From db5694c0fcb58f8224a607ea395d40ccd8d13eb4 Mon Sep 17 00:00:00 2001 From: Alejandro Perez Gancedo Date: Thu, 27 Feb 2025 11:27:16 +0000 Subject: [PATCH 2/5] docs: added missing code type md --- guide/src/debugging.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guide/src/debugging.md b/guide/src/debugging.md index 34f208914d7..d99c2f603ce 100644 --- a/guide/src/debugging.md +++ b/guide/src/debugging.md @@ -62,12 +62,12 @@ One of the preferred ways by developers to debug their code is by setting breakp ``` 3. Once in gdb, set a breakpoint in your Rust code: - ``` + ```bash (gdb) break your_module.rs:42 ``` 4. Run your Python script that imports and uses your Rust extension: - ``` + ```bash (gdb) run -c "import your_module; your_module.your_function()" ``` @@ -94,12 +94,12 @@ On macOS, LLDB is the preferred debugger: ``` 3. Set breakpoints in your Rust code: - ``` + ```bash (lldb) breakpoint set --file your_module.rs --line 42 ``` 4. Run your Python script: - ``` + ```bash (lldb) run -c "import your_module; your_module.your_function()" ``` From 82be715db895619ede803e17c24da1a0d011bf63 Mon Sep 17 00:00:00 2001 From: Alejandro Perez Gancedo <37455131+LifeLex@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:13:36 +0000 Subject: [PATCH 3/5] Update guide/src/debugging.md Co-authored-by: Nathan Goldbaum --- guide/src/debugging.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guide/src/debugging.md b/guide/src/debugging.md index d99c2f603ce..9e31e1ab054 100644 --- a/guide/src/debugging.md +++ b/guide/src/debugging.md @@ -48,7 +48,9 @@ The best start to investigate a crash such as an segmentation fault is a backtra ## Setting breakpoints in your Rust code -One of the preferred ways by developers to debug their code is by setting breakpoints. This can be achieved in PyO3 by using a debugger like `rust-gdb` or `lldb` with your Python interpreter. +One of the preferred ways by developers to debug their code is by setting breakpoints. This can be achieved in PyO3 by using a debugger like `rust-gdb` or `rust-lldb` with your Python interpreter. + +For more information about how to use both `lldb` and `gdb` you can read the [gdb to lldb command map](https://lldb.llvm.org/use/map.html) from the lldb documentation. ### Using rust-gdb 1. Compile your extension with debug symbols: From 166ecda1399a023c6f11c69f23fc93ac28ffccb4 Mon Sep 17 00:00:00 2001 From: Alejandro Perez Gancedo Date: Thu, 27 Feb 2025 22:54:54 +0000 Subject: [PATCH 4/5] docs: refactor debugging section to address comments --- guide/src/debugging.md | 335 ++++++++++++++++++++++++----------------- 1 file changed, 195 insertions(+), 140 deletions(-) diff --git a/guide/src/debugging.md b/guide/src/debugging.md index 9e31e1ab054..06615994e4f 100644 --- a/guide/src/debugging.md +++ b/guide/src/debugging.md @@ -34,11 +34,11 @@ Run Valgrind with `valgrind --suppressions=valgrind-python.supp ./my-command --w The best start to investigate a crash such as an segmentation fault is a backtrace. You can set `RUST_BACKTRACE=1` as an environment variable to get the stack trace on a `panic!`. Alternatively you can use a debugger such as `gdb` to explore the issue. Rust provides a wrapper, `rust-gdb`, which has pretty-printers for inspecting Rust variables. Since PyO3 uses `cdylib` for Python shared objects, it does not receive the pretty-print debug hooks in `rust-gdb` ([rust-lang/rust#96365](https://github.com/rust-lang/rust/issues/96365)). The mentioned issue contains a workaround for enabling pretty-printers in this case. - * Link against a debug build of python as described in the previous chapter - * Run `rust-gdb ` - * Set a breakpoint (`b`) on `rust_panic` if you are investigating a `panic!` - * Enter `r` to run - * After the crash occurred, enter `bt` or `bt full` to print the stacktrace +* Link against a debug build of python as described in the previous chapter +* Run `rust-gdb ` +* Set a breakpoint (`b`) on `rust_panic` if you are investigating a `panic!` +* Enter `r` to run +* After the crash occurred, enter `bt` or `bt full` to print the stacktrace Often it is helpful to run a small piece of Python code to exercise a section of Rust. @@ -51,65 +51,87 @@ The best start to investigate a crash such as an segmentation fault is a backtra One of the preferred ways by developers to debug their code is by setting breakpoints. This can be achieved in PyO3 by using a debugger like `rust-gdb` or `rust-lldb` with your Python interpreter. For more information about how to use both `lldb` and `gdb` you can read the [gdb to lldb command map](https://lldb.llvm.org/use/map.html) from the lldb documentation. -### Using rust-gdb + +### Common setup 1. Compile your extension with debug symbols: + ```bash + # Debug is the default for maturin, but you can explicitly ensure debug symbols with: RUSTFLAGS="-g" maturin develop + + # For setuptools-rust users: + pip install -e . ``` -2. Launch rust-gdb with the Python interpreter: + > **Note**: When using debuggers, make sure that `python` resolves to an actual Python binary or symlink and not a shim script. Some tools like pyenv use shim scripts which can interfere with debugging. + +### Using rust-gdb + +1. Launch rust-gdb with the Python interpreter: + ```bash rust-gdb --args python ``` -3. Once in gdb, set a breakpoint in your Rust code: +2. Once in gdb, set a breakpoint in your Rust code: + ```bash (gdb) break your_module.rs:42 ``` -4. Run your Python script that imports and uses your Rust extension: +3. Run your Python script that imports and uses your Rust extension: + ```bash + # Option 1: Run an inline Python command (gdb) run -c "import your_module; your_module.your_function()" + + # Option 2: Run a Python script + (gdb) run your_script.py + + # Option 3: Run pytest tests + (gdb) run -m pytest tests/test_something.py::TestName ``` -5. When the breakpoint is hit, you can: - - Print variables: `print variable_name` - - Step through code: `next` (or `n`) for stepping over, `step` (or `s`) for stepping into - - Continue execution: `continue` (or `c`) - - Show the current stack frame: `frame` - - Show the backtrace: `bt` - - ### Using rust-lldb (for macOS users) On macOS, LLDB is the preferred debugger: -1. Compile with debug symbols: - ```bash - RUSTFLAGS="-g" maturin develop - ``` +1. Start rust-lldb with Python: -2. Start rust-lldb with Python: ```bash rust-lldb -- python ``` -3. Set breakpoints in your Rust code: +2. Set breakpoints in your Rust code: + ```bash (lldb) breakpoint set --file your_module.rs --line 42 ``` -4. Run your Python script: +3. Run your Python script: + ```bash + # Option 1: Run an inline Python command (lldb) run -c "import your_module; your_module.your_function()" + + # Option 2: Run a Python script + (lldb) run your_script.py + + # Option 3: Run pytest tests + (lldb) run -m pytest tests/test_something.py::TestName ``` ### Using VS Code VS Code with the Rust and Python extensions provides an integrated debugging experience: -1. Create a `.vscode/launch.json` file with a configuration that uses the LLDB Debug Launcher: +1. First, install the necessary VS Code extensions: + * [Rust Analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) + * [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) + * [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) + +2. Create a `.vscode/launch.json` file with a configuration that uses the LLDB Debug Launcher: ```json { @@ -133,130 +155,52 @@ VS Code with the Rust and Python extensions provides an integrated debugging exp "args": ["${file}"], "cwd": "${workspaceFolder}", "sourceLanguages": ["rust"] + }, + { + "name": "Debug PyO3 with Args", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/.venv/bin/python", + "args": ["path/to/your/script.py", "arg1", "arg2"], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "name": "Debug PyO3 Tests", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/.venv/bin/python", + "args": ["-m", "pytest", "tests/your_test.py::test_function", "-v"], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] } ] } ``` - This configuration supports both attaching to a running Python process (useful for Jupyter notebooks) and launching a Python script with debugging enabled. + This configuration supports multiple debugging scenarios: + * Attaching to a running Python process + * Launching the currently open Python file + * Running a specific script with command-line arguments + * Running pytest tests - For Jupyter Notebooks run from VS Code, you'll need to attach to the Jupyter kernel process. Here's an improved version of the helper functions to automate the launch configuration: +3. Set breakpoints in your Rust code by clicking in the gutter next to line numbers. - ```python - from pathlib import Path - import os - import subprocess - import json - import sys +4. Start debugging: + * For attaching to a running Python process: First start the process, then select the "Debug PyO3" configuration and click Start Debugging (F5). You'll be prompted to select the Python process to attach to. + * For launching a Python script: Open your Python script, select the "Launch Python with PyO3" configuration and click Start Debugging (F5). + * For running with arguments: Select "Debug PyO3 with Args" (remember to edit the configuration with your actual script path and arguments). + * For running tests: Select "Debug PyO3 Tests" (edit the test path as needed). +5. When debugging PyO3 code: + * You can inspect Rust variables and data structures + * Use the debug console to evaluate expressions + * Step through Rust code line by line using the step controls + * Set conditional breakpoints for more complex debugging scenarios - def update_launch_json(vscode_config_file_path=None): - """Update VSCode launch.json with the correct Jupyter kernel PID. - - Args: - vscode_config_file_path (str, optional): Path to the .vscode/launch.json file. - If not provided, will use the current working directory. - """ - pid = get_jupyter_kernel_pid() - if not pid: - print("Could not determine Jupyter kernel PID.") - return - - # Determine launch.json path - if vscode_config_file_path: - launch_json_path = vscode_config_file_path - else: - launch_json_path = os.path.join(Path(os.getcwd()), ".vscode", "launch.json") - - # Get Python interpreter path - python_path = sys.executable - - # Default debugger config - debug_config = { - "version": "0.2.0", - "configurations": [ - { - "name": "Debug PyO3 (Jupyter)", - "type": "lldb", - "request": "attach", - "program": python_path, - "pid": pid, - "sourceLanguages": ["rust"], - }, - { - "name": "Launch Python with PyO3", - "type": "lldb", - "request": "launch", - "program": python_path, - "args": ["${file}"], - "cwd": "${workspaceFolder}", - "sourceLanguages": ["rust"] - } - ], - } - - # Create .vscode directory if it doesn't exist - try: - os.makedirs(os.path.dirname(launch_json_path), exist_ok=True) - - # If launch.json already exists, try to update it instead of overwriting - if os.path.exists(launch_json_path): - try: - with open(launch_json_path, "r") as f: - existing_config = json.load(f) - - # Check if our configuration already exists - config_exists = False - for config in existing_config.get("configurations", []): - if config.get("name") == "Debug PyO3 (Jupyter)": - config["pid"] = pid - config["program"] = python_path - config_exists = True - - if not config_exists: - existing_config.setdefault("configurations", []).append(debug_config["configurations"][0]) - - debug_config = existing_config - except Exception: - # If reading fails, we'll just overwrite with our new configuration - pass - - with open(launch_json_path, "w") as f: - json.dump(debug_config, f, indent=4) - print(f"Updated launch.json with PID: {pid} at {launch_json_path}") - except Exception as e: - print(f"Error updating launch.json: {e}") - - - def get_jupyter_kernel_pid(): - """Find the process ID (PID) of the running Jupyter kernel. - - Returns: - int: The process ID of the Jupyter kernel, or None if not found. - """ - # Check if we're running in a Jupyter environment - if 'ipykernel' in sys.modules: - pid = os.getpid() - print(f"Jupyter kernel PID: {pid}") - return pid - else: - print("Not running in a Jupyter environment.") - return None - ``` - -2. Set breakpoints in your Rust code by clicking in the gutter next to line numbers. - -3. Start debugging: - - For attaching to a running Jupyter notebook: First run a cell to ensure the kernel is active, then select the "Debug PyO3 (Jupyter)" configuration and click Start Debugging. - - For launching a Python script: Open your Python script, select the "Launch Python with PyO3" configuration and click Start Debugging. +### Advanced Debugging Configurations -4. When debugging PyO3 code: - - You can inspect Rust variables and data structures - - Use the debug console to evaluate expressions - - Step through Rust code line by line using the step controls - - Set conditional breakpoints for more complex debugging scenarios - -For advanced debugging scenarios, you might also want to add environment variables or enable specific Rust debug flags: +For advanced debugging scenarios, you might want to add environment variables or enable specific Rust debug flags: ```json { @@ -271,4 +215,115 @@ For advanced debugging scenarios, you might also want to add environment variabl }, "sourceLanguages": ["rust"] } -``` \ No newline at end of file +``` + +### Debugging from Jupyter Notebooks + +For Jupyter Notebooks run from VS Code, you can use the following helper functions to automate the launch configuration: + +```python +from pathlib import Path +import os +import json +import sys + + +def update_launch_json(vscode_config_file_path=None): + """Update VSCode launch.json with the correct Jupyter kernel PID. + + Args: + vscode_config_file_path (str, optional): Path to the .vscode/launch.json file. + If not provided, will use the current working directory. + """ + pid = get_jupyter_kernel_pid() + if not pid: + print("Could not determine Jupyter kernel PID.") + return + + # Determine launch.json path + if vscode_config_file_path: + launch_json_path = vscode_config_file_path + else: + launch_json_path = os.path.join(Path(os.getcwd()), ".vscode", "launch.json") + + # Get Python interpreter path + python_path = sys.executable + + # Default debugger config + debug_config = { + "version": "0.2.0", + "configurations": [ + { + "name": "Debug PyO3 (Jupyter)", + "type": "lldb", + "request": "attach", + "program": python_path, + "pid": pid, + "sourceLanguages": ["rust"], + }, + { + "name": "Launch Python with PyO3", + "type": "lldb", + "request": "launch", + "program": python_path, + "args": ["${file}"], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + } + ], + } + + # Create .vscode directory if it doesn't exist + try: + os.makedirs(os.path.dirname(launch_json_path), exist_ok=True) + + # If launch.json already exists, try to update it instead of overwriting + if os.path.exists(launch_json_path): + try: + with open(launch_json_path, "r") as f: + existing_config = json.load(f) + + # Check if our configuration already exists + config_exists = False + for config in existing_config.get("configurations", []): + if config.get("name") == "Debug PyO3 (Jupyter)": + config["pid"] = pid + config["program"] = python_path + config_exists = True + + if not config_exists: + existing_config.setdefault("configurations", []).append(debug_config["configurations"][0]) + + debug_config = existing_config + except Exception: + # If reading fails, we'll just overwrite with our new configuration + pass + + with open(launch_json_path, "w") as f: + json.dump(debug_config, f, indent=4) + print(f"Updated launch.json with PID: {pid} at {launch_json_path}") + except Exception as e: + print(f"Error updating launch.json: {e}") + + +def get_jupyter_kernel_pid(): + """Find the process ID (PID) of the running Jupyter kernel. + + Returns: + int: The process ID of the Jupyter kernel, or None if not found. + """ + # Check if we're running in a Jupyter environment + if 'ipykernel' in sys.modules: + pid = os.getpid() + print(f"Jupyter kernel PID: {pid}") + return pid + else: + print("Not running in a Jupyter environment.") + return None +``` + +To use these functions: + +1. Run the cell containing these functions in your Jupyter notebook +2. Run `update_launch_json()` in a cell +3. In VS Code, select the "Debug PyO3 (Jupyter)" configuration and start debugging From 631edbb53f1bb9e39d26dc37f0f941789a836e93 Mon Sep 17 00:00:00 2001 From: Alejandro Perez Gancedo Date: Fri, 28 Feb 2025 11:34:31 +0000 Subject: [PATCH 5/5] docs: install mdbook-tabs and update docs --- .github/workflows/gh-pages.yml | 2 +- .netlify/build.sh | 8 ++++ Contributing.md | 3 +- guide/book.toml | 4 ++ guide/src/debugging.md | 15 +++++-- guide/theme/tabs.css | 25 ++++++++++++ guide/theme/tabs.js | 75 ++++++++++++++++++++++++++++++++++ 7 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 guide/theme/tabs.css create mode 100644 guide/theme/tabs.js diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 1050b97f314..21b33c93796 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -30,7 +30,7 @@ jobs: - name: Setup mdBook uses: taiki-e/install-action@v2 with: - tool: mdbook,lychee + tool: mdbook,mdbook-tabs,lychee - name: Prepare tag id: prepare_tag diff --git a/.netlify/build.sh b/.netlify/build.sh index a61180be59a..97b7bd72414 100755 --- a/.netlify/build.sh +++ b/.netlify/build.sh @@ -89,6 +89,14 @@ if [ "${INSTALLED_MDBOOK_LINKCHECK_VERSION}" != "mdbook v${MDBOOK_LINKCHECK_VERS cargo install mdbook-linkcheck@${MDBOOK_LINKCHECK_VERSION} --force fi +# Install latest mdbook-tabs. Netlify will cache the cargo bin dir, so this will +# only build mdbook-tabs if needed. +MDBOOK_TABS_VERSION=$(cargo search mdbook-tabs --limit 1 | head -1 | tr -s ' ' | cut -d ' ' -f 3 | tr -d '"') +INSTALLED_MDBOOK_TABS_VERSION=$(mdbook-tabs --version || echo "none") +if [ "${INSTALLED_MDBOOK_TABS_VERSION}" != "mdbook-tabs v${MDBOOK_TABS_VERSION}" ]; then + cargo install mdbook-tabs@${MDBOOK_TABS_VERSION} --force +fi + pip install nox nox -s build-guide mv target/guide/ netlify_build/main/ diff --git a/Contributing.md b/Contributing.md index 76af08325fb..054ef42fb88 100644 --- a/Contributing.md +++ b/Contributing.md @@ -60,7 +60,7 @@ https://doc.rust-lang.org/rustdoc/documentation-tests.html for a guide on doctes You can preview the user guide by building it locally with `mdbook`. -First, install [`mdbook`][mdbook] and [`nox`][nox]. Then, run +First, install [`mdbook`][mdbook], the [`mdbook-tabs`][mdbook-tabs] plugin and [`nox`][nox]. Then, run ```shell nox -s build-guide -- --open @@ -235,5 +235,6 @@ In the meanwhile, some of our maintainers have personal GitHub sponsorship pages - [messense](https://github.com/sponsors/messense) [mdbook]: https://rust-lang.github.io/mdBook/cli/index.html +[mdbook-tabs]: https://mdbook-plugins.rustforweb.org/tabs.html [lychee]: https://github.com/lycheeverse/lychee [nox]: https://github.com/theacodes/nox diff --git a/guide/book.toml b/guide/book.toml index bccc3506098..be682a64eab 100644 --- a/guide/book.toml +++ b/guide/book.toml @@ -6,7 +6,11 @@ author = "PyO3 Project and Contributors" [preprocessor.pyo3_version] command = "python3 guide/pyo3_version.py" +[preprocessor.tabs] + [output.html] git-repository-url = "https://github.com/PyO3/pyo3/tree/main/guide" edit-url-template = "https://github.com/PyO3/pyo3/edit/main/guide/{path}" playground.runnable = false +additional-css = ["theme/tabs.css"] +additional-js = ["theme/tabs.js"] diff --git a/guide/src/debugging.md b/guide/src/debugging.md index 06615994e4f..02f1e9951de 100644 --- a/guide/src/debugging.md +++ b/guide/src/debugging.md @@ -66,7 +66,12 @@ For more information about how to use both `lldb` and `gdb` you can read the [gd > **Note**: When using debuggers, make sure that `python` resolves to an actual Python binary or symlink and not a shim script. Some tools like pyenv use shim scripts which can interfere with debugging. -### Using rust-gdb +### Debugger specific setup + +Depeding on your OS and your preferences you can use two different debuggers, `rust-gdb` or `rust-lldb`. + +{{#tabs }} +{{#tab name="Using rust-gdb" }} 1. Launch rust-gdb with the Python interpreter: @@ -93,9 +98,8 @@ For more information about how to use both `lldb` and `gdb` you can read the [gd (gdb) run -m pytest tests/test_something.py::TestName ``` -### Using rust-lldb (for macOS users) - -On macOS, LLDB is the preferred debugger: +{{#endtab }} +{{#tab name="Using rust-lldb (for macOS users)" }} 1. Start rust-lldb with Python: @@ -122,6 +126,9 @@ On macOS, LLDB is the preferred debugger: (lldb) run -m pytest tests/test_something.py::TestName ``` +{{#endtab }} +{{#endtabs }} + ### Using VS Code VS Code with the Rust and Python extensions provides an integrated debugging experience: diff --git a/guide/theme/tabs.css b/guide/theme/tabs.css new file mode 100644 index 00000000000..8712b859c0b --- /dev/null +++ b/guide/theme/tabs.css @@ -0,0 +1,25 @@ +.mdbook-tabs { + display: flex; +} + +.mdbook-tab { + background-color: var(--table-alternate-bg); + padding: 0.5rem 1rem; + cursor: pointer; + border: none; + font-size: 1.6rem; + line-height: 1.45em; +} + +.mdbook-tab.active { + background-color: var(--table-header-bg); + font-weight: bold; +} + +.mdbook-tab-content { + padding: 1rem 0rem; +} + +.mdbook-tab-content table { + margin: unset; +} diff --git a/guide/theme/tabs.js b/guide/theme/tabs.js new file mode 100644 index 00000000000..8ba5e878c39 --- /dev/null +++ b/guide/theme/tabs.js @@ -0,0 +1,75 @@ +/** + * Change active tab of tabs. + * + * @param {Element} container + * @param {string} name + */ +const changeTab = (container, name) => { + for (const child of container.children) { + if (!(child instanceof HTMLElement)) { + continue; + } + + if (child.classList.contains('mdbook-tabs')) { + for (const tab of child.children) { + if (!(tab instanceof HTMLElement)) { + continue; + } + + if (tab.dataset.tabname === name) { + tab.classList.add('active'); + } else { + tab.classList.remove('active'); + } + } + } else if (child.classList.contains('mdbook-tab-content')) { + if (child.dataset.tabname === name) { + child.classList.remove('hidden'); + } else { + child.classList.add('hidden'); + } + } + } +}; + +document.addEventListener('DOMContentLoaded', () => { + const tabs = document.querySelectorAll('.mdbook-tab'); + for (const tab of tabs) { + tab.addEventListener('click', () => { + if (!(tab instanceof HTMLElement)) { + return; + } + + if (!tab.parentElement || !tab.parentElement.parentElement) { + return; + } + + const container = tab.parentElement.parentElement; + const name = tab.dataset.tabname; + const global = container.dataset.tabglobal; + + changeTab(container, name); + + if (global) { + localStorage.setItem(`mdbook-tabs-${global}`, name); + + const globalContainers = document.querySelectorAll( + `.mdbook-tabs-container[data-tabglobal="${global}"]` + ); + for (const globalContainer of globalContainers) { + changeTab(globalContainer, name); + } + } + }); + } + + const containers = document.querySelectorAll('.mdbook-tabs-container[data-tabglobal]'); + for (const container of containers) { + const global = container.dataset.tabglobal; + + const name = localStorage.getItem(`mdbook-tabs-${global}`); + if (name && document.querySelector(`.mdbook-tab[data-tabname=${name}]`)) { + changeTab(container, name); + } + } +});