Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start of build systems primer #227

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading