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;
|
|
|
|
}
|