Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion blcu-programming/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/.venv
/.venv-build
build/
dist/
/.vscode
*.pyc
*__pycache__/
Expand All @@ -7,4 +10,4 @@
.idea/
containers/*/.venv
.env
*.log
*.log
14 changes: 14 additions & 0 deletions blcu-programming/api/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
from datetime import datetime, timezone
from pathlib import Path

import uvicorn
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field

Expand Down Expand Up @@ -90,3 +92,15 @@ def get_logs(tail: int = Query(default=200, ge=1, le=5000)) -> dict:
"lines": selected_lines,
"line_count": len(selected_lines),
}


def main() -> None:
host = os.environ.get("BLCU_API_HOST", "127.0.0.1")
port = int(os.environ.get("BLCU_API_PORT", "8000"))
log_level = os.environ.get("BLCU_API_LOG_LEVEL", "info")

uvicorn.run(app, host=host, port=port, log_level=log_level)


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions blcu-programming/requirements-build.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-r requirements.txt
pyinstaller==6.17.0
2 changes: 2 additions & 0 deletions blcu-programming/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fastapi==0.124.2
uvicorn==0.38.0
13 changes: 11 additions & 2 deletions electron-app/BUILD.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
# Hyperloop Control Station Build System

The project uses a unified, modular build script (`electron-app/build.mjs`) to handle building the backend (Go), and frontends (React/Vite) for the Electron application.
The project uses a unified, modular build script (`electron-app/build.mjs`) to handle building the backend (Go), the BLCU programming API (Python/PyInstaller), and frontends (React/Vite) for the Electron application.

## Prerequisites

- **Node.js** & **pnpm**
- **Go** (1.21+)
- **Python 3** (for building the BLCU programming API executable)

## Basic Usage

Run the build script from the `electron-app` directory (or via npm scripts).

```sh
# Build EVERYTHING (Backend, Frontends)
# Build EVERYTHING (Backend, BLCU API, Frontends)
pnpm build

# OR
Expand All @@ -31,6 +32,9 @@ You can build individual components by passing their flag.
# Build only the Backend
node build.mjs --backend

# Build only the BLCU programming API executable
node build.mjs --blcu-programming

# Build only the Testing View
node build.mjs --testing-view
```
Expand All @@ -43,10 +47,15 @@ By default, the script builds for all defined platforms (Windows, Linux, macOS).
# Build backend for Windows only
node build.mjs --backend --win

# Build BLCU API for the current Windows host
node build.mjs --blcu-programming --win

# Build everything for Linux
node build.mjs --linux
```

The BLCU programming API is packaged with PyInstaller and cannot be cross-compiled. Build it on the same OS as the Electron release target.

## Advanced: Overwriting Commands

The build script allows you to override configuration properties on the fly. This is useful for CI pipelines where you might want to use different build commands or flags.
Expand Down
7 changes: 4 additions & 3 deletions electron-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The main Electron application that provides the control interface for the Hyperl

## Overview

Desktop application built with Electron that manages the Hyperloop pod control system. Handles backend process management, configuration, and provides multiple frontend views for competition and testing.
Desktop application built with Electron that manages the Hyperloop pod control system. Handles backend process management, BLCU programming API process management, configuration, and provides multiple frontend views for competition and testing.

## Project Structure

Expand All @@ -22,7 +22,7 @@ When running in development mode (unpackaged), the application creates temporary

- `config.toml.backup-{timestamp}` - Automatic backup files created when importing a configuration. These timestamped backups help recover previous configurations if needed.

- `binaries/` - Directory containing compiled backend executables for your platform. These are generated during the build process, when running `pnpm run build`.
- `binaries/` - Directory containing compiled backend and BLCU programming executables for your platform. These are generated during the build process, when running `pnpm run build`.

- `renderer/` - Directory containing built frontend views (control-station, ethernet-view). These are generated during the build process, when running `pnpm run build`.

Expand Down Expand Up @@ -56,7 +56,7 @@ Typical locations:
# Install dependencies
pnpm install

# Build backend and frontends
# Build backend, BLCU programming API, and frontends
pnpm run build

# Run in development mode (you MUST run `pnpm run build` BEFORE!)
Expand Down Expand Up @@ -99,6 +99,7 @@ sudo ifconfig lo0 alias 127.0.0.9 up
## Architecture

- **Backend Process**: Go backend for data processing
- **BLCU Programming Process**: Packaged FastAPI/TFTP API for firmware transfers
- **Packet Sender**: Tool for sending test packets
- **Configuration**: TOML-based config management
- **Views**: Multiple frontend interfaces (Competition/Testing)
Expand Down
111 changes: 111 additions & 0 deletions electron-app/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ const CONFIG = {
},
],
},
"blcu-programming": {
type: "python",
path: join(ROOT, "blcu-programming"),
output: join(__dirname, "binaries"),
entry: join(ROOT, "blcu-programming", "api", "main.py"),
requirements: join(ROOT, "blcu-programming", "requirements-build.txt"),
venv: join(ROOT, "blcu-programming", ".venv-build"),
},
"testing-view": {
type: "frontend",
path: join(ROOT, "frontend/testing-view"),
Expand Down Expand Up @@ -127,6 +135,107 @@ const buildGo = (name, config, requestedPlatforms, extraArgs = "") => {
return success;
};

const getVenvPython = (venvPath) => {
const binDir = process.platform === "win32" ? "Scripts" : "bin";
const executable = process.platform === "win32" ? "python.exe" : "python";
return join(venvPath, binDir, executable);
};

const createPythonVenv = (venvPath, cwd) => {
if (existsSync(getVenvPython(venvPath))) return true;

const python = process.env.PYTHON || "python";

logger.step(`Creating Python build venv with ${python}...`);
return run(`${python} -m venv "${venvPath}"`, cwd);
};

const getPythonTarget = () => {
const goos =
{ win32: "windows", darwin: "darwin", linux: "linux" }[process.platform] ||
process.platform;
const goarch = { x64: "amd64", arm64: "arm64" }[process.arch] || process.arch;
const platformTag =
{ win32: "win", darwin: "mac", linux: "linux" }[process.platform] ||
process.platform;

return {
binarySuffix: `${goos}-${goarch}${process.platform === "win32" ? ".exe" : ""}`,
label: `${goos}/${goarch}`,
platformTag,
};
};

const buildPython = (name, config, requestedPlatforms) => {
const target = getPythonTarget();
const targetRequested =
requestedPlatforms.length === 0 ||
requestedPlatforms.includes("all") ||
requestedPlatforms.includes(target.platformTag);

if (!targetRequested) {
logger.error(
`${name} must be built on the target OS because PyInstaller cannot cross-compile. Current host is ${target.label}.`,
);
return false;
}

logger.info(`Building ${name} (Python/PyInstaller)...`);
mkdirSync(config.output, { recursive: true });

if (!createPythonVenv(config.venv, config.path)) return false;

const pythonBin = getVenvPython(config.venv);

if (
!run(
`"${pythonBin}" -m pip install -r "${config.requirements}"`,
config.path,
)
) {
return false;
}

const binaryName = `${name}-${target.binarySuffix}`;
const binaryBaseName = binaryName.replace(/\.exe$/, "");
const binaryPath = join(config.output, binaryName);
const pyinstallerWorkPath = join(config.path, "build", "pyinstaller");
const pyinstallerSpecPath = join(config.path, "build");

if (existsSync(binaryPath)) {
try {
rmSync(binaryPath, { force: true });
} catch (error) {
if (error.code === "EPERM" || error.code === "EACCES") {
logger.error(
`Could not replace ${binaryPath}. Stop Electron or the running BLCU programming process, then build again.`,
);
return false;
}

throw error;
}
}

return run(
[
`"${pythonBin}" -m PyInstaller`,
"--clean",
"--noconfirm",
"--onefile",
`--name "${binaryBaseName}"`,
`--distpath "${config.output}"`,
`--workpath "${pyinstallerWorkPath}"`,
`--specpath "${pyinstallerSpecPath}"`,
"--hidden-import uvicorn.loops.auto",
"--hidden-import uvicorn.protocols.http.auto",
"--hidden-import uvicorn.lifespan.on",
`"${config.entry}"`,
].join(" "),
config.path,
);
};

const buildFrontend = (name, config, extraArgs = "") => {
if (config.optional && !existsSync(join(config.path, "package.json"))) {
logger.warning(`Skipping ${name} (not initialized)`);
Expand Down Expand Up @@ -212,6 +321,8 @@ logger.header("Hyperloop Control Station Build");

if (config.type === "go") {
success = buildGo(key, config, requestedPlatforms, extraArgs);
} else if (config.type === "python") {
success = buildPython(key, config, requestedPlatforms);
} else if (config.type === "frontend") {
success = buildFrontend(key, config, extraArgs);
if (success && !config.optional) frontendBuilt = true;
Expand Down
8 changes: 7 additions & 1 deletion electron-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
"build:backend:win": "node build.mjs --backend --win",
"build:backend:linux": "node build.mjs --backend --linux",
"build:backend:mac": "node build.mjs --backend --mac",
"build:blcu": "node build.mjs --blcu-programming",
"build:blcu:win": "node build.mjs --blcu-programming --win",
"build:blcu:linux": "node build.mjs --blcu-programming --linux",
"build:blcu:mac": "node build.mjs --blcu-programming --mac",
"build:testing": "node build.mjs --testing-view",
"build:competition": "node build.mjs --competition-view",
"asar:win": "asar list dist/win-unpacked/resources/app.asar | findstr /V node_modules",
Expand Down Expand Up @@ -112,7 +116,9 @@
"icon": "icons/512x512.png",
"category": "Utility",
"artifactName": "${productName}-${version}-linux-${arch}.${ext}",
"executableArgs": ["--no-sandbox"]
"executableArgs": [
"--no-sandbox"
]
}
}
}
Loading
Loading