Using Solver-Aided Languages to Build Package Managers
Open-source software is critical for modern development, but most open-source packages require large networks of prerequisite packages, or dependencies, in order to function correctly. Modern software development workflows use package managers to ease this burden. Given a set of constraints, these systems use dependency solving to select compatible versions of dependencies before installing. However, many dependency solvers make ad hoc implementation choices and use heuristics that affect the set of chosen dependencies, and thus affect correctness, code size, and other factors of the final bundled software in ways that are opaque and confusing to programmers. We present PacSolve, a unifying formal semantics of dependency solving. PacSolve can compactly represent the key features and differences between NPM, PIP and Cargo, and express a wide variety of alternative semantics for dependency solving. PacSolve lends itself to a solver-aided implementation in Rosette, which we use to build a drop-in replacement for NPM called MinNPM. MinNPM allows the user to customize the dependency solving semantics to choose between different objectives and consistency criteria. We show empirically that PacSolve is performant and effective on real-world code. For example, on the top 1000 most downloaded NPM packages, we show that MinNPM can shrink the footprint of 20 packages. Moreover, MinNPM only takes 1.7s longer than NPM on average. We also use MinNPM to show that NPM's tree-solving semantics is only necessary for 3 of these packages.
READ FULL TEXT