Skip to content

fix(gazelle): preserve existing Python targets - #4

Draft
amartani wants to merge 18 commits into
mainfrom
martani/preserve-existing-targets-2
Draft

amartani wants to merge 18 commits into
mainfrom
martani/preserve-existing-targets-2

Conversation

@amartani

@amartani amartani commented Sep 11, 2026 •

Copy link
Copy Markdown

Gazelle now regenerates eligible hand-written py_library and py_test targets in place while retaining custom target structure and non-managed attributes. This supports package-mode split layouts with aggregate or explicit source ownership, as well as file-mode packages with dedicated __init__.py and sibling module targets. It avoids duplicate source ownership, preserves visibility and excluded-only targets, prunes stale sources and dependencies, and prevents duplicate generated binaries.

A target name is reserved only when Gazelle actually emits a target with it, so a test-only package no longer reserves the package library name. Previously a hand-written test carrying that name was skipped for preservation and its sources were duplicated into a generated test target. Package-named libraries whose live sources are all hidden by directory exclusions are also left intact rather than mistaken for empty generated targets.

Source-less package-named umbrella targets are removed as stale by default, allowing dependencies to resolve directly to concrete source owners. Intentional source-less re-exports can opt out with # keep. File-mode package targets no longer absorb sibling modules during regeneration, and visibility directives are normalized so trailing commas and whitespace cannot emit invalid empty labels.

include_pytest_conftest annotations on multi-source tests are merged deterministically. Unset annotations no longer overwrite explicit values based on concurrent parse order, and conflicting explicit values produce an error.

Documentation, unit tests, and integration fixtures protect target eligibility, split-package layouts, excluded source trees, generated-name reservation, main-module extraction, stale umbrella cleanup, file-mode package initialization, visibility normalization, and annotation behavior.

Open in Web Open in Cursor 

amartani and others added 18 commits September 10, 2026 16:44
Teach the Python generator to account for existing source-group rules so custom targets keep ownership of their sources while stale files are pruned and dependencies are resolved.
Preserving existing py_library/py_test targets introduced three defects,
each covered by a new testdata case.

Sources listed by a preserved target were pruned unless Gazelle itself
would have generated them, but that set excludes files hidden by
python_ignore_files, gazelle:exclude, and subdirectories in per-package
mode. Since srcs is mergeable, those sources were deleted from the user's
BUILD file even though they exist on disk. Pruning now keys off whether
the file exists; membership in the managed set only decides whether the
rule is adopted at all, so a rule built entirely from ignored sources
stays untouched as before.

A preserved target with a single source does not claim it, so
appendPyLibrary saw the same file twice and extracted its main module
into two identical py_binary targets, which Gazelle rejected with
"multiple rules found with label". Main modules are now extracted once
per source file.

Extracting a main module in per-file mode also removed __init__.py from
the target, on the assumption that the caller had just added it via
python_generation_mode_per_file_include_init. For a preserved target
that had listed __init__.py by hand this emptied its srcs and deleted
it. Whether __init__.py was auto-included is now passed explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ests

collectExistingPythonSourceRules never returns a rule with empty srcs, and
nothing mutates a preserved py_test's srcs between collection and use, so
the branch emitting an empty rule could not be reached. A preserved test
whose sources have all disappeared is not collected in the first place, and
getRulesWithInvalidSrcs already deletes it.

Removing the branch leaves existingPythonSourceRule.kind unread. The field
was also misleading: it held the canonical kind rather than the kind written
in the BUILD file, so under map_kind the empty rule it built would not have
matched the rule it was meant to delete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t mode

The comment claimed the __init__ target matches the generated project
package, but that target is named after the package. __init__ is preserved
like any other hand-written target; it appears unchanged because project
mode makes it claim __init__.py and regenerating it yields identical
content. The old wording implied name-matching targets are left alone,
which is the opposite of what happens: they are the ones excluded from
preservation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records the review findings for the py_library/py_test preservation feature
that are not addressed on this branch, with line references, the mechanism
that makes each one reachable, and the decisions already taken so they are
not relitigated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Preserved source targets could gain visibility, collide with generated
targets, retain stale dependencies after main-module extraction, or be
incorrectly claimed or deleted for edge-case source sets.

Exclude targets Gazelle must generate itself and sources requiring
dedicated handling. Base claiming on declared sources, preserve
visibility, and recompute dependencies after source extraction. Document
the management semantics and cover the edge cases with integration
fixtures.
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
Co-authored-by: Alex Martani <alexandre.martani@benchling.com>
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.

2 participants