Internal Dependencies

An internal dependency is a dependency between two projects stored in the same repository. Internal dependencies are therefore closely associated with monorepos in Cranko's terminology. You can have a monorepo that doesn't have any internal dependencies, but usually the point of a monorepo setup is to manage a group of interdependent projects.

As outlined in the introduction to Just-in-Time Versioning, internal dependencies (perhaps counterintuitively) take some extra effort to manage. This situation stems from two assumptions in the JIT model:

  • Any intra-project dependency (internal or external) needs to be associated with a version requirement specifying the range of versions of the "dependee" package that the "depending" package is compatible with. In simple cases this might be expressible as "anything newer than 1.0", but version requirements can potentially be complex ("anything in the 1.x or 2.x series, but not 3.x, and not 1.10").
  • In the JIT versioning model, specific version numbers shouldn't be stored in the main development branch of your repository.

It's important to note that dependency version requirements can't be determined automatically. Say that I have a monorepo containing two projects, awesome_database and awesome_webserver. It's reasonable to assume that at any given commit, the two are compatible, but is the development version of awesome_webserver compatible with version 1.9 of awesome_database? Is it compatible with version 1.1? You could imagine some level of automated API analysis to test source-level compatibility, but it's always possible that the semantics of an API can change in a way that maintains source compatibility but breaks actual usage. Ultimately the only sound approach is for a human to make this determination.

Getting back to Cranko's challenge: at some point I'm going to want to make a new release of awesome_webserver with metadata saying that it requires awesome_database >= 2.0. How can I tell Cranko what version requirement to insert into the awesome_webserver project files when the main development branch can't "know" what the most recent version of awesome_database is?

Commit-Based Internal Dependency Version Requirements

Cranko solves this problem by requiring that you specify internal dependency version requirements as Git commits, not version numbers. For each internal dependency from a "depending" project X on a "dependee" project Y, you must specify a Git commit such that X is compatible with releases of Y whose sources contain that commit in their histories.

What does Cranko do with this information? When making a release of project X, Cranko has sufficient information to determine the oldest version of project Y containing that commit. It will rewrite project X's public metadata to encode that version requirement.

It can happen that no such release exists — perhaps project X requires a new feature that was just added to Y, and no release of Y has yet been made. Cranko will detect this situation and, correctly, refuse to let you make a release of project X. However, you can release X and Y simultaneously (in one rc push), and Cranko will detect this and generate correct metadata.

The commit-based model implies a restriction that version requirements for internal dependencies must have the simplest form: X is compatible with any version of Y newer than Z, for some Z determined at release time. This is not expected to be restrictive in practice because Cranko assumes that at any given commit in a monorepo, all projects are compatible as expressed in the source tree.

Expressing Internal Dependency Commit Requirements

Project meta-files don't have native support for commit-based version requirements because it would be inappropriate to include information specific to a project's revision system in such files. Therefore, Cranko always has to define some kind of custom way for you to capture this metadata, with the specific mechanism depending on the project type. For instance:

  • In Rust, you add [package.metadata.internal_dep_versions] fields in Cargo.toml
  • In Python, you annotate version requirement lines in your setup.py file or equivalent

The documentation for each language integration should specify the approach and specific syntax you should use.

Development with Internal Dependency Requirements

The cranko bootstrap command will endeavor to update your project files to include your pre-existing internal version requirements using a special "manual" mode. This is required for version requirements that reach into a project's pre-Cranko history.

Once the internal requirements are set up, you should ideally update commit requirements as APIs are added or broken. For instance, say that project awesome_database adds a new API in commit A, and project awesome_webserver starts using it sometime later in commit B. Commit B should update the metadata to indicate that awesome_webserver now requires a version of awesome_database based on commit A, or later.

If you don't remember to update the metadata immediately, that's OK. So long as the metadata for awesome_webserver are updated sometime before its next release, the released files will contain the right information.