349 lines
9.9 KiB
Nim
349 lines
9.9 KiB
Nim
# SPDX-License-Identifier: LSL-1.0
|
|
# Copyright (c) 2026 Markus Maiwald
|
|
# Stewardship: Self Sovereign Society Foundation
|
|
#
|
|
# This file is part of the Nexus Sovereign Core.
|
|
# See legal/LICENSE_SOVEREIGN.md for license terms.
|
|
|
|
## variants.nim
|
|
## Typed variant system for deterministic, content-addressed packages
|
|
## Evolution of USE flags into semantic domains with type safety
|
|
|
|
import std/[tables, strutils, sequtils, algorithm, sets, os]
|
|
import config
|
|
|
|
type
|
|
FlagType* = enum
|
|
ftBool ## Simple on/off: +lto, -debug
|
|
ftChoice ## Mutually exclusive: init=dinit
|
|
ftSet ## Multiple allowed: security=pie,relro,hardened
|
|
ftEnum ## Predefined options with validation
|
|
|
|
VariantFlag* = object
|
|
domain*: string ## "init", "graphics", "security"
|
|
name*: string ## "dinit", "wayland", "pie"
|
|
flagType*: FlagType
|
|
enabled*: bool
|
|
value*: string ## For choice/enum types
|
|
affects*: seq[string] ## What this flag impacts
|
|
conflicts*: seq[string]
|
|
requires*: seq[string]
|
|
|
|
FlagDomain* = object
|
|
name*: string
|
|
description*: string
|
|
flagType*: FlagType
|
|
exclusive*: bool ## Only one flag can be enabled
|
|
options*: seq[string]
|
|
default*: string ## Default value for choice types
|
|
defaults*: seq[string] ## Default values for set types
|
|
|
|
VariantFingerprint* = object
|
|
hash*: string ## BLAKE3 hash of variant configuration
|
|
packageName*: string
|
|
version*: string
|
|
domains*: Table[string, seq[string]]
|
|
compilerFlags*: CompilerFlags
|
|
toolchain*: string
|
|
target*: string
|
|
|
|
PackageVariant* = object
|
|
fingerprint*: VariantFingerprint
|
|
installPath*: string
|
|
isDefault*: bool
|
|
installedAt*: string
|
|
|
|
# ============================================
|
|
# Domain Definitions (9 Semantic Domains)
|
|
# ============================================
|
|
|
|
proc getSemanticDomains*(): Table[string, FlagDomain] =
|
|
## Get the 9 semantic domains for typed variants
|
|
result = initTable[string, FlagDomain]()
|
|
|
|
# 1. Init System (exclusive choice)
|
|
result["init"] = FlagDomain(
|
|
name: "init",
|
|
description: "Init system selection",
|
|
flagType: ftChoice,
|
|
exclusive: true,
|
|
options: @["systemd", "dinit", "openrc", "runit", "s6"],
|
|
default: "dinit"
|
|
)
|
|
|
|
# 2. Runtime Features (set)
|
|
result["runtime"] = FlagDomain(
|
|
name: "runtime",
|
|
description: "Core runtime features",
|
|
flagType: ftSet,
|
|
exclusive: false,
|
|
options: @[
|
|
"ssl", "http3", "zstd", "lz4", "ipv6",
|
|
"dbus", "doc", "examples",
|
|
"python", "ruby", "perl", "lua", "go", "rust",
|
|
"cuda", "rocm", "onnx", "tensorrt",
|
|
"steam", "wine", "proton"
|
|
],
|
|
defaults: @["ssl", "ipv6"]
|
|
)
|
|
|
|
# 3. Graphics (choice + sub-features)
|
|
result["graphics"] = FlagDomain(
|
|
name: "graphics",
|
|
description: "Display server and GPU API",
|
|
flagType: ftChoice,
|
|
exclusive: false, # Can have display + GPU APIs
|
|
options: @[
|
|
"none", "X", "wayland",
|
|
"vulkan", "opengl", "mesa",
|
|
"nvidia", "amd", "intel-gpu"
|
|
],
|
|
default: "none"
|
|
)
|
|
|
|
# 4. Audio System (exclusive choice)
|
|
result["audio"] = FlagDomain(
|
|
name: "audio",
|
|
description: "Sound server selection",
|
|
flagType: ftChoice,
|
|
exclusive: true,
|
|
options: @["none", "pipewire", "pulseaudio", "alsa", "jack", "oss"],
|
|
default: "pipewire"
|
|
)
|
|
|
|
# 5. Security Hardening (set)
|
|
result["security"] = FlagDomain(
|
|
name: "security",
|
|
description: "Security hardening features",
|
|
flagType: ftSet,
|
|
exclusive: false,
|
|
options: @["pie", "relro", "hardened", "fortify", "stack-protector"],
|
|
defaults: @["pie", "relro"]
|
|
)
|
|
|
|
# 6. Optimization (set with conflicts)
|
|
result["optimization"] = FlagDomain(
|
|
name: "optimization",
|
|
description: "Build optimizations",
|
|
flagType: ftSet,
|
|
exclusive: false,
|
|
options: @["lto", "pgo", "march-native", "debug", "strip"],
|
|
defaults: @["lto"]
|
|
)
|
|
|
|
# 7. Integration (set)
|
|
result["integration"] = FlagDomain(
|
|
name: "integration",
|
|
description: "System interfaces and integration",
|
|
flagType: ftSet,
|
|
exclusive: false,
|
|
options: @[
|
|
"docker", "podman", "nipcells", "containerd", "runc", "crun",
|
|
"kvm", "qemu", "libvirt", "xen", "bhyve",
|
|
"nexus-api", "nexus-db", "nexus-sync", "nexus-monitor", "nexus-security"
|
|
],
|
|
defaults: @[]
|
|
)
|
|
|
|
# 8. Network (set)
|
|
result["network"] = FlagDomain(
|
|
name: "network",
|
|
description: "Networking stack and protocols",
|
|
flagType: ftSet,
|
|
exclusive: false,
|
|
options: @[
|
|
"ipv6", "wireguard", "zerotier", "tailscale",
|
|
"mesh", "p2p", "ipfs", "libp2p"
|
|
],
|
|
defaults: @["ipv6"]
|
|
)
|
|
|
|
# 9. Developer Tools (set)
|
|
result["developer"] = FlagDomain(
|
|
name: "developer",
|
|
description: "Development tools and features",
|
|
flagType: ftSet,
|
|
exclusive: false,
|
|
options: @["debugger", "profiler", "lsp", "repl", "hot-reload", "sanitizer", "coverage"],
|
|
defaults: @[]
|
|
)
|
|
|
|
# ============================================
|
|
# Variant Flag Parsing
|
|
# ============================================
|
|
|
|
proc parseVariantFlag*(flagStr: string): VariantFlag =
|
|
## Parse domain-scoped variant flag
|
|
## Examples:
|
|
## "+init=dinit"
|
|
## "+runtime=ssl,http3,zstd"
|
|
## "+optimization=lto"
|
|
## "-debug"
|
|
|
|
let trimmed = flagStr.strip()
|
|
|
|
if trimmed.len == 0:
|
|
raise newException(ValueError, "Empty variant flag")
|
|
|
|
# Determine enabled/disabled
|
|
var enabled = true
|
|
var flagPart = trimmed
|
|
|
|
if trimmed[0] == '+':
|
|
enabled = true
|
|
flagPart = trimmed[1..^1]
|
|
elif trimmed[0] == '-':
|
|
enabled = false
|
|
flagPart = trimmed[1..^1]
|
|
|
|
# Check for domain syntax: domain=value
|
|
if '=' in flagPart:
|
|
let parts = flagPart.split('=', 1)
|
|
result = VariantFlag(
|
|
domain: parts[0],
|
|
name: parts[1],
|
|
flagType: ftChoice, # Will be determined by domain
|
|
enabled: enabled,
|
|
value: parts[1]
|
|
)
|
|
else:
|
|
# Simple flag (backward compatible)
|
|
result = VariantFlag(
|
|
domain: "", # Will be inferred
|
|
name: flagPart,
|
|
flagType: ftBool,
|
|
enabled: enabled,
|
|
value: ""
|
|
)
|
|
|
|
proc inferDomain*(flagName: string, domains: Table[string, FlagDomain]): string =
|
|
## Infer domain from flag name for backward compatibility
|
|
for domainName, domain in domains:
|
|
if flagName in domain.options:
|
|
return domainName
|
|
return "runtime" # Default domain
|
|
|
|
# ============================================
|
|
# Variant Fingerprint Generation
|
|
# ============================================
|
|
|
|
proc calculateVariantFingerprint*(
|
|
packageName: string,
|
|
version: string,
|
|
domains: Table[string, seq[string]],
|
|
compilerFlags: CompilerFlags,
|
|
toolchain: string = "default",
|
|
target: string = "native"
|
|
): string =
|
|
## Calculate deterministic BLAKE3 hash for variant
|
|
## Hash of: source + version + flags + toolchain + target
|
|
|
|
var hashInput = ""
|
|
|
|
# 1. Package identity
|
|
hashInput.add(packageName & "\n")
|
|
hashInput.add(version & "\n")
|
|
|
|
# 2. Sorted domain flags (deterministic)
|
|
var sortedDomainNames = domains.keys.toSeq
|
|
sortedDomainNames.sort()
|
|
|
|
for domainName in sortedDomainNames:
|
|
hashInput.add(domainName & ":")
|
|
var sortedFlags = domains[domainName]
|
|
sortedFlags.sort()
|
|
hashInput.add(sortedFlags.join(",") & "\n")
|
|
|
|
# 3. Compiler flags
|
|
hashInput.add("cflags:" & compilerFlags.cflags & "\n")
|
|
hashInput.add("cxxflags:" & compilerFlags.cxxflags & "\n")
|
|
hashInput.add("ldflags:" & compilerFlags.ldflags & "\n")
|
|
|
|
# 4. Toolchain
|
|
hashInput.add("toolchain:" & toolchain & "\n")
|
|
|
|
# 5. Target
|
|
hashInput.add("target:" & target & "\n")
|
|
|
|
# Calculate hash (simplified for now - will use BLAKE3)
|
|
# For now, use a simple hash
|
|
var simpleHash = 0
|
|
for c in hashInput:
|
|
simpleHash = (simpleHash * 31 + ord(c)) and 0x7FFFFFFF
|
|
|
|
result = "blake3-" & simpleHash.toHex()[0..11].toLower()
|
|
|
|
proc generateVariantPath*(
|
|
packageName: string,
|
|
version: string,
|
|
fingerprint: string,
|
|
baseDir: string = "/Programs"
|
|
): string =
|
|
## Generate variant installation path
|
|
## Format: /Programs/<Name>/<Version>-<Fingerprint>/
|
|
result = baseDir / packageName / (version & "-" & fingerprint)
|
|
|
|
# ============================================
|
|
# Variant Management
|
|
# ============================================
|
|
|
|
proc listVariants*(packageName: string, baseDir: string = "/Programs"): seq[PackageVariant] =
|
|
## List all installed variants of a package
|
|
result = @[]
|
|
|
|
let packageDir = baseDir / packageName
|
|
if not dirExists(packageDir):
|
|
return
|
|
|
|
for kind, path in walkDir(packageDir):
|
|
if kind == pcDir:
|
|
let dirName = path.splitPath().tail
|
|
if dirName.contains("-blake3-"):
|
|
let parts = dirName.split("-blake3-", 1)
|
|
if parts.len == 2:
|
|
result.add(PackageVariant(
|
|
fingerprint: VariantFingerprint(
|
|
hash: "blake3-" & parts[1],
|
|
packageName: packageName,
|
|
version: parts[0]
|
|
),
|
|
installPath: path,
|
|
isDefault: false, # TODO: Check symlinks
|
|
installedAt: ""
|
|
))
|
|
|
|
proc getDefaultVariant*(packageName: string): string =
|
|
## Get the default variant fingerprint for a package
|
|
## Checks which variant is currently symlinked
|
|
# TODO: Implement by checking /System/Links/ symlinks
|
|
result = ""
|
|
|
|
proc setDefaultVariant*(packageName: string, fingerprint: string): bool =
|
|
## Set which variant is the default (symlinked)
|
|
## Updates symlinks in /System/Links/
|
|
# TODO: Implement symlink switching
|
|
result = false
|
|
|
|
# ============================================
|
|
# Display Functions
|
|
# ============================================
|
|
|
|
proc displayVariant*(variant: PackageVariant) =
|
|
## Display variant information
|
|
echo variant.fingerprint.packageName & " " & variant.fingerprint.version & "-" & variant.fingerprint.hash
|
|
if variant.isDefault:
|
|
echo " [DEFAULT]"
|
|
echo " Path: " & variant.installPath
|
|
|
|
proc displayVariants*(variants: seq[PackageVariant]) =
|
|
## Display list of variants
|
|
if variants.len == 0:
|
|
echo "No variants found"
|
|
return
|
|
|
|
echo "Installed Variants:"
|
|
echo ""
|
|
for variant in variants:
|
|
displayVariant(variant)
|
|
echo ""
|