When your software has third party dependencies you’ll normally develop using the latest versions of those dependencies. This makes sense; the latest versions should have all of the latest features, bug fixes and security fixes, and during development it’s quite easy to handle upgrades to your dependencies.
Imagine then that you’ve developed a system, and you’ve released it. Of course, as you get closer to releasing your application you’ll generally fix the version numbers of the dependencies so that you can precisely control what gets deployed to your system.
Imagine your surprise then, when you run a build with some minor change, and it fails with a list of complaints about dependencies.
Or imagine you’re using a language like Python, where dependencies are downloaded during installation, and one day your installer suddenly starts failing even though it used to work. Again it’s complaining about dependencies.
What’s gone wrong?
Back when you were developing your system, and you were updating the versions of the dependencies you had a choice. Of course, you made the right choice and set the exact version(s) of each dependency that your software needed. Something like:
Where “==” specifies the exact requirement, and “<=” specifies a version earlier than or exactly that specified.
If you take a closer look at the dependencies for, for example,
somefilestuff v3.2.6 you might see something like:
So far, so good. Looking even further, you might take a look at
awesomecache as that seemed to appear in the error logs, and you see the dependencies listed as:
cachesecuritythings package, or maybe one of its dependencies is probably pretty popular in your error logs, so it looks like it might be to blame.
However, that’s not actually the case. It looks like the creators of the
awesomecache package decided that they wanted the latest version of
cachesecuritythings to be installed whenever
awesomecache was installed. That probably seemed like a good choice: the latest security updates to the package would always be installed without the need for them to make any changes and release a new version of their own package. Problems start to occur when the creators of
cachesecuritythings make a change that no longer works with your environment, or your operating system, or some other package. They may be doing everything right – updating their documentation, updating version numbers, letting everyone know their new list of compatible environments, but none of that matters. It doesn’t matter because when the creators of
awesomecache include it, they make a point of ignoring almost all of those details when they didn’t specify a particular version number to be included in their own package. By doing this, they also ensure that everyone who uses their package is liable to have build problems when
cachesecuritythings is updated. Further, everyone who depends on these packages also faces this problem, and the further away from the original dependency specification, the less likely the package designer is to know
cachesecuritythings is even a dependency, let alone that it’s included without any control over the version. Because of this, if you’re going to apportion blame, and given the amount of trouble this will be causing you, that’s reasonable, the blame actually lies with the creators of
So, What Can You Do To Solve This Problem?
The bad news is that you can’t entirely prevent this problem from affecting you, although you can minimize the chances of it causing you problems.
First of all, make sure that your code specifies all of the versions of all of its dependencies; you can’t really complain too much about other developers not specifying versions if you don’t do it yourself.
Next, examine your code’s dependencies and see whether they specify the versions for their own dependencies. If they do, examine the next level of dependencies. If there’s a dependency without a version specified then you’ll need to investigate further.
Doing this can be a lot of work – you might need to look through the repositories you’re downloading the dependencies from, and maybe root through the source code too.
If there’s a version of the build that works, it makes things a lot easier because it’s easier to find the version of each dependency that is installed. There are tools to help with this; for example if you’re using Python there are tools such as
pipdeptree that will display a list of all of your installed packages, and all of their dependencies, and all of their dependencies, and so on.
Once you’ve found the dependencies whose versions aren’t specified you need to be the one who does things properly – in your own project you need to add a specific version the dependency – even though it’s not directly one of yours.
Translating this into your requirements list will give you something like:
# Third party dependencies where other people didn't pin their versions properly
cachesecuritythings==<appropriate version number>
By doing this, you’re forcing the system to use a fixed version of the dependency and this should solve the problem. It’s not a graceful solution, after all it’s someone else's dependency, but it’s about the best there is until they actually specify a version for their own dependency. At the very least you can take control of the versioning and keep your system working, and that’s really the important part.