This is an automated email from the ASF dual-hosted git repository. potiuk pushed a commit to branch backport-e4dbc55-v3-1-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit e2e03213ca6d4c473ed619899d7939a9181ce0de Author: Jarek Potiuk <[email protected]> AuthorDate: Sun Mar 15 12:33:17 2026 +0100 [v3-1-test] Add --open-ide flag and enhance --confirm in setup_idea.py (#63632) * Add --confirm and --open-ide flags to setup_idea.py - --confirm now auto-answers yes to all prompts (IDE close, process kill, file overwrite) instead of only the IDE close prompt - --open-ide opens IntelliJ IDEA or PyCharm in the project directory after setup completes, supporting macOS (open -a) and Linux (Toolbox scripts and PATH lookup) * Add AGENTS.md for IDE setup with --confirm and --open-ide usage * Update setup_idea docs with --confirm and --open-ide descriptions (cherry picked from commit e4dbc55b032579cde1fc43a007bf385e935cdf31) Co-authored-by: Jarek Potiuk <[email protected]> --- .../contributors_quick_start_pycharm_intellij.rst | 341 +++++++++++++++++++++ dev/ide_setup/AGENTS.md | 22 ++ dev/ide_setup/setup_idea.py | 83 ++++- 3 files changed, 434 insertions(+), 12 deletions(-) diff --git a/contributing-docs/quick-start-ide/contributors_quick_start_pycharm_intellij.rst b/contributing-docs/quick-start-ide/contributors_quick_start_pycharm_intellij.rst new file mode 100644 index 00000000000..7666ffc7d71 --- /dev/null +++ b/contributing-docs/quick-start-ide/contributors_quick_start_pycharm_intellij.rst @@ -0,0 +1,341 @@ + .. Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + .. http://www.apache.org/licenses/LICENSE-2.0 + + .. Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +.. contents:: Table of Contents + :depth: 2 + :local: + +Setup your project +################## + +1. Open your IDE or source code editor and select the option to clone the repository + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_clone.png" + alt="Cloning github fork to Pycharm"> + </div> + +2. Paste the repository link in the URL field and submit. + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_click_on_clone.png" + alt="Cloning github fork to Pycharm"> + </div> + +3. Run the ``dev/ide_setup/setup_idea.py`` script to configure the project automatically. + The script runs ``uv sync`` to create the ``.venv`` virtualenv, detects the Python SDK, and + generates the ``.idea/airflow.iml``, ``.idea/modules.xml``, and ``.idea/misc.xml`` files. + + The script supports two modes — **single-module** and **multi-module** — and + **auto-detects** which one to use based on the installed IDE: + + * If **IntelliJ IDEA** is detected → defaults to **multi-module**. + * If only **PyCharm** is detected (or no IDE is found) → defaults to **single-module**. + + You can override auto-detection with ``--multi-module`` or ``--single-module``. + + **Single-module mode** — all source roots are registered under one IntelliJ module. + This works in both PyCharm and IntelliJ IDEA: + + .. code-block:: bash + + $ uv run dev/ide_setup/setup_idea.py --single-module + + **Multi-module mode** — each distribution/package gets its own IntelliJ module with a + separate ``.iml`` file (e.g. ``airflow-core/airflow-core.iml``, + ``providers/amazon/providers-amazon.iml``). This gives better per-module SDK control and + cleaner project structure in the IDE's Project view. + + .. code-block:: bash + + $ uv run dev/ide_setup/setup_idea.py --multi-module + + .. note:: + + **Multi-module mode requires IntelliJ IDEA Ultimate** — it does **not** work in PyCharm + (Community or Professional). PyCharm does not support multiple content roots pointing to + sub-directories of the project root that each carry their own ``.iml`` module file; it + silently ignores or mishandles sub-modules. IntelliJ IDEA Ultimate with the Python plugin + handles this correctly because it has full support for the IntelliJ multi-module project + model. If you use PyCharm, stick with single-module mode. + + Then restart PyCharm/IntelliJ IDEA. + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm-airflow.iml.png" + alt="airflow.iml"> + </div> + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm-modules.xml.png" + alt="modules.xml"> + </div> + + Script options + ============== + + ``--python VERSION`` + Choose the Python minor version for the virtualenv (e.g. ``3.12``). The version is passed + to ``uv sync --python`` and must be compatible with the project's ``requires-python`` + constraint. When omitted, ``uv`` picks the default version. + + .. code-block:: bash + + $ uv run dev/ide_setup/setup_idea.py --python 3.12 + + ``--multi-module`` / ``--single-module`` + Control whether the project is configured as a single IntelliJ module (all source roots in + one ``.iml`` file) or as multiple modules (one ``.iml`` per distribution/package, e.g. + ``airflow-core/airflow-core.iml``, ``providers/amazon/providers-amazon.iml``). + + **By default the script auto-detects which IDE is installed** and picks the appropriate + mode: multi-module when IntelliJ IDEA is found, single-module when only PyCharm is found + (or when no IDE can be detected). Use ``--multi-module`` or ``--single-module`` to + override the auto-detected default. + + In multi-module mode the script also creates a dedicated ``dev/breeze`` virtualenv + (via a second ``uv sync``) with its own Python SDK named *Python X.Y (breeze)*. + All other sub-modules inherit the project-level SDK. + + .. code-block:: bash + + # Force multi-module (requires IntelliJ IDEA) + $ uv run dev/ide_setup/setup_idea.py --multi-module + + # Force single-module (works in both PyCharm and IntelliJ IDEA) + $ uv run dev/ide_setup/setup_idea.py --single-module + + ``--confirm`` + Automatically answer yes to all interactive confirmation prompts (IDE close, process kill, + file overwrite). Useful for non-interactive, scripted, or agent-driven runs. + + .. code-block:: bash + + $ uv run dev/ide_setup/setup_idea.py --confirm + + ``--open-ide`` + Open IntelliJ IDEA or PyCharm in the project directory after setup completes. On macOS + uses ``open -a``, on Linux looks for JetBrains Toolbox launcher scripts and falls back to + commands on ``PATH``. Prefers IntelliJ IDEA when both IDEs are installed. + + .. code-block:: bash + + $ uv run dev/ide_setup/setup_idea.py --open-ide + + # Combine with --confirm for fully non-interactive setup + open + $ uv run dev/ide_setup/setup_idea.py --confirm --open-ide + + ``--no-kill`` + Do not attempt to detect and kill running PyCharm/IntelliJ IDEA processes. By default, + the script looks for running IDE processes, asks for confirmation, sends ``SIGTERM``, and + falls back to ``SIGKILL`` if they don't exit within 5 seconds. Use ``--no-kill`` to + disable this behaviour and fall back to the manual confirmation prompt instead. + + .. code-block:: bash + + $ uv run dev/ide_setup/setup_idea.py --no-kill + + ``--idea-path PATH`` + Path to the JetBrains configuration directory to update instead of auto-detecting all + installed IDEs. Can point to the base JetBrains directory + (e.g. ``~/Library/Application Support/JetBrains``) or a specific product directory + (e.g. ``.../JetBrains/IntelliJIdea2025.1``). Useful when auto-detection does not find + your IDE or when you want to target a specific installation. + + .. code-block:: bash + + $ uv run dev/ide_setup/setup_idea.py --idea-path ~/Library/Application\ Support/JetBrains/IntelliJIdea2025.1 + + ``--exclude MODULE_OR_GROUP`` + Exclude modules from the generated project configuration. Can be specified multiple times. + Useful when you only work on a subset of the codebase and want faster IDE indexing. + + A value can be either a module path relative to the project root (e.g. ``providers/amazon``, + ``dev/breeze``) or one of the recognised group names: + + * ``providers`` — all provider modules under ``providers/`` + * ``shared`` — all shared libraries under ``shared/`` + * ``dev`` — the ``dev`` module + * ``tests`` — test-only modules (``docker-tests``, ``kubernetes-tests``, etc.) + + Examples: + + .. code-block:: bash + + # Exclude all providers and shared libraries + $ uv run dev/ide_setup/setup_idea.py --exclude providers --exclude shared + + # Exclude a single provider + $ uv run dev/ide_setup/setup_idea.py --exclude providers/amazon + + # Multi-module with only core modules + $ uv run dev/ide_setup/setup_idea.py --multi-module --exclude providers --exclude shared + + Options can be combined freely. For instance, to create a multi-module project with + Python 3.12 excluding all providers: + + .. code-block:: bash + + $ uv run dev/ide_setup/setup_idea.py --multi-module --python 3.12 --exclude providers + + What the script generates + ========================= + + * ``.idea/airflow.iml`` — root module definition with source roots (single-module mode) or + exclude-only root module (multi-module mode). + * ``.idea/modules.xml`` — module registry listing all IntelliJ modules. + * ``.idea/misc.xml`` — project-level Python SDK reference (derived from ``.venv``). + * ``.idea/.name`` — sets the PyCharm project name to ``airflow-<dirname>`` so the + auto-detected SDK name matches the configuration. + * ``<module>/<module>.iml`` — per-module files (multi-module mode only). + + The script also registers the Python SDKs (root project and Breeze) in the global + JetBrains ``jdk.table.xml`` configuration using the ``uv (<name>)`` naming convention + that matches PyCharm's auto-detected uv interpreters. This means the SDKs are + immediately available when you open the project — no manual interpreter setup needed. + + The script also configures project-wide exclusion patterns (``__pycache__``, + ``node_modules``, ``*.egg-info``, cache directories, etc.) so that IntelliJ does not + index or search generated/build artifacts. + +4. Alternatively, you can configure your project manually. Configure the source root directories + for ``airflow-core``, ``task-sdk``, ``airflow-ctl`` and ``devel-common``. You also have to set + "source" and "tests" root directories for each provider you want to develop (!). + + In Airflow 3.0 we split ``airflow-core``, ``task-sdk``, ``airflow-ctl``, ``devel-common``, + and each provider to be separate distribution — each with separate ``pyproject.toml`` file, + so you need to separately add ``src`` and ``tests`` directories for each provider you develop + to be respectively "source roots" and "test roots". + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_add_provider_sources_and_tests.png" + alt="Adding Source Root directories to Pycharm"> + </div> + + You also need to add ``task-sdk`` sources (and ``devel-common`` in similar way). + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_add_task_sdk_sources.png" + alt="Adding Source Root directories to Pycharm"> + </div> + +5. If you configured the project manually (step 4), configure the Python interpreter to use + the virtualenv created by ``uv sync``: go to ``File → Settings → Project → Python Interpreter``, + click the gear icon, choose *Add Interpreter → Existing*, and point to ``.venv/bin/python``. + If you used the setup script (step 3), the SDK is already registered globally — just + restart the IDE and the interpreter will be available automatically. + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_add_interpreter.png" + alt="Configuring Python Interpreter"> + </div> + +6. It is recommended to invalidate caches and restart PyCharm after setting up the project. + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_invalidate_caches.png" + alt="Invalidate caches and restart Pycharm"> + </div> + + + +Setting up debugging +#################### + +It requires "airflow-env" virtual environment configured locally. + +1. Configuring Airflow database connection + +- Airflow is by default configured to use SQLite database. Configuration can be seen on local machine + ``~/airflow/airflow.cfg`` under ``sql_alchemy_conn``. + +- Installing required dependency for MySQL connection in ``airflow-env`` on local machine. + + .. code-block:: bash + + $ pyenv activate airflow-env + $ pip install PyMySQL + +- Now set ``sql_alchemy_conn = mysql+pymysql://root:@127.0.0.1:23306/airflow?charset=utf8mb4`` in file + ``~/airflow/airflow.cfg`` on local machine. + +2. Debugging an example Dag + +- Add Interpreter to PyCharm pointing interpreter path to ``~/.pyenv/versions/airflow-env/bin/python``, which is virtual + environment ``airflow-env`` created with pyenv earlier. For adding an Interpreter go to ``File -> Setting -> Project: + airflow -> Python Interpreter``. + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_add_interpreter.png" + alt="Adding existing interpreter"> + </div> + +- In PyCharm IDE open Airflow project, directory ``/files/dags`` of local machine is by default mounted to docker + machine when breeze Airflow is started. So any Dag file present in this directory will be picked automatically by + scheduler running in docker machine and same can be seen on ``http://127.0.0.1:28080``. + +- Copy any example Dag present in the ``/airflow/example_dags`` directory to ``/files/dags/``. + +- Add a ``__main__`` block at the end of your Dag file to make it runnable: + + .. code-block:: python + + if __name__ == "__main__": + dag.test() + +- Run the file. + +Creating a branch +################# + +1. Click on the branch symbol in the status bar + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_creating_branch_1.png" + alt="Creating a new branch"> + </div> + +2. Give a name to a branch and checkout + + .. raw:: html + + <div align="center" style="padding-bottom:10px"> + <img src="images/pycharm_creating_branch_2.png" + alt="Giving a name to a branch"> + </div> + +Follow the `Quick start <../03b_contributors_quick_start_seasoned_developers.rst>`_ for typical development tasks. diff --git a/dev/ide_setup/AGENTS.md b/dev/ide_setup/AGENTS.md new file mode 100644 index 00000000000..ebb878d86a1 --- /dev/null +++ b/dev/ide_setup/AGENTS.md @@ -0,0 +1,22 @@ + <!-- SPDX-License-Identifier: Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0 --> + +<!-- START doctoc generated TOC please keep comment here to allow auto update --> +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [IDE Setup](#ide-setup) + +<!-- END doctoc generated TOC please keep comment here to allow auto update --> + +# IDE Setup + +When the user wants to open the project in IntelliJ IDEA or PyCharm (e.g. to review +a PR, inspect code, or work on changes), run: + +```bash +uv run dev/ide_setup/setup_idea.py --confirm --open-ide +``` + +This regenerates the IDE configuration files without prompts (`--confirm`) and +opens the IDE in the project directory automatically (`--open-ide`). diff --git a/dev/ide_setup/setup_idea.py b/dev/ide_setup/setup_idea.py index a19cbfb2bb6..c1f5cabb58b 100755 --- a/dev/ide_setup/setup_idea.py +++ b/dev/ide_setup/setup_idea.py @@ -28,6 +28,7 @@ import argparse import os import platform import re +import shutil import signal import subprocess import sys @@ -461,7 +462,7 @@ def _find_jetbrains_pids() -> list[tuple[int, str]]: return [] -def _kill_jetbrains_ides() -> bool: +def _kill_jetbrains_ides(*, confirm: bool = False) -> bool: """Attempt to gracefully terminate running JetBrains IDE processes. Sends SIGTERM first and waits briefly, then SIGKILL if processes remain. @@ -473,9 +474,10 @@ def _kill_jetbrains_ides() -> bool: print("[yellow]Detected running JetBrains IDE process(es):[/]") for pid, comm in pids: print(f" PID {pid}: {comm}") - should_kill = Confirm.ask("\nKill these processes to proceed?") - if not should_kill: - return True + if not confirm: + should_kill = Confirm.ask("\nKill these processes to proceed?") + if not should_kill: + return True for pid, _comm in pids: try: os.kill(pid, signal.SIGTERM) @@ -499,6 +501,53 @@ def _kill_jetbrains_ides() -> bool: return True +def _open_ide(*, project_dir: Path, idea_path: Path | None = None) -> None: + """Open IntelliJ IDEA or PyCharm in *project_dir*. + + On macOS uses ``open -a``, on Linux uses the IDE's launcher script. + Prefers IntelliJ IDEA over PyCharm when both are installed. + """ + system = platform.system() + has_intellij, has_pycharm = _detect_installed_ides(idea_path) + if system == "Darwin": + # Try to find the .app bundle via common Toolbox / standalone paths. + app_name = None + if has_intellij: + app_name = "IntelliJ IDEA" + elif has_pycharm: + app_name = "PyCharm" + if app_name: + print(f"[cyan]Opening {app_name}...[/]") + subprocess.Popen(["open", "-a", app_name, str(project_dir)]) + return + elif system == "Linux": + # JetBrains Toolbox symlinks launchers into ~/.local/share/JetBrains/Toolbox/scripts/ + toolbox_scripts = Path.home() / ".local" / "share" / "JetBrains" / "Toolbox" / "scripts" + for cmd_name, is_match in [("idea", has_intellij), ("pycharm", has_pycharm)]: + if not is_match: + continue + script = toolbox_scripts / cmd_name + if script.exists(): + label = "IntelliJ IDEA" if cmd_name == "idea" else "PyCharm" + print(f"[cyan]Opening {label}...[/]") + subprocess.Popen([str(script), str(project_dir)]) + return + # Fall back to shell commands on PATH. + for cmd_name, is_match in [("idea", has_intellij), ("pycharm", has_pycharm)]: + if not is_match: + continue + cmd_path = shutil.which(cmd_name) + if cmd_path: + label = "IntelliJ IDEA" if cmd_name == "idea" else "PyCharm" + print(f"[cyan]Opening {label}...[/]") + subprocess.Popen([cmd_path, str(project_dir)]) + return + print( + "[yellow]Could not find IntelliJ IDEA or PyCharm to open automatically.[/]\n" + "[yellow]Please open the IDE manually in:[/] " + str(project_dir) + ) + + def _find_jetbrains_config_base() -> Path | None: """Return the base JetBrains configuration directory for the current platform.""" system = platform.system() @@ -974,8 +1023,14 @@ def _build_parser() -> argparse.ArgumentParser: parser.add_argument( "--confirm", action="store_true", - help="Skip the confirmation prompt asking whether PyCharm/IntelliJ IDEA " - "has been closed. Useful for non-interactive or scripted runs.", + help="Automatically answer yes to all confirmation prompts (IDE close, " + "process kill, file overwrite). Useful for non-interactive or scripted runs.", + ) + parser.add_argument( + "--open-ide", + action="store_true", + help="Open IntelliJ IDEA or PyCharm in the project directory after " + "setup completes. Uses the detected IDE installation.", ) parser.add_argument( "--no-kill", @@ -1088,7 +1143,7 @@ def main(): if not args.no_kill: pids = _find_jetbrains_pids() if pids: - _kill_jetbrains_ides() + _kill_jetbrains_ides(confirm=args.confirm) else: print("[green]No running IntelliJ IDEA / PyCharm processes detected — safe to proceed.[/]\n") elif not args.confirm: @@ -1141,10 +1196,11 @@ def main(): print(f" [dim]·[/] {len(previous_iml_files)} sub-module .iml file(s)") print() - should_continue = Confirm.ask("Overwrite the files?") - if not should_continue: - print("[yellow]Skipped\n") - return + if not args.confirm: + should_continue = Confirm.ask("Overwrite the files?") + if not should_continue: + print("[yellow]Skipped\n") + return print() cleanup_previous_setup() @@ -1159,7 +1215,10 @@ def main(): register_sdk(breeze_sdk_name, BREEZE_PATH, BREEZE_PATH, idea_path=idea_path) print("\n[green]Success[/]\n") - print("[yellow]Important:[/] Restart PyCharm/IntelliJ IDEA to pick up the new configuration.\n") + if args.open_ide: + _open_ide(project_dir=ROOT_AIRFLOW_FOLDER_PATH, idea_path=idea_path) + else: + print("[yellow]Important:[/] Restart PyCharm/IntelliJ IDEA to pick up the new configuration.\n") if __name__ == "__main__":
