Intro to Build Systems
What is a build system?
A build system is a program that produces all of the compiled code and resources needed to build your program.
A build system is not a compiler. The build system invokes a compiler on your source code to produce executable code.
A build system can exist outside the context of compiled languages. In these cases, the thing we call the "build system" is actually a package manager.
Examples of build systems
Java
- Gradle
- Maven
C/C++
- Cmake
- Make
- Autotools
Javascript
- Node package manager (npm)
- Yarn
Python
- Pip
Ruby
- Bundler
Rust
- Cargo
Compiled langs ("classic" build system)
Interpreted langs (package managers)
Hands-on
with Gradle
Install gradle at https://gradle.org/install/
Find complete example at github.com/nicholastmosher/gradle-starter
Create a new project with Gradle
$ gradle init
Starting a Gradle Daemon (subsequent builds will be faster)
BUILD SUCCESSFUL in 4s
2 actionable tasks: 2 executed
$ ls -la
drwxrwxr-x. 4 nick nick 4096 Jul 26 16:38 .
drwxr-xr-x. 56 nick nick 4096 Jul 26 16:38 ..
-rw-rw-r--. 1 nick nick 1172 Jul 26 16:38 build.gradle
drwxrwxr-x. 4 nick nick 4096 Jul 26 16:38 .gradle
drwxrwxr-x. 3 nick nick 4096 Jul 26 16:38 gradle
-rwxrwxr-x. 1 nick nick 5296 Jul 26 16:38 gradlew
-rw-rw-r--. 1 nick nick 2260 Jul 26 16:38 gradlew.bat
-rw-rw-r--. 1 nick nick 589 Jul 26 16:38 settings.gradle
After "gradle init", never use the global gradle command
From now on, use the gradle "wrapper" script: gradlew
$ ./gradlew build # on Unix
> gradlew.bat build # on Windows
Add source files
$ mkdir -p src/{main,test}/com/example
// src/main/com/example/Main.java
public class Main {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
// src/test/com/example/MainTest.java
import org.junit.Assert;
import org.junit.Test;
public class MainTest {
@Test
public void testTrue() {
Assert.assertTrue(true);
}
}
Add build configuration
// build.gradle
// Apply the java plugin to add support for Java
apply plugin: 'java'
apply plugin: 'application'
// In this section you declare where to find the dependencies of your project
repositories {
// Use 'jcenter' for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
}
// In this section you declare the dependencies for your production and test code
dependencies {
// Include JUnit as a dependency for tests
// JUnit will not be included in "production" builds
testImplementation 'junit:junit:4.12'
}
// Tell gradle where your main class is
mainClassName = "com.example.Main"
// Tell gradle where to find your source files
sourceSets {
main.java.srcDirs = ['src/main'] // Production code
test.java.srcDirs = ['src/test'] // Test code
}
./gradlew commands
$ ./gradlew run
:compileJava
:processResources NO-SOURCE
:classes
:run
Hello, world!
BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed
$ ./gradlew test
:compileJava
:processResources NO-SOURCE
:classes
:compileTestJava
:processTestResources NO-SOURCE
:testClasses
:test
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed
Use "./gradlew tasks" to see available commands
Adding dependencies with Gradle
Picocli: "A mighty tiny command line interface" https://picocli.info/
# build.gradle
dependencies {
// Picocli, a command-line parsing library
implementation 'info.picocli:picocli:4.0.1'
// ...
}
package com.example;
import picocli.CommandLine;
@CommandLine.Command(name = "greeter", description = "Say hello in many different ways!")
public class Main {
@CommandLine.Option(names = {"--name"}, description = "The name of whomst to greet!")
private String name = "World";
public static void main(String[] args) {
Main application = new Main();
CommandLine cmd = new CommandLine(application);
try {
cmd.parseArgs(args);
} catch (Exception e) {
cmd.usage(System.out);
return;
}
System.out.printf("Hello, %s!\n", application.name);
}
}
Trying our application
$ ./gradlew installDist
$ build/install/your-project-name/bin/your-project-name --name Bob
Hello, Bob!
$ build/install/your-project-name/bin/your-project-name
Hello, World!
$ build/install/your-project-name/bin/your-project-name --blah Wrong Arguments
Usage: greeter [--name=<name>]
Say hello in many different ways!
--name=<name> The name of whomst to greet!
The ./gradlew installDist command bundles up all of your
code and dependencies into Jar files and puts them into "build/install/your-project-name/lib/", then puts launcher scripts into "build/install/your-project-name/bin/"
Breaking it down
What parts of our example are Java-specific,
and what parts are general to all programming?
Some Definitions
- Repository: A catalog (typically online) which hosts code for download
- Source Repository (e.g. Github): A site from which to view and/or download source code
- Registry/Artifact Repository (e.g. Maven Central): A site from which to download compiled code
- Library: Code that is not executable. Libraries are reusable code that may be imported by other libraries or applications
- Application: Code that is executable. Applications may depend on one or many libraries
Some Definitions
- Package: Code that is distributed via a registry to be installed (applications) or linked to (libraries)
- Module: Code that is logically separated by purpose. One or more modules may live within a single package.
- Namespace: A collection of code items prefixed by a path. Useful for distinguishing items with the same name
-
In Java: com.typesafe.config.Config vs my.app.Config
-
In Rust: reqwest::Client vs mongodb::Client
-
Namespaces are important to understand
Rust-y pseudocode
enum CodeItem { Class, Function, Value }
struct Namespace {
items: Vec<CodeItem>,
}
struct Path(String)
type Program = HashMap<Path, Namespace>;
Think of code "items" as your top-level constructs.
These may be classes, functions, values, etc.
A namespace is a collection of items at a named path
Tying back to Java
this will get confusing
"Java packages" are the same thing as namespaces
Text
Intro to Build Systems
By Nick Mosher
Intro to Build Systems
- 253