GymPal/build.d

172 lines
4.5 KiB
D
Raw Normal View History

2022-12-09 10:41:13 +00:00
#!/usr/bin/env rdmd
/**
* This module is responsible for building the project.
*/
module build;
import std.stdio;
import std.file;
import std.string;
import std.conv;
import std.path;
2022-12-12 12:31:21 +00:00
import std.digest.md;
import std.base64;
2022-12-09 10:41:13 +00:00
const string MCU_ID = "atmega328p";
const ulong CPU_FREQ = 16_000_000;
const string BOOTLOADER = "arduino";
const ulong AVRDUDE_BAUDRATE = 57_600;
const string SOURCE_DIR = "src";
const string BUILD_DIR = "bin";
const string[] COMPILER_FLAGS = [
"-Wall",
"-Os"
];
int main(string[] args) {
2022-12-09 10:44:27 +00:00
if (args.length < 2) {
return build();
}
string command = args[1].strip.toLower;
2022-12-12 12:31:21 +00:00
if (command == "flash") {
build().quitIfNonZero();
return flashToMCU(buildPath("bin", "gympal.hex"));
}
if (command == "build") return build(true);
2022-12-09 10:44:27 +00:00
if (command == "clean") return clean();
writefln!"Unknown command: \"%s\"."(command);
return 0;
}
int clean() {
rmdirRecurse(BUILD_DIR);
2022-12-09 10:41:13 +00:00
return 0;
}
2022-12-12 12:31:21 +00:00
int build(bool force = false) {
2022-12-09 10:41:13 +00:00
if (!exists(BUILD_DIR)) mkdir(BUILD_DIR);
string[] sources = findFiles(SOURCE_DIR, ".c");
string[] objects;
objects.reserve(sources.length);
foreach (source; sources) {
2022-12-12 12:31:21 +00:00
objects ~= compileSourceToObject(source, force);
2022-12-09 10:41:13 +00:00
}
string elfFile = linkObjects(objects);
string hexFile = copyToHex(elfFile);
writefln!"Built %s"(hexFile);
2022-12-12 12:31:21 +00:00
runOrQuit("avr-size " ~ hexFile);
2022-12-09 10:41:13 +00:00
return 0;
}
//-------- Utility functions below here -------
int run(string shellCommand) {
import std.process : Pid, spawnShell, wait;
Pid pid = spawnShell(shellCommand);
return wait(pid);
}
void runOrQuit(string shellCommand, int[] successExitCodes = [0]) {
import core.stdc.stdlib : exit;
import std.algorithm : canFind;
int result = run(shellCommand);
if (!canFind(successExitCodes, result)) exit(result);
}
2022-12-12 12:31:21 +00:00
void quitIfNonZero(int n) {
import core.stdc.stdlib : exit;
if (n != 0) exit(n);
}
bool shouldCompileSource(string sourcePath) {
string name = baseName(sourcePath);
name = name[0 .. name.lastIndexOf('.')];
string hashPath = buildPath("bin", "hash", name ~ ".md5");
string objectPath = buildPath("bin", name ~ ".o");
if (!exists(hashPath) || !exists(objectPath)) return true;
ubyte[] storedHash = Base64.decode(readText(hashPath).strip());
ubyte[16] currentHash = md5Of(readText(sourcePath));
return storedHash != currentHash;
}
string compileSourceToObject(string sourcePath, bool force = false) {
2022-12-09 10:41:13 +00:00
string flags = join(COMPILER_FLAGS, " ");
string name = baseName(sourcePath);
name = name[0 .. name.lastIndexOf('.')];
string objectPath = buildPath("bin", name ~ ".o");
2022-12-12 12:31:21 +00:00
string hashPath = buildPath("bin", "hash", name ~ ".md5");
if (!force && !shouldCompileSource(sourcePath)) {
writefln!"Not compiling %s because no changes detected."(sourcePath);
return objectPath;
}
2022-12-09 10:41:13 +00:00
string cmd = format!"avr-gcc %s -DF_CPU=%dUL -mmcu=%s -c -o %s %s"(
flags,
CPU_FREQ,
MCU_ID,
objectPath,
sourcePath
);
writeln(cmd);
runOrQuit(cmd);
2022-12-12 12:31:21 +00:00
ubyte[16] hash = md5Of(readText(sourcePath));
string hashDir = buildPath("bin", "hash");
if (!exists(hashDir)) mkdir(hashDir);
std.file.write(hashPath, Base64.encode(hash));
2022-12-09 10:41:13 +00:00
return objectPath;
}
string linkObjects(string[] objectPaths) {
string objectsArg = join(objectPaths, " ");
string flags = join(COMPILER_FLAGS, " ");
string elfFile = buildPath(BUILD_DIR, "gympal.elf");
string cmd = format!"avr-gcc %s -o %s %s"(
flags,
elfFile,
objectsArg
);
writeln(cmd);
runOrQuit(cmd);
return elfFile;
}
string copyToHex(string elfFile) {
string hexFile = buildPath(BUILD_DIR, "gympal.hex");
string cmd = format!"avr-objcopy -O ihex -R .eeprom %s %s"(
elfFile,
hexFile
);
writeln(cmd);
runOrQuit(cmd);
return hexFile;
}
int flashToMCU(string hexFile) {
string cmd = format!"avrdude -c %s -p %s -P /dev/ttyUSB0 -b %d flash:w:%s:i"(
BOOTLOADER,
MCU_ID,
AVRDUDE_BAUDRATE,
hexFile
);
writeln(cmd);
return run(cmd);
}
string[] findFiles(string path, string suffix = null) {
import std.array;
import std.algorithm;
auto app = appender!(string[]);
foreach (DirEntry entry; dirEntries(path, SpanMode.breadth, false)) {
if (entry.isFile && (suffix is null || entry.name.endsWith(suffix))) {
app ~= entry.name;
}
}
return app[];
}