Skip to content

feat(sdist): dynamic setup.py evaluator for build dep extraction#874

Merged
arrdem merged 3 commits intomainfrom
arrdem/setup-py-evaluator-860
Mar 20, 2026
Merged

feat(sdist): dynamic setup.py evaluator for build dep extraction#874
arrdem merged 3 commits intomainfrom
arrdem/setup-py-evaluator-860

Conversation

@arrdem
Copy link
Collaborator

@arrdem arrdem commented Mar 20, 2026

Adds a dynamic setup.py evaluator to the sdist configure tool. Instead of trying to statically analyze setup.py via AST (which only covers a shrinking subset of simple cases), we exec() the file with a capturing setup() injected into module globals and a mock import system that prevents ImportError on missing packages.

This is inherently unsound — setup.py is arbitrary Python — but we're already running in Bazel's sandbox with a hermetic interpreter, so the blast radius is contained. The approach handles dynamic patterns (conditionals, function calls, file reads, platform checks) that real-world legacy packages actually use.

How it works

  • _fake_setup(*args, **kwargs) captures all keyword args then raises _SetupCapture to abort execution
  • _SetupCapture inherits from BaseException so except Exception: blocks in setup.py can't swallow it
  • _MockModule provides a recursive mock object for any attribute access, preventing crashes from from mypackage import __version__ etc.
  • _MockImportFinder intercepts imports via sys.meta_path, returning mock modules for anything not in stdlib
  • setuptools.setup, distutils.core.setup, and bare setup in globals all route to the capturing function
  • Interpreter state (sys.modules, sys.meta_path, sys.argv, sys.path, os.getcwd()) is fully restored in a finally block
  • BaseException catch handles SystemExit from sys.exit() calls gracefully

Extracted setup_requires feed into build_requires and extra_deps. install_requires is reported in a new setup_py_install_requires field.

Also removes the noisy _log_build_dep_info print statements that spammed Bazel output during every sdist build.

Changes are visible to end-users: no

Test plan

  • 37 tests total, all passing
  • 11 new "rogues' gallery" tests covering real-world setup.py patterns
  • 10 tests from initial commit covering literal lists, variable refs, list concat, dynamic runtime logic, etc.
  • 16 pre-existing tests continue to pass

Base automatically changed from arrdem/sdist-native-detection-860 to main March 20, 2026 05:12
Exec setup.py with a capturing setup() and mock import system to
extract setup_requires and install_requires from legacy packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arrdem arrdem force-pushed the arrdem/setup-py-evaluator-860 branch from b6711b6 to 12b280e Compare March 20, 2026 05:14
@aspect-workflows
Copy link

aspect-workflows bot commented Mar 20, 2026

Bazel 8 (Test)

All tests were cache hits

106 tests (100.0%) were fully cached saving 47s.


Bazel 9 (Test)

All tests were cache hits

106 tests (100.0%) were fully cached saving 1m 18s.


Bazel 8 (Test)

e2e

All tests were cache hits

39 tests (100.0%) were fully cached saving 32s.


Bazel 9 (Test)

e2e

All tests were cache hits

39 tests (100.0%) were fully cached saving 29s.


Bazel 8 (Test)

examples/uv_pip_compile

All tests were cache hits

1 test (100.0%) was fully cached saving 444ms.

arrdem and others added 2 commits March 19, 2026 23:52
- _SetupCapture now inherits BaseException so `except Exception:` in
  setup.py files can't swallow the capture signal
- Catch BaseException (not just Exception) so sys.exit() doesn't crash
  detect()
- Accept *args in _fake_setup for ancient positional-arg setup() calls
- Save/restore sys.path and os.getcwd() to prevent state leaks
- Add 11 new test cases covering real-world setup.py patterns:
  __main__ guard, qualified distutils call, file I/O failure, setup()
  never called, sys.exit(), platform conditionals, pkg_resources import,
  os.chdir(), sys.path mutation, Extension objects, setup.cfg+setup.py
  merging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The build dep discovery logging via print() was very noisy during
normal Bazel builds. Remove the function and its call sites entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arrdem arrdem merged commit d6883bc into main Mar 20, 2026
4 checks passed
@arrdem arrdem deleted the arrdem/setup-py-evaluator-860 branch March 20, 2026 07:40
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