Dependency audit v1

Abstract

There have been situations in recent times when a new release of a 3rd party tool or library has broken our builds, without any new commits on status-im. This means it is very difficult to go back and rebuild a version from an old commit, if that 3rd party tool or library introduces a different behavior, or is altogether incompatible. To combat this, we must ensure that we lock down the versions of the tools and libraries we depend upon.
What follows is the result of an audit done across status-react to raise potential issues and suggest remedy actions. Please comment if you have other suggestions or have come across other issues that haven’t been documented here.

Results

Unlocked git dependencies

Problem:

Even if these repos are maintained by Status, we should still lock versions down, since any future updates in those repos will make builds of older versions of the consumer repo impossible to reproduce

Found instances:

Suggested actions:

Use specific commit SHAs/tags to reference the repos.

Unlocked npm dependencies

Problem:

We fork some repositories in order to customize some aspects, but most of the times we don’t have the discipline to create an official release and we add the reference to packages.json without the reference to a tag/release. That means that as soon as someone pushes a new commit to that dependency repo, it will be impossible to build the exact same executable in the future when building a past commit

Found instances:

mobile/package.json.orig and desktop/package.json.orig

Suggested actions:

Create releases for each of the forked repos and use that tag in package.json.orig

Tools which are installed through brew/apt without locked-down versions

Problem:

This isn’t necessarily a problem per-se in every case, but we should be aware of it and acknowledge that we’re accepting the risks implicitly if we don’t lock things down

Found instances (non-exhaustive list):

Suggested actions:

  • Lock down the versions for tools which we know must be locked down (e.g. Node)
  • Discuss which others we should strive to lock down (all?, only a subset which is more bound to present problems?) → Could be interesting to have a text file serve as a single source of truth regarding the tool versions used, which would be used in scripts to retrieve the sanctioned versions.

Handling of pre-existing versions of tools

Problem:

Some tools are installed with a specific version indication, but only if the tool isn’t already present in the user’s system, so it may happen that the user already has a version which doesn’t match the one we recommend/expect (e.g. very old version or a newer broken version).

Found instances:

Examples: conan, lein, watchman, node (when nvm not installed), Ruby, Clojure CLI.

Suggested actions:

  • Standardize on a single method that checks not only for the existence of a tool, but that it matches a sanctioned version.
  • Alert the user if a tool is not the expected version and explicitly ask for his approval to ignore the warning.
  • In the specific case of yarn, we might want to look into the yarn policies command to enforce a single yarn version without imposing a version globally on the user’s system.

yarn.lock versions don’t match package.json

Problem:

yarn check is currently reporting some issues that prevent us from using the command in a CI environment to detect future problems.

Found instances:

$ yarn check
yarn check v1.12.3
warning Pattern ["bignumber.js@github:status-im/bignumber.js#master"] is trying to unpack in the same destination "/home/pedro/.cache/yarn/v4/npm-bignumber-js-4.0.2-cc066a0a3d6bfe0c436c9957f4ea8344bf963c89/node_modules/bignumber.js" as pattern ["bignumber.js@https://github.com/status-im/bignumber.js.git"]. This could result in non-deterministic behavior, skipping.
info [email protected]: The platform "linux" is incompatible with this module.
info "[email protected]" is an optional dependency and failed compatibility check. Excluding it from installation.
error "react-native-firebase#react-native@>= 0.57.0-rc.3" doesn't satisfy found match of "[email protected]"
error "react-native-fs#react-native@^0.51.0" doesn't satisfy found match of "[email protected]"
error "react-native-keychain" is wrong version: expected "3.0.0-rc.3", got "3.0.0"
error Found 3 errors.

For the desktop variant, there are 7 warnings and 36 errors, which deserve some attention. Most of the errors could be fixed by updating yarn.lock to point to babel/[email protected].

Suggested actions:

  • Fix warning by using the same URL for bignumber package on both mobile and desktop environments;
  • Fix error for react-native-keychain by creating our own release and updating version string in yarn.lock;
  • Look into fixing react-native errors, since react-native-firebase wants >=0.57.0-rc.3 and react-native-fs wants ^0.51.0 (the latest version of react-native-fs wants ^0.57.0, so we’d be OK if we were on RN 0.57). We can do this if we’re OK with only upgrading react-native-* libraries if they are compatible with the RN version we currently use (or be willing to upgrade otherwise);
  • Finally, run this command on the CI build and use it to fail PR builds which generate warnings or errors.

Other notes

In install_node_via_package_manager we’re configuring the source with https://deb.nodesource.com/setup_9.x on Linux, should be 10.x.

2 Likes

I would say that we need to pin versions of all these tools. This way we will also make tools upgrades visible.

As it turns out, there are a few challenges with the package managers we use (and it doesn’t look like there are better alternatives out there at the moment - e.g. snappy doesn’t have recipes for cmake and other tools we need):

  • On Linux, different distro versions have specific package versions available. So for instance, you might find e.g. cmake 3.5.1 is the latest package version for Ubuntu 16.04, but that package version isn’t available on Ubuntu 18.04.
    • Alternative solution: @jakubgs suggested that the easiest way to work around this and have an environment which is guaranteed to be the same everywhere is with a Docker image, and I tend to agree. We’d then forward commands on the makefile to the Docker container;
  • On MacOS, brew doesn’t support installing a specific version of a recipe out of the box. It can be done by manipulating the recipes GitHub repo (checking out a specific commit and installing from there), but that is probably more trouble than it’s worth.
    • Alternative solution: for the time being, I’ll use packages which at least lock down the major version (e.g. install node@10 package instead of node).

On MacOS, brew doesn’t support installing a specific version of a recipe out of the box.

Actually, yes it does.

You can specify a URL for a specific version of a formula if you want to via URL to a specific file on github, for example:

brew install 'https://raw.githubusercontent.com/Homebrew/homebrew-core/55c61f4768aed107bfbedaaccfe35706b2885b47/Formula/[email protected]'
1 Like

There’s also Nix & NixOS | Reproducible builds and deployments to install dependencies, which should just do what we want (at the cost of adding yet another tool, one tool to rule them all though), not sure if it’s worth considering

1 Like

It definitely looks interesting, and worth investigation if it would allow us to have a single package manager for MacOS and Linux. Right now it’s a mess with Homebrew for MacOS and apt-get for Linux (where apt-get doesn’t allow retrieving historic versions).

We used nix before to pin dependencies (less dependencies than status-react), it all worked out fine, the only issue is that package availability is not very extensive so you might have to write your own package recipes

Funny that, we’ve experimented with Nix for Nimbus, though it’s a bit of a chicken and egg - not enough users to maintain it, therefore we don’t want to be a user. Perhaps together we can bring usage up to the critical mass needed… summoning @zahary and @stefantalpalaru!

Maybe the way to evaluate dependencies when bringing new ones in (such as nix), is to judge them by what they remove, not what they add. If by adding nix, something else cannot be removed, it’s a net complexity increase, regardless if some operations become easier - those gains are offset by how much harder it becomes to decipher problems and find root causes, and how much more knowledge you need to understand the whole.

Yeah, it would only make sense to bring nix in if that meant we could get rid of dependency on Homebrew (for OSX) and apt (which doesn’t support historic versions).

I’d love to see more adoption of Nix within the company. The promise of Nix is that you’ll be able to execute a single command that creates an environment with a precisely controlled version of everything required by the build (this includes things like libraries, compilers and build tools, even the shell running the environment).

The most significant downside of Nix is that it uses a rather peculiar build configuration language which has a bit steep learning curve.

Welcome to the jungle, we got fun and package managers: List of software package management systems - Wikipedia

Your best bet is to leave packaging to distro packagers. Just provide a proper build system that doesn’t make assumptions about the target environment (I remember hitting a hardcoded Qt library path that couldn’t be overridden in an NPM package).

The work in progress is in the feature/nix branch of status-react. So far it has allowed me to get rid of Homebrew (macOS), apt-get (Linux), npm install -g and nvm dependencies, so sounds like a net complexity decrease (although Nix does have a steep learning curve as soon as you start needing to create your own expressions). It does make things much simpler and cleaner for the end user (contributor), which is a huge win in my opinion.

UPD: By the way, Nix would be capable of replacing Conan too (used to build Qt), so eventually we can get even leaner.

2 Likes