progress bar on builds (fuck you golang)
This commit is contained in:
@@ -33,6 +33,35 @@ function resolveClientModuleDir(rootDir: string): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function estimateBuildPackageCount(clientDir: string): Promise<number> {
|
||||||
|
try {
|
||||||
|
const result = await $`go list -deps -f '{{if not .Standard}}{{.ImportPath}}{{end}}' ./cmd/agent`
|
||||||
|
.cwd(clientDir)
|
||||||
|
.quiet()
|
||||||
|
.nothrow();
|
||||||
|
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packages = result.stdout
|
||||||
|
.toString()
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line.length > 0);
|
||||||
|
|
||||||
|
return new Set(packages).size;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProgressBar(percent: number, width = 24): string {
|
||||||
|
const clamped = Math.max(0, Math.min(100, percent));
|
||||||
|
const filled = Math.round((clamped / 100) * width);
|
||||||
|
return `[${"#".repeat(filled)}${"-".repeat(Math.max(0, width - filled))}]`;
|
||||||
|
}
|
||||||
|
|
||||||
type BuildProcessConfig = {
|
type BuildProcessConfig = {
|
||||||
platforms: string[];
|
platforms: string[];
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
@@ -245,35 +274,150 @@ export async function startBuildProcess(
|
|||||||
sendToStream({ type: "output", text: "Client printing disabled (noprint tag)\n", level: "info" });
|
sendToStream({ type: "output", text: "Client printing disabled (noprint tag)\n", level: "info" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalPackages = await estimateBuildPackageCount(clientDir);
|
||||||
|
if (totalPackages > 0) {
|
||||||
|
sendToStream({
|
||||||
|
type: "output",
|
||||||
|
text: `Estimated compile units: ${totalPackages} packages\n`,
|
||||||
|
level: "info",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buildTool = config.obfuscate ? "garble" : "go";
|
const buildTool = config.obfuscate ? "garble" : "go";
|
||||||
|
const verboseArg = "-x -v ";
|
||||||
const tagArg = config.noPrinting ? "-tags noprint " : "";
|
const tagArg = config.noPrinting ? "-tags noprint " : "";
|
||||||
logger.info(`[build:${buildId.substring(0, 8)}] Building: ${buildTool} build ${tagArg}${ldflags ? `-ldflags="${ldflags}" ` : ""}-o ${outDir}/${outputName} ./cmd/agent`);
|
logger.info(`[build:${buildId.substring(0, 8)}] Building: ${buildTool} build ${verboseArg}${tagArg}${ldflags ? `-ldflags="${ldflags}" ` : ""}-o ${outDir}/${outputName} ./cmd/agent`);
|
||||||
logger.info(`[build:${buildId.substring(0, 8)}] Environment: GOOS=${os} GOARCH=${actualArch} CGO_ENABLED=${env.CGO_ENABLED} CC=${env.CC || "<default>"}`);
|
logger.info(`[build:${buildId.substring(0, 8)}] Environment: GOOS=${os} GOARCH=${actualArch} CGO_ENABLED=${env.CGO_ENABLED} CC=${env.CC || "<default>"}`);
|
||||||
|
sendToStream({ type: "output", text: "Build trace enabled (-x -v)\n", level: "info" });
|
||||||
|
|
||||||
|
const planCmd = config.obfuscate
|
||||||
|
? (config.noPrinting
|
||||||
|
? (ldflags
|
||||||
|
? $`garble build -n -tags noprint -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
||||||
|
: $`garble build -n -tags noprint -o ${outDir}/${outputName} ./cmd/agent`)
|
||||||
|
: (ldflags
|
||||||
|
? $`garble build -n -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
||||||
|
: $`garble build -n -o ${outDir}/${outputName} ./cmd/agent`))
|
||||||
|
: (config.noPrinting
|
||||||
|
? (ldflags
|
||||||
|
? $`go build -n -tags noprint -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
||||||
|
: $`go build -n -tags noprint -o ${outDir}/${outputName} ./cmd/agent`)
|
||||||
|
: (ldflags
|
||||||
|
? $`go build -n -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
||||||
|
: $`go build -n -o ${outDir}/${outputName} ./cmd/agent`));
|
||||||
|
|
||||||
|
const planResult = await planCmd.env(env).cwd(clientDir).quiet().nothrow();
|
||||||
|
const plannedSteps = planResult.stdout
|
||||||
|
.toString()
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line.length > 0).length;
|
||||||
|
|
||||||
|
if (plannedSteps > 0) {
|
||||||
|
sendToStream({
|
||||||
|
type: "output",
|
||||||
|
text: `Planned build steps (-n): ${plannedSteps}\n`,
|
||||||
|
level: "info",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const buildCmd = config.obfuscate
|
const buildCmd = config.obfuscate
|
||||||
? (config.noPrinting
|
? (config.noPrinting
|
||||||
? (ldflags
|
? (ldflags
|
||||||
? $`garble build -tags noprint -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
? $`garble build -x -v -tags noprint -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
||||||
: $`garble build -tags noprint -o ${outDir}/${outputName} ./cmd/agent`)
|
: $`garble build -x -v -tags noprint -o ${outDir}/${outputName} ./cmd/agent`)
|
||||||
: (ldflags
|
: (ldflags
|
||||||
? $`garble build -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
? $`garble build -x -v -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
||||||
: $`garble build -o ${outDir}/${outputName} ./cmd/agent`))
|
: $`garble build -x -v -o ${outDir}/${outputName} ./cmd/agent`))
|
||||||
: (config.noPrinting
|
: (config.noPrinting
|
||||||
? (ldflags
|
? (ldflags
|
||||||
? $`go build -tags noprint -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
? $`go build -x -v -tags noprint -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
||||||
: $`go build -tags noprint -o ${outDir}/${outputName} ./cmd/agent`)
|
: $`go build -x -v -tags noprint -o ${outDir}/${outputName} ./cmd/agent`)
|
||||||
: (ldflags
|
: (ldflags
|
||||||
? $`go build -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
? $`go build -x -v -ldflags=${ldflags} -o ${outDir}/${outputName} ./cmd/agent`
|
||||||
: $`go build -o ${outDir}/${outputName} ./cmd/agent`));
|
: $`go build -x -v -o ${outDir}/${outputName} ./cmd/agent`));
|
||||||
|
|
||||||
const proc = buildCmd.env(env).cwd(clientDir).nothrow();
|
const proc = buildCmd.env(env).cwd(clientDir).nothrow();
|
||||||
|
|
||||||
|
const compiledPackages = new Set<string>();
|
||||||
|
const packageLinePattern = /^[A-Za-z0-9._~\-]+(?:\/[A-Za-z0-9._~\-]+)+$/;
|
||||||
|
let executedSteps = 0;
|
||||||
|
let lastBarPercent = -1;
|
||||||
|
const phaseStartedAt = Date.now();
|
||||||
|
|
||||||
|
const emitBuildProgressBar = (force = false) => {
|
||||||
|
const packagePercent =
|
||||||
|
totalPackages > 0
|
||||||
|
? Math.floor((compiledPackages.size / Math.max(1, totalPackages)) * 100)
|
||||||
|
: -1;
|
||||||
|
const stepPercent =
|
||||||
|
plannedSteps > 0
|
||||||
|
? Math.floor((executedSteps / Math.max(1, plannedSteps)) * 100)
|
||||||
|
: -1;
|
||||||
|
|
||||||
|
let percent = Math.max(packagePercent, stepPercent);
|
||||||
|
if (percent < 0) {
|
||||||
|
const elapsedMs = Date.now() - phaseStartedAt;
|
||||||
|
percent = Math.min(90, Math.floor(elapsedMs / 1500));
|
||||||
|
}
|
||||||
|
|
||||||
|
percent = Math.max(0, Math.min(99, percent));
|
||||||
|
if (!force && percent === lastBarPercent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastBarPercent = percent;
|
||||||
|
|
||||||
|
let detail = "working";
|
||||||
|
if (totalPackages > 0) {
|
||||||
|
detail = `pkgs ${Math.min(compiledPackages.size, totalPackages)}/${totalPackages}`;
|
||||||
|
} else if (plannedSteps > 0) {
|
||||||
|
detail = `steps ${Math.min(executedSteps, plannedSteps)}/${plannedSteps}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToStream({
|
||||||
|
type: "status",
|
||||||
|
text: `Compiling ${platform}: ${renderProgressBar(percent)} ${percent}% (${detail})`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
emitBuildProgressBar(true);
|
||||||
|
const progressBarTimer = setInterval(() => emitBuildProgressBar(false), 1000);
|
||||||
|
let result: any;
|
||||||
|
try {
|
||||||
for await (const line of proc.lines()) {
|
for await (const line of proc.lines()) {
|
||||||
sendToStream({ type: "output", text: line + "\n", level: "info" });
|
sendToStream({ type: "output", text: line + "\n", level: "info" });
|
||||||
|
|
||||||
|
if (line.trim().length > 0) {
|
||||||
|
executedSteps += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (packageLinePattern.test(trimmed) && !compiledPackages.has(trimmed)) {
|
||||||
|
compiledPackages.add(trimmed);
|
||||||
|
|
||||||
|
if (totalPackages > 0) {
|
||||||
|
const percent = Math.min(99, Math.floor((compiledPackages.size / totalPackages) * 100));
|
||||||
|
sendToStream({
|
||||||
|
type: "status",
|
||||||
|
text: `Compiling ${platform}: ${compiledPackages.size}/${totalPackages} packages (${percent}%)`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sendToStream({
|
||||||
|
type: "status",
|
||||||
|
text: `Compiling ${platform}: ${compiledPackages.size} packages`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitBuildProgressBar(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await proc;
|
||||||
|
} finally {
|
||||||
|
clearInterval(progressBarTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await proc;
|
|
||||||
logger.info(`[build:${buildId.substring(0, 8)}] Process exited with code: ${result.exitCode}`);
|
logger.info(`[build:${buildId.substring(0, 8)}] Process exited with code: ${result.exitCode}`);
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
@@ -286,6 +430,11 @@ export async function startBuildProcess(
|
|||||||
throw new Error(`Build failed for ${platform}`);
|
throw new Error(`Build failed for ${platform}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendToStream({
|
||||||
|
type: "status",
|
||||||
|
text: `Compiling ${platform}: ${renderProgressBar(100)} 100% (complete)`,
|
||||||
|
});
|
||||||
|
|
||||||
const filePath = `${outDir}/${outputName}`;
|
const filePath = `${outDir}/${outputName}`;
|
||||||
const file = Bun.file(filePath);
|
const file = Bun.file(filePath);
|
||||||
const size = file.size;
|
const size = file.size;
|
||||||
@@ -297,7 +446,7 @@ export async function startBuildProcess(
|
|||||||
size,
|
size,
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMsg = `Γ£ù Failed to build ${platform}: ${err.message || err}\n`;
|
const errorMsg = `[ERROR] Failed to build ${platform}: ${err.message || err}\n`;
|
||||||
logger.error(`[build:${buildId.substring(0, 8)}] ${errorMsg.trim()}`);
|
logger.error(`[build:${buildId.substring(0, 8)}] ${errorMsg.trim()}`);
|
||||||
sendToStream({ type: "output", text: errorMsg, level: "error" });
|
sendToStream({ type: "output", text: errorMsg, level: "error" });
|
||||||
throw err;
|
throw err;
|
||||||
@@ -306,7 +455,7 @@ export async function startBuildProcess(
|
|||||||
|
|
||||||
build.status = "completed";
|
build.status = "completed";
|
||||||
logger.info(`[build:${buildId.substring(0, 8)}] Build completed successfully! Built ${build.files.length} file(s)`);
|
logger.info(`[build:${buildId.substring(0, 8)}] Build completed successfully! Built ${build.files.length} file(s)`);
|
||||||
sendToStream({ type: "output", text: `\nΓ£ô Build completed successfully!\n`, level: "success" });
|
sendToStream({ type: "output", text: `\n[OK] Build completed successfully!\n`, level: "success" });
|
||||||
sendToStream({ type: "complete", success: true, files: build.files, buildId, expiresAt: build.expiresAt });
|
sendToStream({ type: "complete", success: true, files: build.files, buildId, expiresAt: build.expiresAt });
|
||||||
|
|
||||||
saveBuild({
|
saveBuild({
|
||||||
|
|||||||
Reference in New Issue
Block a user