LingSys
↓ 0 downloadsYour machine's memory, GPU, motherboard and network, drawn and played in the Ling design system: LingSys, a small build for USB liquid-cooler displays, LingNet for IPv4/IPv6, DNS and packets, and a LingOS edition. 64-bit and 32-bit Windows builds included.
Keywords system-monitor, visualization, network, ipv6, gpu, lcd, lingos, ling
lingfu install LingSysCode / Files (48)
tools/build_exe.py
#!/usr/bin/env python3
"""Build LingSys.exe, LingSysMini.exe and LingNet.exe for 64-bit and 32-bit Windows.
python tools/build_exe.py all three apps, 64-bit (dist/x64) and 32-bit (dist/x86)
python tools/build_exe.py mini just one app: full, mini or net
python tools/build_exe.py --x64 only 64-bit (or --x86 for only 32-bit)
Why not plain `ling build`? It makes an exe that runs the program on the Cranelift JIT, which in
Ling 2030.1.48 never frees lists or strings, so the program grows by gigabytes a minute. This script
generates the same kind of Rust project but calls the interpreter instead (flat memory, about 20 fps),
embeds the app's icon (assets/*.ico, made by tools/make_icons.py), and makes each exe self-contained:
it carries the samplers, writes them to %LOCALAPPDATA%\\LingSys, starts them hidden, and stops them on
exit. `sim` as an argument skips them.
Needs Rust (with the i686-pc-windows-msvc target for --x86) and the Ling source tree `ling` was built from
(set LING_HOME if it is not C:/Users/User/Programs/ling). The first build of each architecture compiles Ling
itself, which takes about six minutes; later ones only compile the small wrapper crate.
32-bit note: Ling's Windows window code has a literal (0x9000_0000 as isize) that does not fit a 32-bit
isize. It wraps to exactly the bit pattern intended, but rustc rejects it by default, so the 32-bit build
allows that one lint (-A overflowing_literals) rather than changing the Ling source.
32-bit note 2: the same window code imports SetWindowLongPtrW by name, which only exists in the 64-bit
user32.dll (on 32-bit Windows it is a header macro for SetWindowLongW, same arguments). A 32-bit exe built
as-is stops in the Windows loader with "entry point not found". After building, this script renames that one
import to SetWindowLongW inside the exe (patch_x86), which is what the macro does. The real fix belongs in
Ling's src/runtime/mod.rs (use SetWindowLongW when target_pointer_width = "32").
"""
import os, shutil, subprocess, sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
BUILD = ROOT / '.ling-build'
LING_HOME = Path(os.environ.get('LING_HOME', 'C:/Users/User/Programs/ling'))
APPS = { # key: (ling program, exe name, icon)
'full': ('hatchsys', 'LingSys', 'lingsys.ico'),
'mini': ('hatchmini', 'LingSysMini', 'lingsys.ico'),
'net': ('hatchnet', 'LingNet', 'lingnet.ico'),
}
ARCHS = { # key: (rust target, dist folder, extra RUSTFLAGS)
'x64': ('x86_64-pc-windows-msvc', 'x64', ''),
'x86': ('i686-pc-windows-msvc', 'x86', '-A overflowing_literals'),
}
# which samplers each exe starts: (script, extra arguments)
SAMPLERS = {
'hatchsys': [('sampler.ps1', []), ('sampler_hw.ps1', ['gpu']), ('sampler_hw.ps1', ['board']), ('sampler_hw.ps1', ['net'])],
'hatchmini': [('sampler.ps1', []), ('sampler_hw.ps1', ['gpu']), ('sampler_hw.ps1', ['board']), ('sampler_hw.ps1', ['net'])],
'hatchnet': [('sampler_hw.ps1', ['netx'])],
}
SNAPS = ['mem.snap', 'gpu.snap', 'board.snap', 'net.snap', 'netx.snap']
CARGO_TOML = '''[workspace]
[package]
name = "{name}"
version = "{version}"
edition = "2021"
build = "build.rs"
[[bin]]
name = "{name}"
path = "src/main.rs"
[dependencies]
ling = {{ path = "{root}", package = "ling-lang" }}
[build-dependencies]
winresource = "0.1"
[profile.release]
lto = "fat"
codegen-units = 1
opt-level = 3
strip = true
'''
BUILD_RS = '''// embeds the app icon (app.ico) and the version info in the exe
use std::path::Path;
fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
return;
}
if !Path::new("app.ico").exists() {
return;
}
println!("cargo:rerun-if-changed=app.ico");
let mut res = winresource::WindowsResource::new();
res.set_icon("app.ico");
res.set("ProductName", "LingSys");
res.set("FileDescription", "LingSys");
if let Err(e) = res.compile() {
println!("cargo:warning=icon embed skipped ({e})");
}
}
'''
MAIN_RS = r'''// Built by tools/build_exe.py: runs {ling} on the interpreter and carries its own samplers.
#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
use std::os::windows::process::CommandExt;
use std::path::Path;
use std::process::{{Child, Command}};
const SOURCE: &str = include_str!("../{ling}.ling");
const SAMPLER_MEM: &str = include_str!("../sampler.ps1");
const SAMPLER_HW: &str = include_str!("../sampler_hw.ps1");
fn spawn(dir: &Path, script: &str, extra: &[&str]) -> Option<Child> {{
let mut args: Vec<String> = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-File"]
.iter().map(|s| s.to_string()).collect();
args.push(dir.join(script).display().to_string());
args.extend(extra.iter().map(|s| s.to_string()));
args.push("-ParentPid".into());
args.push(std::process::id().to_string());
Command::new("powershell").args(&args).creation_flags(0x0800_0000).spawn().ok()
}}
fn main() {{
let sim = std::env::args().any(|a| a == "sim");
let base = std::env::var("LOCALAPPDATA").unwrap_or_else(|_| ".".into());
let dir = Path::new(&base).join("LingSys");
let _ = std::fs::create_dir_all(&dir);
let _ = std::fs::write(dir.join("sampler.ps1"), SAMPLER_MEM);
let _ = std::fs::write(dir.join("sampler_hw.ps1"), SAMPLER_HW);
let _ = std::env::set_current_dir(&dir);
for f in [{snaps}] {{
let _ = std::fs::remove_file(dir.join(f));
}}
let mut kids: Vec<Child> = Vec::new();
if !sim {{
{spawns}
}}
let result = ling::run_named(SOURCE, None, Some("{ling}.ling"));
for k in kids.iter_mut() {{
let _ = k.kill();
}}
for f in [{snaps}] {{
let _ = std::fs::remove_file(dir.join(f));
let _ = std::fs::remove_file(dir.join(format!("{{f}}.tmp")));
}}
if let Err(e) = result {{
eprintln!("{{e}}");
std::process::exit(1);
}}
}}
'''
def run(cmd, **kw):
print('>', ' '.join(str(c) for c in cmd))
subprocess.run(cmd, check=True, **kw)
def version():
for line in (ROOT / 'Ling.toml').read_text(encoding='utf8').splitlines():
if line.strip().startswith('version'):
return line.split('=')[1].strip().strip('"')
return '0.0.0'
def patch_x86(path):
"""Rename the import SetWindowLongPtrW -> SetWindowLongW (padded with NULs) in a 32-bit exe."""
data = bytearray(path.read_bytes())
old = b'SetWindowLongPtrW\0'
new = b'SetWindowLongW\0' + b'\0' * (len(old) - len(b'SetWindowLongW\0'))
n = data.count(old)
if n != 1:
sys.exit('%s: expected exactly one SetWindowLongPtrW import, found %d' % (path, n))
data = data.replace(old, new)
path.write_bytes(bytes(data))
print('patched SetWindowLongPtrW -> SetWindowLongW in', path)
def build(app_key, arch_key):
ling, exe_name, icon = APPS[app_key]
triple, folder, flags = ARCHS[arch_key]
proj = BUILD / ('exe-%s-%s' % (ling, arch_key))
(proj / 'src').mkdir(parents=True, exist_ok=True)
(proj / 'Cargo.toml').write_text(CARGO_TOML.format(name=ling, version=version(), root=str(LING_HOME).replace('\\', '/')), encoding='utf8')
(proj / 'build.rs').write_text(BUILD_RS, encoding='utf8')
shutil.copy(ROOT / 'assets' / icon, proj / 'app.ico')
for f in (ling + '.ling', 'sampler.ps1', 'sampler_hw.ps1'):
shutil.copy(ROOT / f, proj / f)
spawns = []
for script, kinds in SAMPLERS[ling]:
extra = ', '.join('"-Kind", "%s"' % k for k in kinds)
spawns.append(' kids.extend(spawn(&dir, "%s", &[%s]));' % (script, extra))
(proj / 'src' / 'main.rs').write_text(
MAIN_RS.format(ling=ling, spawns=chr(10).join(spawns), snaps=', '.join('"%s"' % s for s in SNAPS)), encoding='utf8')
env = dict(os.environ, CARGO_TARGET_DIR=str(BUILD / ('target-' + arch_key)))
if flags:
env['RUSTFLAGS'] = (env.get('RUSTFLAGS', '') + ' ' + flags).strip()
run(['cargo', 'build', '--release', '--target', triple], cwd=proj, env=env)
built = Path(env['CARGO_TARGET_DIR']) / triple / 'release' / (ling + '.exe')
out_dir = ROOT / 'dist' / folder
out_dir.mkdir(parents=True, exist_ok=True)
out = out_dir / (exe_name + '.exe')
shutil.copy(built, out)
if arch_key == 'x86':
patch_x86(out)
print('wrote %s (%.1f MB)' % (out, out.stat().st_size / 1e6))
def main():
args = sys.argv[1:]
apps = [a for a in args if a in APPS] or list(APPS)
archs = [a[2:] for a in args if a in ('--x64', '--x86')] or list(ARCHS)
for arch in archs:
for app in apps:
build(app, arch)
if __name__ == '__main__':
main()