Heads-up: PyTorch's build is switching from setuptools to scikit-build-core, and setup.py goes away

Next Monday Jul 20, 2026, PyTorch’s build backend switches from setuptools to
scikit-build-core 1.0, and setup.py is removed.

TL;DR: If you build with spin or pip install (regular or editable),
nothing changes. If you invoke python setup.py ... anywhere
(scripts, docs, muscle memory), switch to spin/pip. Prebuilt wheels and runtime
behavior are unaffected.

Why

Modern Python packaging orchestrates builds through standard interfaces
(PEP 517/518/621); the legacy python setup.py ... commands they replaced
have been deprecated for years. PyTorch is a hybrid: a substantial
CMake-built native core alongside a large pure-Python library, and while
CMake covers the native half down to the extension modules, wheels,
sdists, and package metadata are outside its scope. Until now, setup.py
bridged that gap with a decade of custom orchestration. The maintained,
standards-based backend purpose-built for exactly this hybrid is
scikit-build-core: it packages the Python side like any modern backend and
drives the CMake build, all from pyproject.toml. The switch puts PyTorch’s
build on the interfaces the rest of the ecosystem targets and makes
standards-compliant artifacts the default.

What you need to change

setup.py is removed, so any direct invocation of it stops working. The
replacements:

Before After
python setup.py develop spin develop (or pip install -e . -v --no-build-isolation)
python setup.py install spin install (or pip install . -v --no-build-isolation)
python setup.py bdist_wheel python -m build --wheel --no-isolation
python setup.py clean spin clean

Either form works. Each spin command is a thin wrapper that runs exactly
the pip command shown beside it (using uv when available); the pip and
python -m build forms are the standard Python build interfaces and have
been the recommended way to build PyTorch for a while. The wrappers belong
to spin, the developer CLI we adopted last
year
.
Build-adjacent developer tasks that used to hide behind setup.py
subcommands are moving there; spin with no arguments lists what is
available. Spin is part of requirements.txt, so a PyTorch dev environment
already has it; for a standalone, global install use uv tool install spin
and run it from the repository root.

What stays the same

  • The spin and pip workflows are untouched, including editable
    installs and all incremental-rebuild behavior (the CMake cache carries
    over between builds exactly as before).
  • Every build-configuration environment variable keeps working:
    USE_CUDA, USE_ROCM, DEBUG, REL_WITH_DEB_INFO, MAX_JOBS,
    CMAKE_BUILD_TYPE, CC/CXX, and the rest. They now seed the CMake
    cache directly and are documented in cmake/EnvVarForwarding.cmake and
    pyproject.toml, instead of being interpreted by setup.py code.
  • ccache/sccache, cross-compilation, and CMake-cache-based workflows
    carry over: the backend drives the same CMake build you know, without
    setup.py in the middle.
  • Performance is neutral, by construction and by measurement. We
    compared same-day CI binary builds (scikit-build-core vs setuptools):
    matched build jobs show identical durations within noise, and all 52
    wheel artifacts across every platform family are byte-comparable in size
    (within 0.02%; the only content difference is license files now included
    per PEP 639, and the setuptools-specific top_level.txt going away).

Editable installs

scikit-build-core’s editable mode separates the two halves of a
development install cleanly: Python sources are imported straight from
your checkout (edit and rerun, as always), while compiled artifacts
(torch._C, the libtorch libraries) are imported from the wheel install
location instead of being scattered through the source tree.

Day to day this is invisible (import torch just works), but a few
things are worth knowing:

  • Tooling that expects .so/.pyd files inside the package directory
    (torch/lib, torch/_C*.so) needs updating; compiled artifacts no
    longer land there.
  • Rebuilding after a C++ change works exactly as it does today: rerun
    spin develop (or pip install -e . -v --no-build-isolation) for an
    incremental, CMake-cached rebuild.
  • New and strictly opt-in: an editable install can also rebuild
    automatically on import torch. This capability is not battle-tested
    yet, so it is off by default and the rerun workflow above remains the
    recommended one. Opting in happens at install time, by setting the
    variable on the install command (SKBUILD_EDITABLE_REBUILD=true spin develop); that install then rebuilds on the first import torch in a
    process, until you reinstall without the variable. CONTRIBUTING.md has
    the details.

The full development flow is documented in CONTRIBUTING.md.

If you build on top of PyTorch

Nothing changes. C++/CUDA extensions built with torch.utils.cpp_extension
keep working as-is: the setuptools-based helpers are not removed, and torch
keeps its runtime setuptools dependency. The one case that needs the
command replacements above is CI that builds PyTorch itself from source.

If you redistribute PyTorch

For conda-forge, Spack, distro, and other from-source packagers:

  • Build via the standard PEP 517 interfaces: python -m build --wheel --no-isolation or pip install. Anything that invokes setup.py
    directly needs porting.
  • Build requirements now include scikit-build-core>=1.0 (see
    [build-system] in pyproject.toml / requirements-build.txt).
  • Configuration via environment variables is unchanged, and CMake options
    can also be passed explicitly through the standard config-settings
    mechanism, right on the build command:
    pip install . -Ccmake.define.FOO=BAR (long form
    --config-settings=cmake.define.FOO=BAR) or
    python -m build -Ccmake.define.FOO=BAR. See the
    scikit-build-core configuration docs
    for the full set of options.

Additional benefits

  • Build products no longer land in the package directory: with setuptools,
    any from-source build (regular or editable) copied compiled libraries
    into torch/ itself; now the source tree stays clean apart from the
    familiar build/ directory.
  • The switch retires over 2,000 lines of bespoke build orchestration in
    favor of declarative configuration in pyproject.toml.
  • Wheel metadata (version, dependencies, license files) now flows through
    standard hooks instead of hand-rolled code; PEP 639 license inclusion,
    for example, comes for free.
  • The migration lands on scikit-build-core 1.0, which gained several
    capabilities specifically to support PyTorch’s needs: for example, the
    [tool.scikit-build.env] table, added upstream so that PyTorch (and
    projects like it) need no custom backend wrapper at all. Our thanks to
    @henryiii and the scikit-build team. The idea has been in the air
    for a while, too:
    scikit-build came up on this forum back in 2021,
    before scikit-build-core was even written.

Rollout and feedback

The change lands as two stacked PRs, #180247 (backend switch) and
#180248 (setup.py removal), tracked in RFC #157807, where the full
design discussion lives. CI is validated across all platforms and wheel
families, including content parity of the produced wheels against the
setuptools build.

One question we would like your input on: where should the build-variable
reference (USE_CUDA, MAX_JOBS, and friends) live long-term? Candidates are
CONTRIBUTING.md, the README, the CMake file that implements the forwarding
(cmake/EnvVarForwarding.cmake), pyproject.toml, or the rendered docs site;
tell us which you would find most discoverable.

If you maintain tooling that scripts setup.py, redistribute PyTorch, or
have an unusual build integration, now is the moment to check it;
comments welcome here or on the tracking issue #189758.