Skip to content

build: fix setuptools flat-layout discovery so wheels/sdists can be built - #109

Open
radkisson wants to merge 2 commits into
Sophomoresty:mainfrom
radkisson:build/fix-setuptools-discovery
Open

radkisson wants to merge 2 commits into
Sophomoresty:mainfrom
radkisson:build/fix-setuptools-discovery

Conversation

@radkisson

Copy link
Copy Markdown

Problem

Every packaging path fails on main:

$ uv build
error: Multiple top-level packages discovered in a flat-layout: ['cloudflare', 'gemini_web2api'].

$ pip install "gemini-web2api @ git+https://github.com/Sophomoresty/gemini-web2api"
  × Failed to build `gemini-web2api @ git+...`
  ╰─▶ Call to `setuptools.build_meta.build_wheel` failed

$ uv tool install "gemini-web2api @ git+https://github.com/Sophomoresty/gemini-web2api"
 (same failure)

pyproject.toml has no [tool.setuptools] section, so setuptools uses flat-layout auto-discovery. It finds two candidate top-level directories — cloudflare/ and gemini_web2api/ — and refuses to guess. cloudflare/ is a JavaScript Cloudflare Worker with no Python in it, but the heuristic only looks for a directory, so it is counted (and wins alphabetically before the real package is even considered).

Why it went unnoticed

Nothing in the repo builds a distribution:

Path Installs a package?
README: python gemini_web2api.py no — runs a file
Dockerfile: COPY gemini_web2api/ + python -m no — copies files
CI: Docker image build only no

So only users on the pip install / uv tool install paths hit the error.

Change

[tool.setuptools.packages.find]
include = ["gemini_web2api*"]

Using packages.find with an include pattern rather than a hardcoded packages = ["gemini_web2api"] list so future subpackages are picked up automatically. The pattern also keeps tests/ (which does have an __init__.py) out of the distribution.

Verification

On a clean checkout of this branch:

  • uv build → gemini_web2api-1.1.0-py3-none-any.whl + gemini_web2api-1.1.0.tar.gz
  • wheel contents are exactly the 8 gemini_web2api/*.py files plus dist-info; tests/ and cloudflare/ are absent, top_level.txt is a single line
  • adding a throwaway subpackage (gemini_web2api/_tmpsub/) makes it appear in the wheel automatically, then removing it makes it disappear
  • pip install dist/gemini_web2api-1.1.0.tar.gz into a clean venv → gemini-web2api --version prints gemini-web2api 1.1.0
  • uv tool install --python 3.13 --with httpx . → Installed 1 executable: gemini-web2api
  • pytest tests → 18 passed
  • python gemini_web2api.py (single-file path documented in README) is untouched by this change

The second commit adds a Build workflow so this class of failure cannot recur: it builds both distributions, installs the wheel with the [streaming] extra into a clean venv, runs the console script, and asserts httpx was resolved.

Related (not fixed here, separate PRs welcome)

  1. gemini_web2api/__main__.py prints Listening: http://0.0.0.0:{port} unconditionally, ignoring CONFIG["host"] — a server bound to 127.0.0.1 reports 0.0.0.0, and vice versa.
  2. dependencies = [] with httpx behind the streaming extra means pip install gemini-web2api silently yields buffered SSE; the README's Quick Start tells users to pip install httpx but the package path never mentions it.

Without an explicit package list, setuptools falls back to flat-layout
auto-discovery, which sees two candidate top-level directories at the repo
root (`cloudflare/` and `gemini_web2api/`) and aborts:

    error: Multiple top-level packages discovered in a flat-layout:
    ['cloudflare', 'gemini_web2api']

`cloudflare/` is a JavaScript Cloudflare Worker (no Python at all), but the
discovery heuristic only checks for a directory, so it wins alphabetically
and the build dies before the wheel is written.

This affects every installer path that builds a distribution: `uv build`,
`pip install .`, `pip install "gemini-web2api @ git+https://..."`,
`uv tool install`, and PyPI publishing. Running the single-file script and
the Docker image are unaffected because neither installs a package.

Use `packages.find` with an include pattern rather than a hardcoded list so
that future subpackages (and the `tests/` package, which must stay out) are
handled automatically.
The existing workflow only builds the Docker image, and the Dockerfile
copies `gemini_web2api/` directly and runs it with `python -m`, so nothing
in CI ever exercises the pyproject build. That is how the broken
flat-layout discovery shipped unnoticed.

This job builds both distributions, installs the wheel with the
`[streaming]` extra in a clean venv, and asserts the console script runs and
that httpx was actually pulled in.
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.

1 participant