灵符lingfu

LingSys

↓ 0 downloads

Your 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 LingSys
LicenseMIT
AuthorsLing Lin <taellinglin@gmail.com>
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / 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()

← Back to index