If we're asked about the greatest technical disaster in the
'modern' IT industry, we'll respond that, definitely, it is the notion of a
dependency and the naturally emerging dependency hell.
Dependencies within a single programming project may (and must) be kept
under strict control, that's why we disallow using any 'build tools' but
make
(either GNU Make, or Posix Make, but not CMake nor any other
'modern' tools intended to 'assist' the building process, effectively
steering the programmers to a total control loss). However, when it comes
to external dependencies, no efficient control over them is possible at
all.
External dependencies can be build-time and run-time. So here's the rule:
make
utility (again, either GNU Make or Posix Make,
but no other tool) and — doubtfully and with
limitations, but, well, to some extent — standard libraries. Okay,
presently this only means plain C standard library, as we deliberately
prohibit any use of the so-called standard C++ libraries in our software
projects being implemented with the help of C++, and actually no other
general purpose programming languages are allowed here, while special
purpose programming languages are not allowed to have libraries at
all.The idea behind the rule is obvious: the less your program needs to build and run, the better. However, there's also another idea. If you, as the author of a program, by simply choosing what to use and what not to use, can solve in advance any problem which either a user of the program, or a package maintainer may experience, then you must do so for any program you're going to let anyone use.
Interpreted execution is not acceptable for general-purpose languages, as the interpreter would be an obvious runtime dependency. Interpreted execution model is only suitable for scripting and built-in DSLs, and for them it is the only possible choice as a compiler used for scripts would become a runtime dependency.
First of all, forget about interpreted languages that have their “ecosystems”, such as Perl, Python, Ruby and so on. In both situations when the interpreted execution model is to be used (again, these are scripting and built-in DSLs), there must be no damn ecosystem, as the ecosystem will become run-time dependency even if the interpreter itself is built-in. Smaller languages such as Tcl are good for built-in interpreters. Often it even makes sense to implement your own language, like another small dialect of Lisp or whatever. So, languages like Perl, Python, Ruby and so on, are not acceptable as general-purpose languages, and they are not acceptable as built-in languages because of their ecosystems. Actually they have no valid purposes at all. There are no tasks in the universe that those langs may be suitable for, and learning such languages is obviously a waste of time.
All these semi-interpreted languages like Java or C# must not be considered for any task as well, because their “run-time environments”, such as JVM, dot-net, mono etc., are run-time external dependencies, too.
There are some points to mention about compile-time dependencies, too. First of all, forget about autoconf/autotools and the like. They tend to produce problems with portability, instead of solving them. And don't even think about cmake. Yes, there are systems around where CMake is not available by default, so a package maintainer (or, worse, a user) will have to build cmake itself in order to build your program. Furthermore, CMake is easy to use for programmers, like, “take this bunch of code and create a binary for me”, but is a real nightmare for package makers. Furthermore, the last but not least, if you feel you need CMake, it effectively means your codebase is out of control. Definitely you must regain the control as soon as possible, instead of outsorcing it to various harmful tools.
The source tree of your program must be fully self-containing; whenever the program is published in its source form, everything it needs must be included in the archive along with your own sources. No external libraries are tolerable, at all; we unwillingly make a temporary exception for some parts of the C standard library. If you think you really need a certain library, please consider it carefully: you will, first of all, need to put it into the main source tree of the project, which effectively means it becomes another piece of code we (you!) have to maintain. And to build every time we decide to do a complete rebuild.
Second, the code of a library you include that way must obey all rules just like any of the code, so it is highly likely you'll have to hack the library to get rid of inappropriate things like autoconf/autotools, CMake and other crap. Remember we limit the language subsets used in the project, and all parts of the code within the project must obey, including the libraries, so chances are you'll have to do a lot of job removing, e.g., C99-isms.
Last but not least, it appears to be stupid to have in your source tree a huge library of which you only use a small subset, so perhaps you'll have to prepare a more or less reasonable subset of the library's initial code to use in your project. Once again, think first.
On the other hand, if the library you want only consists of a single small module with a clear interface, written in C90 or K&R C, perhaps it is not a problem to add it. Beware of these header-only 'libraries', although they are not under a complete ban.
Besides that, you must always make sure your program can be built as a statically-linked binary. It is okay to keep dynamic builds possible for cases when the program is transferred to the target machine as a source tarball and gets built there, but for all cases of binary distribution, including distribution as a package for a particular package manager (such as deb, yum, rpm and the like), all executable binaries must depend on nothing but the OS kernel. Yes, the possibility for distribution as a binary is mandatory, you must never assume the compiler exists on the target machine. And no, packing all the “shared” libs together with the binary is NOT a solution.
If your program uses some binary data not intended to be supplied by the user, such as icons or other images, you must make sure they all are built into the executable. The most obvious way to do so is to convert every such binary into a C module containing a single initialized char array, and simply use such generated module in your program.
Another thing is not so obvious: be extremely discriminating with
features provided by the so-called “standard library”.
Some of them may even ruin your capability of building statically (like
Glibc's getpwnam
), some other, like locales, will silently
make your binary dependent on external data files. And a lot of them may
accidentally make your code less portable (this is specially true for GNU
extensions and functions invented by “standards”). Remember
one thing: the less you depend on, the better.
In particular, please do your best not to suck in all these locale-related
stuff. E.g., don't use functions from <ctype.h>
, such
as isspace
, isalpha
, isdigit
and so
on. Single thoughtless
if (isspace(c)) {
instead of just
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' ) {
will ruin everything.
In fact, it is highly likely we'll get rid of the standard library one day, completely. The less parts of it our program will depend on, the less effort we'll have to spend doing it, and this should be always kept in mind.
The problems caused by libraries often look easily fixable but the fixes are clearly to take a lot of time and effort. Sometimes some prioritization may be done, so that fixes to foreign code, such as de-c99-fication, are postponed, but only in case you're sure they're doable, and it is you, not someone else, who, earlier or later, will have to do all the dirty job. And one more thing: asking the user to install a library in order to build your damn program is absolutely and striclty inappropriate, so depending on an external library, not bundled with your sources, is not tolerable at all, even temporarily.