The Tower Keeps Rising: What a Decade of Software Evolution Teaches Us About Technical Debt

The Tower Keeps Rising — this phrase, borrowed from a recent reflection by Armin Ronacher, captures the uncomfortable truth about modern software development: we keep building on top of existing systems, adding layers, abstractions, and dependencies, without ever truly dismantling the old. In his July 2026 blog post, Ronacher presents a sobering case study of how the Python ecosystem, particularly around packaging and tooling, has evolved over the past ten years. The result? A tower that is tall, functional, but increasingly fragile.

This article is not about Python specifically — it’s about a pattern that repeats across every tech stack. Whether you’re building a SaaS product, managing a legacy codebase, or starting a new project, the dynamics Ronacher describes will sound painfully familiar. Let’s break down what happened, why it matters, and what you can learn from it.

The Problem: A Decade of Layering Without Unification

Ronacher’s story begins in 2016. At that time, the Python packaging landscape was already fragmented. Tools like setuptools, pip, virtualenv, and wheel coexisted, each solving part of the problem but not working seamlessly together. Developers had to understand the quirks of each tool to build and distribute packages. The community knew this was a problem, but instead of a clean break, they chose to evolve incrementally.

Fast forward to 2026. The tower has kept rising. New tools like pipenv, poetry, flit, and hatch have emerged, each promising a better developer experience. But they all rely on the same underlying infrastructure — PyPI, setuptools, and the wheel format. The result is a complex stack where a modern tool like pip (version 24+) still calls legacy setup.py code under the hood for certain packages. The tower is taller, but the foundation hasn’t been replaced.

This is not unique to Python. Think about JavaScript’s npm ecosystem, where packages depend on hundreds of transitive dependencies. Or the Java ecosystem, where Maven and Gradle still handle legacy Ant builds. In every case, the industry prefers adding new layers over removing old ones. The reason is simple: refactoring a foundation is risky, expensive, and breaks existing users.

The Case Study: Python Packaging in 2026

Let’s look at specific examples from the source article. Ronacher describes a scenario where a developer in 2026 wants to publish a package. The process involves:

  • Writing a pyproject.toml file (the modern standard)
  • Using a build backend like hatchling or flit-core
  • Running pip install build to build the package
  • Uploading to PyPI via twine

But here’s the catch: if the package has a setup.py file (common for many older packages), the build system silently falls back to setuptools, even if the developer specified a different backend. This means that a decade-old code path is still executed, with all its bugs and inconsistencies. Ronacher notes that the Python core team has made tremendous progress — for example, the introduction of pip 23+ and the deprecation of Python 2 — but the tower keeps rising because each new tool must maintain backward compatibility.

One concrete statistic from the article: as of July 2026, over 40% of packages on PyPI still include a setup.py file, even if they also have a pyproject.toml. This means that the modern packaging pipeline still handles legacy code for nearly half of all packages. The tower is not just tall — it’s built on a foundation that was never fully replaced.

Why This Matters: The Cost of Ever-Rising Abstraction

For businesses and developers, this pattern has real consequences. Consider a startup building a data pipeline in 2026. They use modern tools like uv (a fast Python package manager) and rye (a project manager). These tools are fast and elegant, but they depend on the same PyPI infrastructure and legacy build systems. When a critical dependency fails to build because of a setup.py issue, the startup loses hours debugging a problem that shouldn’t exist in 2026.

Another example: a large enterprise maintaining a monorepo with hundreds of internal packages. They standardize on poetry for dependency management. But when they need to integrate with an external library that still uses setup.py with custom build commands, they hit compatibility issues. The tower keeps rising, but the floors don’t always connect.

Ronacher’s key insight is that the software industry has a collective aversion to breaking changes. We prefer to build scaffolding around old systems rather than replace them. This is rational in the short term — breaking changes anger users and require migration efforts — but it creates long-term technical debt that compounds.

The Solution: Intentional Deprecation and Radical Simplification

What can be done? The article doesn’t offer a magic bullet, but it points to a few principles that have worked in practice:

  1. Set hard deadlines for deprecation. The Python community successfully removed Python 2 support by setting a firm date and sticking to it. This forced package maintainers to migrate. Similar approaches could work for old build systems.

  2. Create clear migration paths. When the pyproject.toml standard was introduced, the community provided tools to automatically convert setup.py to the new format. This reduced friction.

  3. Limit backward compatibility windows. New tools should not be designed to support legacy systems indefinitely. At some point, the old layers must be removed.

  4. Measure and communicate. Ronacher notes that the Python packaging authority now publishes annual reports on the percentage of packages using legacy formats. Transparency creates pressure to modernize.

One practical example from the article: the pip team introduced a feature in 2025 that warns users when a package uses a deprecated build system. This simple change increased the migration rate by 15% in the first six months. Small nudges can have large effects.

Real-World Implications for Your Tech Stack

If you’re a CTO or lead developer, Ronacher’s case study offers a mirror for your own systems. Ask yourself:

  • How many layers of abstraction does your build pipeline have?
  • How many legacy dependencies are you carrying?
  • When was the last time you removed a foundational component rather than adding a wrapper?

The tower keeps rising in every organization. The question is whether you are building a skyscraper or a tower of Babel. The skyscraper has a solid foundation and clear architecture. The tower of Babel has many floors, but they don’t communicate well, and the top is unstable.

Conclusion: Build to Last, Not Just to Rise

Armin Ronacher’s reflection on the Python ecosystem is a cautionary tale for all of us. The tower keeps rising because it’s easier to add than to remove. But the cost of that approach is mounting complexity, slower development, and hidden bugs. The best teams I’ve seen — whether in open source or commercial projects — prioritize periodic cleanup. They schedule time to remove deprecated code, they set sunset dates for old features, and they resist the temptation to add yet another abstraction layer.

In 2026, the tower is taller than ever. But it doesn’t have to be unstable. By learning from the Python packaging story, we can build systems that are not only rising but also resilient.

Source

← All posts

Comments