Gymboard/build-apps.d

163 lines
4.7 KiB
D
Raw Permalink Normal View History

2023-04-02 10:05:07 +00:00
#!/usr/bin/env rdmd
/**
* A simple build script that builds all Gymboard applications and collects
* build artifacts for deployment. You can run it with `./build_apps.d`, and it
* will build each application for production deployment using a separate
* thread.
*
* Supply the "clean" command to remove all build artifacts instead of building.
2023-05-17 09:38:26 +00:00
*
* Eventually, this will be migrated to gymboard-cli.
2023-04-02 10:05:07 +00:00
*/
module build_apps;
import std.stdio;
import std.process;
import std.file;
import std.path;
import std.regex;
import core.thread;
enum BUILDS_DIR = "build";
int main(string[] args) {
if (args.length > 1 && args[1] == "clean") {
2023-05-17 09:38:26 +00:00
writeln("Cleaning builds");
if (exists(BUILDS_DIR)) rmdirRecurse(BUILDS_DIR);
2023-04-02 10:05:07 +00:00
return 0;
}
Thread[] buildThreads = [
runBuildInThread(API_BUILD_SPEC),
runBuildInThread(CDN_BUILD_SPEC),
runBuildInThread(SEARCH_BUILD_SPEC),
runBuildInThread(APP_BUILD_SPEC),
runBuildInThread(UPLOADS_BUILD_SPEC)
];
foreach (t; buildThreads) t.join();
return 0;
}
/**
* Specification for building an application.
*/
struct BuildSpec {
string name;
string workingDir;
string buildCommand;
typeof(ctRegex!(`\d`)) artifactRegex;
}
static immutable API_BUILD_SPEC = BuildSpec(
"api",
"gymboard-api",
"./mvnw clean package spring-boot:repackage",
ctRegex!(`target/gymboard-api-.*\.jar$`)
);
static immutable CDN_BUILD_SPEC = BuildSpec(
"cdn",
"gymboard-cdn",
"./mvnw clean package spring-boot:repackage",
ctRegex!(`target/gymboard-cdn-.*\.jar$`)
);
static immutable SEARCH_BUILD_SPEC = BuildSpec(
"search",
"gymboard-search",
"./mvnw clean package spring-boot:repackage -DskipTests=true",
ctRegex!(`target/gymboard-search-.*\.jar$`)
);
static immutable APP_BUILD_SPEC = BuildSpec(
"app",
"gymboard-app",
"quasar build",
ctRegex!(`gymboard-app/dist$`)
);
static immutable UPLOADS_BUILD_SPEC = BuildSpec(
"uploads",
"gymboard-uploads",
"dub build --build=release",
ctRegex!(`gymboard-uploads/gymboard-uploads$`)
);
/**
* Runs a build and returns its exit code.
* Params:
* spec = The build spec to run.
* Returns: The exit code of the build. 0 indicates success.
*/
int runBuild(const BuildSpec spec) {
string buildDir = buildPath(BUILDS_DIR, spec.name);
if (exists(buildDir)) rmdirRecurse(buildDir);
mkdirRecurse(buildDir);
2023-05-17 09:38:26 +00:00
const logFilePath = buildPath(buildDir, "build.log");
const errorLogFilePath = buildPath(buildDir, "build-error.log");
File buildLogFile = File(logFilePath, "w");
File buildErrorLogFile = File(errorLogFilePath, "w");
2023-04-02 10:05:07 +00:00
Pid pid = spawnShell(
spec.buildCommand,
std.stdio.stdin,
buildLogFile,
buildErrorLogFile,
null,
Config.none,
spec.workingDir,
nativeShell()
);
int result = wait(pid);
2023-05-17 09:38:26 +00:00
if (result != 0) {
writefln!"Build command failed for build \"%s\". Check %s for more info."(spec.name, errorLogFilePath);
return result;
}
// Clean up unused log files.
if (getSize(logFilePath) == 0) std.file.remove(logFilePath);
if (getSize(errorLogFilePath) == 0) std.file.remove(errorLogFilePath);
2023-04-02 10:05:07 +00:00
// Find and extract artifacts.
bool artifactFound = false;
foreach (DirEntry entry; dirEntries(spec.workingDir, SpanMode.breadth, false)) {
Captures!string c = matchFirst(entry.name, spec.artifactRegex);
if (!c.empty) {
artifactFound = true;
string destFilename = buildPath(buildDir, baseName(entry.name));
writefln!"Copying artifact %s to %s"(entry.name, destFilename);
if (entry.isFile) {
writefln!" Filesize: %d"(entry.size);
copy(entry.name, destFilename);
} else if (entry.isDir) {
Pid cpPid = spawnShell("cp -R " ~ entry.name ~ " " ~ destFilename);
int cpResult = wait(cpPid);
if (cpResult != 0) {
writefln!"Failed to copy directory: %d"(cpResult);
return cpResult;
}
}
}
}
if (!artifactFound) {
writefln!"Warning: Build %s completed successfully, but no matching artifacts were found."(spec.name);
}
return 0;
}
/**
* Wraps building of a build spec in a thread.
* Params:
* spec = The spec to build.
* Returns: The thread running the build, which is already started.
*/
Thread runBuildInThread(const BuildSpec spec) {
Thread t = new Thread(() {
writefln!"Building \"%s\" in %s"(spec.name, spec.workingDir);
int result = runBuild(spec);
writefln!"Build \"%s\" exited with code %d."(spec.name, result);
});
t.start();
return t;
}