Skip to content

Commit

Permalink
Start of build systems primer
Browse files Browse the repository at this point in the history
This covers the general background of build systems concepts, before
jumping into any one build system.

Upcoming chapters:
* Make as a build tool
* Caching
* Cross-user caching
* Bazel as a build tool
* Removing undeclared dependencies: toolchains
* Remote execution
* Optimising critical paths
  • Loading branch information
illicitonion committed Jun 13, 2024
1 parent 865a6a5 commit e5c549c
Show file tree
Hide file tree
Showing 20 changed files with 3,131 additions and 0 deletions.
3 changes: 3 additions & 0 deletions example-projects/cpp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/out
/gen/constants.h
*.o
18 changes: 18 additions & 0 deletions example-projects/cpp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
run: out
./out

clean:
rm -f out
find . -name '*.o' -exec rm {} \;

out: main.o formatting/formatting.o
clang++ main.o formatting/formatting.o -o out

formatting/formatting.o: formatting/formatting.cpp
clang++ formatting/formatting.cpp -I "." -c -o formatting/formatting.o

main.o: main.cpp gen/constants.h formatting/formatting.h
clang++ main.cpp -std=c++11 -I "." -c -o main.o

gen/constants.h: scripts/generate_constants.sh
./scripts/generate_constants.sh "Daniel" "Laura"
17 changes: 17 additions & 0 deletions example-projects/cpp/formatting/formatting.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "formatting/formatting.h"

#include <sstream>
#include <string>
#include <vector>

std::string JoinWithCommas(std::vector<std::string> parts) {
std::stringstream out;
if (parts.size() == 0) {
return out.str();
}
out << parts[0];
for (size_t i = 1; i < parts.size(); ++i) {
out << ", " << parts[i];
}
return out.str();
}
4 changes: 4 additions & 0 deletions example-projects/cpp/formatting/formatting.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include <string>
#include <vector>

std::string JoinWithCommas(std::vector<std::string> parts);
9 changes: 9 additions & 0 deletions example-projects/cpp/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <iostream>

#include "formatting/formatting.h"
#include "gen/constants.h"

int main(void) {
std::cout << "Hello " << JoinWithCommas(constants::names) << "!" << std::endl;
return 0;
}
22 changes: 22 additions & 0 deletions example-projects/cpp/scripts/generate_constants.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

set -euo pipefail

mkdir -p gen

cat >gen/constants.h <<EOF
#include <string>
#include <vector>
namespace constants {
static std::vector<std::string> names = {
EOF

for name in "$@"; do
echo " \"${name}\"," >> gen/constants.h
done

cat >>gen/constants.h <<EOF
};
}
EOF
2 changes: 2 additions & 0 deletions example-projects/java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.class
*.jar
22 changes: 22 additions & 0 deletions example-projects/java/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
run: com/example/Main.class
java -cp .:third_party/guava-33.2.1-jre.jar com.example.Main

clean:
find . -name '*.class' -exec rm {} \;
rm -rf third_party com/example/gen/Constants.java:

com/example/Main.class: com/example/gen/Constants.class com/example/fmt/Formatting.class
javac com/example/Main.java

com/example/gen/Constants.java: scripts/generate_constants.sh
./scripts/generate_constants.sh "Daniel" "Laura"

com/example/gen/Constants.class: com/example/gen/Constants.java
javac com/example/gen/Constants.java

com/example/fmt/Formatting.class: third_party/guava-33.2.1-jre.jar
javac -cp third_party/guava-33.2.1-jre.jar com/example/fmt/Formatting.java

third_party/guava-33.2.1-jre.jar:
mkdir -p third_party
curl -o third_party/guava-33.2.1-jre.jar https://repo1.maven.org/maven2/com/google/guava/guava/33.2.1-jre/guava-33.2.1-jre.jar
10 changes: 10 additions & 0 deletions example-projects/java/com/example/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example;

import com.example.fmt.Formatting;
import com.example.gen.Constants;

class Main {
public static void main(String[] args) {
System.out.printf("Hello %s!%n", Formatting.joinWithCommas(Constants.names()));
}
}
9 changes: 9 additions & 0 deletions example-projects/java/com/example/fmt/Formatting.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.fmt;

import com.google.common.base.Joiner;

public class Formatting {
public static String joinWithCommas(Iterable<String> names) {
return Joiner.on(", ").join(names);
}
}
12 changes: 12 additions & 0 deletions example-projects/java/com/example/gen/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.gen;

import java.util.ArrayList;

public class Constants {
public static Iterable<String> names() {
ArrayList<String> names = new ArrayList<>();
names.add("Daniel");
names.add("Laura");
return names;
}
}
25 changes: 25 additions & 0 deletions example-projects/java/scripts/generate_constants.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash

set -euo pipefail

mkdir -p com/example/gen

cat >com/example/gen/Constants.java <<EOF
package com.example.gen;
import java.util.ArrayList;
public class Constants {
public static Iterable<String> names() {
ArrayList<String> names = new ArrayList<>();
EOF

for name in "$@"; do
echo " names.add(\"${name}\");" >>com/example/gen/Constants.java
done

cat >>com/example/gen/Constants.java <<EOF
return names;
}
}
EOF
56 changes: 56 additions & 0 deletions primers/build-systems/01-what-is-a-build-system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
+++
title="1. What is a build system?"
+++

# 1. What is a build system?

## What is building software?

"Building software" can refer to many things. One example is a software engineer may describe themself as "building software" when they write code.

In this primer we're specifically concerned with one idea of building software: Converting some input file (typically source code) into some useful output (often executables or bundles ready to run or deploy, or test results). Most programming languages have some kind of build process, but different languages require more or less building.

If you have written complex JavaScript, you have probably seen `package.json` files, and interacted with `npm` or `yarn`. These are tools used to build JavaScript. Here is an example `package.json` file:

```json
{
"name": "fancy-project",
"type": "module",
"scripts": {
"build": "mkdir -p dist && webpack",
"test": "jest test"
},
"dependencies": {
"dotenv": "^16.4.5"
},
"devDependencies": {
"jest": "^29.7.0",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
}
}
```

Some of the actions these tools are used to do are:
* Fetch all of the dependencies a project needs into a `node_modules` folder.
* Run all of the tests in a project.
* Combine a source file and everything it imports into a single file, and minify that file.

These kind of actions are all related to building the software. Building software is concerned with analysing dependencies between different pieces of code, and running some commands to take source code and convert it to something useful.

## What is a build tool?

A "build system" or "build tool" is a tool which is aware of the relationships between different pieces of code, and performs actions to transform code from one form to another. A good build tool is **correct** (it always produces the correct output when you run it) and **fast** (i.e. it performs its actions as quickly as possible). We will use the terms "build tool" and "build system" interchangeably.

## Why do we automate builds?

A lot of the time we could perform the actions a build tool performs ourselves manually.

To fetch dependencies, we could look in a `package.json` file and, for each dependency, work out what URL can be used to fetch it, download that URL, and unpack the files into the correctly named directory in the `node_modules` directory. And then do the same for each of its dependencies too.

To run tests, we could manually find the `jest` tool in our `node_modules` directory, and run it to run our tests.

We automate these processes with build tools for a few reasons:
1. It avoids us needing to know things. What URL can a dependency be found at? What should its directory in `node_modules` be named? While we could find these things out, the build tool knows them, so we don't need to.
2. It avoids us needing to work out the order we need to do things. The build tool knows which actions need to happen before which other actions, and will make sure they're done in the right order.
3. It avoids us needing to think about what's already been done, and what needs to be re-done. Imagine we had already manually downloaded `dotenv` and `jest`. Then we changed the version of `dotenv` in the `package.json`. We can delete and re-download both `dotenv` and `jest` (which ensures we're doing the _correct_ thing), but this would be slower than it could be. If the version of `jest` hasn't changed, maybe we don't need to delete it and download it again. By just leaving the downloaded `jest` as-is, we can be _faster_. But manually analysing what we can skip and what we need to re-do is complicated and error-prone (what if changing the version of `dotenv` actually _does_ mean we need to re-download `jest`? Skipping it would mean our build was not _correct_!)
Loading

0 comments on commit e5c549c

Please sign in to comment.