Skip to content

Generate launchers for gui-scripts in editable installs#10968

Open
HarperZ9 wants to merge 4 commits into
python-poetry:mainfrom
HarperZ9:fix/editable-gui-scripts-10481
Open

Generate launchers for gui-scripts in editable installs#10968
HarperZ9 wants to merge 4 commits into
python-poetry:mainfrom
HarperZ9:fix/editable-gui-scripts-10481

Conversation

@HarperZ9

Copy link
Copy Markdown

Summary

Scripts declared in [project.gui-scripts] are not made available by poetry install (editable mode). This fixes #10481 (labelled kind/bug + status/confirmed).

EditableBuilder._add_scripts() only reads the console_scripts entry-point group:

scripts = entry_points.get("console_scripts", [])

so GUI scripts are silently dropped from the venv's bin/Scripts directory.

The editable-vs-wheel asymmetry

Wheel installs are unaffected: the wheel only records entry points in entry_points.txt, and pip generates the launchers for both console_scripts and gui_scripts at install time. Editable installs, by contrast, generate the launcher scripts themselves inside EditableBuilder — and that code path never iterated gui_scripts. The result is an inconsistency: a project's GUI scripts work after pip install . / building a wheel, but are missing after poetry install.

Note that the editable dist-info's entry_points.txt already lists the [gui_scripts] group correctly, because poetry-core's convert_entry_points() returns both groups (verified against poetry-core 2.4.1):

elif group_name == "gui-scripts":
    group_name = "gui_scripts"

So the metadata is right; only the launcher generation is incomplete. (A prior attempt, #9549, was closed procedurally pending poetry-core support for the gui_scripts group — that support now exists.)

The fix

One line — also iterate the gui_scripts group:

scripts = entry_points.get("console_scripts", []) + entry_points.get("gui_scripts", [])

Editable mode writes an identical plain-Python script (plus a Windows .cmd wrapper) for every script, with no console-vs-gui distinction, so the rest of the loop (split(" = "), the ValueError hint handling, the .cmd wrapper) applies to GUI scripts unchanged.

Reproduction

A minimal project:

[project]
name = "mygui-pkg"
version = "0.1.0"

[project.scripts]
mycli = "mygui_pkg:cli"

[project.gui-scripts]
mygui = "mygui_pkg:gui"

[build-system]
requires = ["poetry-core>=2.0"]
build-backend = "poetry.core.masonry.api"

Before this change, after an editable install:

  • entry_points.txt contains both [console_scripts] and [gui_scripts] blocks ✅
  • Scripts/mycli (+ mycli.cmd on Windows) exists ✅
  • Scripts/mygui is missing

After this change, Scripts/mygui (+ mygui.cmd) is generated alongside mycli, with the same launcher content.

Test

Extends test_builder_installs_proper_files_for_standard_packages in tests/masonry/builders/test_editable_builder.py:

  • The simple_project fixture gains a [project.gui-scripts] entry (gui-foo = "foo:gui").
  • New assertions: tmp_venv._bin_dir.joinpath("gui-foo").exists(), the launcher file contents, and the RECORD entry.
  • The existing entry_points.txt assertion is updated to include the [gui_scripts] block.

The GUI-specific assertions are gated on project == "simple_project" rather than also applied to simple_project_legacy, because the legacy [tool.poetry] schema in poetry-core (poetry-schema.json) does not define a gui-scripts table — [tool.poetry.gui-scripts] is rejected as an unexpected property. GUI scripts are a PEP 621–only feature, so the legacy fixture is intentionally left unchanged.

Verified: the new assertion fails on main (no launcher generated) and passes with the fix.

$ pytest tests/masonry/builders/test_editable_builder.py -k standard_packages
2 passed

ruff check, ruff format --check, and mypy all pass on the changed files.

Coordination

I'm aware of other open PRs that touch EditableBuilder._add_scripts() / the same launcher path: #9802 (shebang spaces), #10946 (Windows UTF-8 script wrappers), and #10736 (file scripts). As of opening this PR none of them have merged, so this applies cleanly to main. If any of them merge first, I'm happy to rebase and re-apply the gui_scripts inclusion to the new launcher path — in particular #9802 hardcodes a "console" kind, and the intent here is simply that GUI scripts also get a generated launcher.

AI assistance disclosure

This change was prepared with the help of Claude (Anthropic). A human reviewed the bug, the one-line fix, and the test; the reproduction and the failing-on-main / passing-with-fix behavior were verified by actually running the editable builder and the test suite, not assumed. I'm responsible for the contents of this PR and happy to make any adjustments maintainers request.

`EditableBuilder._add_scripts()` only iterated the `console_scripts`
entry-point group, so scripts declared in `[project.gui-scripts]` were
listed in the editable dist-info's `entry_points.txt` (poetry-core's
`convert_entry_points()` emits the `gui_scripts` group) but no runnable
launcher was ever written to the venv's bin/Scripts directory. Wheel
installs were unaffected because pip generates the launchers itself.

Iterate the `gui_scripts` group as well. Editable mode writes an
identical plain-Python script (plus a Windows `.cmd` wrapper) for every
script with no console-vs-gui distinction, so the existing loop applies
to gui scripts unchanged.

Fixes python-poetry#10481

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In test_builder_installs_proper_files_for_standard_packages, the project == "simple_project" guard is repeated in multiple places; consider extracting the GUI-script-specific assertions into a separate test or helper to keep the main test simpler and reduce branching.
  • When building scripts in _add_scripts, you might want to guard against missing keys without creating intermediate lists by using something like entry_points.get("console_scripts") or [] and entry_points.get("gui_scripts") or [], which avoids potential None concatenation if the underlying data structure changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `test_builder_installs_proper_files_for_standard_packages`, the `project == "simple_project"` guard is repeated in multiple places; consider extracting the GUI-script-specific assertions into a separate test or helper to keep the main test simpler and reduce branching.
- When building `scripts` in `_add_scripts`, you might want to guard against missing keys without creating intermediate lists by using something like `entry_points.get("console_scripts") or []` and `entry_points.get("gui_scripts") or []`, which avoids potential `None` concatenation if the underlying data structure changes.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@HarperZ9

HarperZ9 commented Jul 2, 2026

Copy link
Copy Markdown
Author

Addressed the Sourcery review feedback in 0f5a7f3e0a290164ede00291342e20cf118f9990:

  • replaced the project-name entry-point helper branch with explicit parametrized expected values for modern vs legacy fixtures;
  • moved the GUI launcher assertion into a focused test_builder_installs_gui_scripts test;
  • kept the entry_points.get(... ) or [] guard that was already in this branch.

Local verification from this branch on Windows/Python 3.12:

  • uv run --isolated --with-editable . --with pytest --with pytest-mock --with pytest-randomly --with pytest-xdist --with deepdiff --with responses --with jaraco-classes --with coverage python -m pytest tests/masonry/builders/test_editable_builder.py -k "builder_installs_proper_files_for_standard_packages or builder_installs_gui_scripts" -n 0 -q -> 3 passed, 7 deselected
  • .\.venv\Scripts\poetry.exe run ruff format --check tests/masonry/builders/test_editable_builder.py src/poetry/masonry/builders/editable.py -> 2 files already formatted
  • .\.venv\Scripts\poetry.exe run ruff check tests/masonry/builders/test_editable_builder.py src/poetry/masonry/builders/editable.py -> all checks passed

Note: the repo .venv on this machine was missing test deps, so I used an isolated uv run environment for pytest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scripts defined in [project.gui-scripts] are not made available by poetry install

1 participant