Cleaning up Lichess dependency tree

I recently started contributing to development of Lichess (lila is the main repo). Architecturally it is a monolith written in Scala consisting of several dozens interconnected modules. All the dependencies are conveniently specified in the build.sbt file, which looked something like this:

lazy val puzzle = module("puzzle",
  Seq(common, memo, hub, history, db, user, rating, pref, tree, game),
  ...
)

lazy val user = module("user",
  Seq(common, memo, db, hub, rating, socket),
  ...
)

As one can guess, the dependencies of each module are specified in a list given as an argument to the module function. However, for many modules this list contains some transitive dependencies explicitly, while others are not included. For example, the puzzle module can pull its common, memo , hub, and rating dependencies transitively through user, similar to how it does that with the socket dependency.

This situation is problematic because of at least 2 reasons:

Untangling the mess

After some googling (chatGPT-ing?) I could not easily find a satisfying dependency resolution/simplification tool and so decided to write (prompt?) a script that would do that for me. ChatGPT suggested using python libraries networkx for graph building/analysis and pydot for the generation of pretty images.

The script itself can be found here and in essence consists of the following steps:

That’s it, now the only thing left is produce pretty graphs using pydot. Here are the old and the new (simplified) dependency graphs with essential dependencies colored in cyan and topological generations organized in vertical columns going from left to right:

Original dependency graph:

Original Lichess dependency graph

Simplified dependency graph:

Simplified Lichess dependency graph

Conclusions