summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-15 10:06:18 -0500
committerBen Sima <ben@bsima.me>2025-11-15 10:06:18 -0500
commitb3dd5f285365f59153a8f4549efa0607ccddf19d (patch)
tree60f4fbe7a8d4e858f069f997546c2b3337a0002a
parent905c6aa06c00150f0c051ead776a64aee0b2212c (diff)
Fix NixOS integration: separate package building from OS builds
Problem: Calling bild.run inside NixOS configs triggered IFD during OS evaluation. ANSI escape codes from bild broke JSON parsing in Nix sandbox, causing build failures. Root cause: bild.run uses IFD (Import From Derivation) which runs bild --plan during Nix evaluation. When this happened inside NixOS service definitions, it ran recursively and bild output ANSI codes that corrupted the JSON analysis output. Solution: Two-phase architecture + NO_COLOR support 1. Biz/Packages.nix: Pre-builds all packages outside NixOS context 2. Biz.nix: Accepts packages as function argument (default: Packages.nix) 3. Omni/Bild.nix: Sets NO_COLOR=1 in analysis derivation 4. Omni/Log/Terminal.hs: Respects NO_COLOR env var 5. Omni/Log/Terminal.hs: Skip getTerminalSize when NO_COLOR set to avoid escape code output 6. Omni/Log/Concurrent.hs: Skip line initialization without ANSI support Now NixOS builds succeed: - Package IFD happens once at top level - No recursive builds during service evaluation - Clean JSON output from bild --plan in Nix sandbox - NixOS configs reference pre-analyzed packages Changes: - Add Biz/Packages.nix - standalone package builder - Update Biz.nix to accept packages argument - Update Biz/Dragons/Analysis.nix to use Packages.nix - Remove Biz/Targets.nix (replaced by Packages.nix) - Add NO_COLOR support throughout logging stack - Fix ANSI.getTerminalSize outputting escape codes when NO_COLOR set Amp-Thread-ID: https://ampcode.com/threads/T-bc0f6fc7-46bf-4aa2-892e-dd62e7251d4b Co-authored-by: Amp <amp@ampcode.com>
-rwxr-xr-xBiz.nix36
-rwxr-xr-xBiz/Dragons/Analysis.nix35
-rw-r--r--Biz/Packages.nix15
-rw-r--r--Biz/Targets.nix19
-rw-r--r--Omni/Bild.nix1
-rw-r--r--Omni/Log/Concurrent.hs7
-rw-r--r--Omni/Log/Terminal.hs16
7 files changed, 70 insertions, 59 deletions
diff --git a/Biz.nix b/Biz.nix
index 7435c08..3ccf955 100755
--- a/Biz.nix
+++ b/Biz.nix
@@ -1,16 +1,22 @@
#!/usr/bin/env run.sh
# nunya
-{bild, ...}: let
- # Pre-declared targets prevent recursive builds during NixOS evaluation.
- # All bild.run calls happen once at this top level.
- targets = import ./Biz/Targets.nix {inherit bild;};
-in
- # This is the biz hosting service. Currently it defines a base OS similar to
- # Omni/Cloud.nix et al and starts each Biz/* thing as a systemd service. A
- # better solution might be to define each Biz/* thing as a container, and then
- # wire them together as necessary here, but I don't know how that works so I'll
- # just stick to this method for now.
- bild.os {
+#
+# To build the NixOS system:
+# 1. First build packages: nix-build Biz/Packages.nix
+# 2. Then build OS with packages: nix-build Biz.nix --arg packages "import ./Biz/Packages.nix {}"
+#
+# Or use the wrapper: Omni/Ide/run.sh Biz.nix
+{
+ bild,
+ packages ? import ./Biz/Packages.nix {inherit bild;},
+ ...
+}:
+# This is the biz hosting service. Currently it defines a base OS similar to
+# Omni/Cloud.nix et al and starts each Biz/* thing as a systemd service. A
+# better solution might be to define each Biz/* thing as a container, and then
+# wire them together as necessary here, but I don't know how that works so I'll
+# just stick to this method for now.
+bild.os {
imports = [
./Omni/Cloud/Hardware.nix
./Omni/Os/Base.nix
@@ -25,14 +31,14 @@ in
time.timeZone = "America/New_York";
services.storybook = {
enable = false;
- package = targets.storybook;
+ package = packages.storybook;
};
services.podcastitlater-web = {
enable = true;
- package = targets.podcastitlater-web;
+ package = packages.podcastitlater-web;
};
services.podcastitlater-worker = {
enable = true;
- package = targets.podcastitlater-worker;
+ package = packages.podcastitlater-worker;
};
- }
+}
diff --git a/Biz/Dragons/Analysis.nix b/Biz/Dragons/Analysis.nix
index de641e8..b0e0cc9 100755
--- a/Biz/Dragons/Analysis.nix
+++ b/Biz/Dragons/Analysis.nix
@@ -1,18 +1,19 @@
#!/usr/bin/env run.sh
-{bild}: let
- targets = import ../Targets.nix {inherit bild;};
-in
- # Run this like so:
- #
- # bild Biz/Dragons/Analysis.nix
- # docker load < _/nix/Biz/Dragons/Analysis.nix
- # docker run --volume $PWD:/src dragons-analyze dragons-analyze /src/.git
- bild.image {
- name = "dragons-analyze";
- tag = "latest";
- fromImage = null;
- fromImageName = null;
- fromImageTag = "latest";
- contents = [bild.pkgs.git targets.dragons-analysis];
- config.Cmd = ["/bin/dragons-analyze"];
- }
+{
+ bild,
+ packages ? import ../Packages.nix {inherit bild;},
+}:
+# Run this like so:
+#
+# bild Biz/Dragons/Analysis.nix
+# docker load < _/nix/Biz/Dragons/Analysis.nix
+# docker run --volume $PWD:/src dragons-analyze dragons-analyze /src/.git
+bild.image {
+ name = "dragons-analyze";
+ tag = "latest";
+ fromImage = null;
+ fromImageName = null;
+ fromImageTag = "latest";
+ contents = [bild.pkgs.git packages.dragons-analysis];
+ config.Cmd = ["/bin/dragons-analyze"];
+}
diff --git a/Biz/Packages.nix b/Biz/Packages.nix
new file mode 100644
index 0000000..6b17fe5
--- /dev/null
+++ b/Biz/Packages.nix
@@ -0,0 +1,15 @@
+# Build all Biz packages independently, outside NixOS context.
+#
+# This file builds all Biz packages and returns them as an attribute set.
+# The NixOS config (Biz.nix) will accept these as inputs rather than
+# building them during OS evaluation.
+#
+# Usage:
+# nix-build Biz/Packages.nix # builds all packages
+# nix-build Biz/Packages.nix -A storybook # builds one package
+{bild ? import ../Omni/Bild.nix {}}: {
+ storybook = bild.run ../Biz/Storybook.py;
+ podcastitlater-web = bild.run ../Biz/PodcastItLater/Web.py;
+ podcastitlater-worker = bild.run ../Biz/PodcastItLater/Worker.py;
+ dragons-analysis = bild.run ../Biz/Dragons/Analysis.hs;
+}
diff --git a/Biz/Targets.nix b/Biz/Targets.nix
deleted file mode 100644
index 77e462c..0000000
--- a/Biz/Targets.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-# Pre-declared build targets for the Biz namespace.
-#
-# This file exposes all buildable Biz targets as an attribute set, allowing
-# NixOS configs to reference them directly without triggering recursive builds.
-#
-# To add a new target:
-# 1. Add the attribute here pointing to bild.run ./path/to/target
-# 2. Reference it in Biz.nix or other configs as targets.<name>
-{bild}: {
- # Web services
- storybook = bild.run ./Storybook.py;
- podcastitlater-web = bild.run ./PodcastItLater/Web.py;
- podcastitlater-worker = bild.run ./PodcastItLater/Worker.py;
-
- # CLI tools and analysis
- dragons-analysis = bild.run ./Dragons/Analysis.hs;
-
- # Add new Biz targets here as they are created
-}
diff --git a/Omni/Bild.nix b/Omni/Bild.nix
index f6291ef..aae82db 100644
--- a/Omni/Bild.nix
+++ b/Omni/Bild.nix
@@ -229,6 +229,7 @@
+ self.lib.strings.removePrefix (toString src) (toString target);
buildPhase = ''
export CODEROOT=$(pwd)
+ export NO_COLOR=1
mkdir $out
${self.bild}/bin/bild --plan "$TARGET" 1> $out/analysis.json \
2> >(tee -a $out/stderr >&2)
diff --git a/Omni/Log/Concurrent.hs b/Omni/Log/Concurrent.hs
index 83289f3..edf87fd 100644
--- a/Omni/Log/Concurrent.hs
+++ b/Omni/Log/Concurrent.hs
@@ -79,9 +79,10 @@ withLineManager nss action = do
-- | Initialize all lines with pending status
initializeLines :: LineManager -> IO ()
initializeLines LineManager {..} =
- case tiMode lmTermInfo of
- SingleLine -> pure () -- No initialization needed
- MultiLine -> do
+ case (tiMode lmTermInfo, tiSupportsANSI lmTermInfo) of
+ (_, False) -> pure () -- No ANSI support, skip initialization
+ (SingleLine, _) -> pure () -- No initialization needed
+ (MultiLine, _) -> do
nsMap <- readIORef namespaceLines
forM_ (Map.toList nsMap) <| \(ns, _) -> do
ANSI.hSetCursorColumn IO.stderr 0
diff --git a/Omni/Log/Terminal.hs b/Omni/Log/Terminal.hs
index a78e544..0d2ca7a 100644
--- a/Omni/Log/Terminal.hs
+++ b/Omni/Log/Terminal.hs
@@ -33,16 +33,22 @@ detectTerminal :: IO TerminalInfo
detectTerminal = do
term <- Env.lookupEnv "TERM"
area <- Env.lookupEnv "AREA"
+ noColor <- Env.lookupEnv "NO_COLOR"
-- Check if we support ANSI
- let supportsANSI = case (term, area) of
- (Just "dumb", _) -> False
- (_, Just "Live") -> False -- production logs
- (Nothing, _) -> False
+ let supportsANSI = case (term, area, noColor) of
+ (_, _, Just _) -> False -- NO_COLOR set
+ (Just "dumb", _, _) -> False
+ (_, Just "Live", _) -> False -- production logs
+ (Nothing, _, _) -> False
_ -> True
-- Get terminal size, catching exceptions from stdin issues
- mSize <- Exception.catch ANSI.getTerminalSize <| \(_ :: Exception.IOException) -> pure Nothing
+ -- When NO_COLOR is set or ANSI is not supported, skip terminal size detection
+ -- to avoid outputting escape codes
+ mSize <- case supportsANSI of
+ False -> pure Nothing -- Skip if no ANSI support
+ True -> Exception.catch ANSI.getTerminalSize <| \(_ :: Exception.IOException) -> pure Nothing
let (width, height) = case mSize of
Just (h, w) -> (w, h)
Nothing -> (80, 24) -- sensible default