How do I eliminate absolute paths from my code and build scripts?

In my last post, I discussed the need for your code and build scripts to avoid absolute paths. If they do use absolute paths, those paths become hard dependencies that must be satisfied for you to be able to build your product on a given machine. Obviously, you cannot eliminate all hard dependencies (you’ll need to build within the context of an operating system, for example), but the more you eliminate the easier it will be to quickly start working on the code, rather than on your system.

The need to eliminate absolute paths applies to all paths that are used, implicitly or explicitly: paths to build tools, to source files, to target locations for built files, even to standard OS components. The more of these you can replace, the more independent your build scripts will be. Let’s look at some ways we can eliminate these paths.

Use relative paths

If you want to just replace your absolute paths with relative paths, you immediately face the question of “relative to what?” The simple answer is the current directory. But each of the other options below can and will use relative paths as well (e.g. you can append a relative path to one you get from an environment variable). The major benefit to switching to relative paths is that it will often be the most straightforward change for paths to code and the resulting binaries. To make this change, first find out if the absolute path you care about is always relative to some base path. Then get that base path using one of the methods below, and append the relative path.

Use the current directory

Often the easiest path to take advantage of is the current working directory. Any language you’re using will provide you a way to either get the current working directory path, or at least execute commands as if you were at that directory in a shell. You can also change it if necessary. When a script is always run from a given directory, you can rely on having it as the current working directory in your scripts.

Use the path to the current file

It’s really nice to be able to run scripts without switching to the directory they are in, so it’s often good in build scripts to use the path to the script or code file itself when combining with relative paths. The way to do this varies from language to language and from tool to tool, but is almost always available. One way to take advantage of this is just to cache the current working directory, change it to the directory of the current file, then do the main part of your script, then change back to the cached directory. Alternatively, you can store the path in a variable and use it as needed throughout the script.

Use the PATH variable (sparingly)

All major operating systems have the concept of a PATH variable that they’ll search when trying to execute a command. The PATH variable usually isn’t very helpful when specifying specific files, only when running commands. It’s primary use is for common build tools that don’t have any other way of providing a path for execution. Changing the PATH on your development and build machines should be done as little as possible. If there are other ways of finding a program to run (other than absolute paths), use them. However, it probably makes sense to make one change to your PATH, by adding the path to a common repository of build and development tools that are used in house. You can go pretty extreme and include all of your build tools (including standard ones like Java, the .NET framework tools, etc.) in a repository like this, or just include those developed in-house. If you have a script for setting up the development environment and tools on a new machine, use it to set add any paths needed to the system-wide PATH variable.

Use environment variables

Some tools are best accessed via environment variables other than PATH that provide a path that you can use. For example, Java uses JAVA_HOME. Stuff that ships with the operating system might be available using an environment variable like SYSTEMROOT. If necessary your build scripts can set and later use environment variables in order to communicate information about the layout of code and other files on disk.

Use system calls

From certain languages, you’ll be able to make direct OS calls to methods, like System.Environment.GetFolderPath or SHGetKnownFolderIDList, that provide paths to common locations on the system. There are typically more paths available this way than through environment variables, but it may take more work to write the code to get that information.

Use the registry

Quite frequently, installers will set registry keys that indicate the location of the tools and programs installed. You can use these registry keys to get paths for your build scripts, thus allowing developers and build managers to install things to non-standard locations, which may be useful when you need to install multiple versions of certain tools.

Use configuration files

Finally, you can also use configuration files to allow developers, or the build manager, to set or override specific paths used in the build scripts or in the code.

Just as breaking dependencies can make your code cleaner, breaking dependencies in your build scripts can make them cleaner and decouple them from a specific machine or setup. This gives you more freedom in how you manage builds and releases, seams for more easily modifying your build process, and more confidence that your scripts will work in any environment.