Skip to content

Commit 1c39a5d

Browse files
committed
overhaul messaging + add --debug
1 parent 0a22667 commit 1c39a5d

4 files changed

Lines changed: 194 additions & 103 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ nix run .#nynx -- --flake <flake-url> --operation <operation>
2020

2121
### Flags
2222

23-
- `--build-host`: Specify the host on which to build closures (default: `localhost`).
24-
- `--flake`: Specify the flake path or URL (e.g., `github:alyraffauf/nixcfg`).
23+
- `--debug`: Enable debug output.
24+
- `--skip`: Skip a comma-separated subset of jobs.
2525
- `--jobs`: Comma-separated subset of jobs to run (default: all jobs).
26+
- `--flake`: Specify the flake path or URL (e.g., `github:alyraffauf/nixcfg`).
27+
- `--build-host`: Specify the host on which to build closures (default: `localhost`).
2628
- `--operation`: Operation to perform (`switch`, or `activate` for Darwin; `boot`, `test`, `switch` for NixOS).
27-
- `--skip`: Skip a comma-separated subset of jobs.
28-
- `--verbose`: Enable verbose output.
2929

3030
### Example
3131

src/helpers.go

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@ package main
33
import (
44
"encoding/json"
55
"fmt"
6-
"os"
76
"os/exec"
87
"strings"
98
)
109

10+
// DebugInfo contains command execution details for debugging
11+
type DebugInfo struct {
12+
Command string // The command that was run
13+
StdOut string // Standard output
14+
StdErr string // Standard error
15+
WasSuccess bool // Whether the command succeeded
16+
}
17+
1118
// Deployment spec for a single job.
1219
type JobSpec struct {
1320
Output string `json:"output"`
@@ -27,44 +34,51 @@ type NixEvalJobsResult struct {
2734
System string `json:"system"`
2835
}
2936

30-
func fatal(format string, args ...any) {
31-
fmt.Fprintf(os.Stderr, "[nynx] Error: "+format+"\n", args...)
32-
os.Exit(1)
33-
}
34-
35-
func info(format string, args ...any) {
36-
fmt.Printf("[nynx] "+format+"\n", args...)
37-
}
38-
39-
func getConfigAttr(cfg string, job string, attr string) string {
37+
// getConfigAttr evaluates a configuration attribute from the nix flake.
38+
// Returns empty string if evaluation fails.
39+
func getConfigAttr(cfg string, job string, attr string) (string, *DebugInfo, error) {
4040
attrPath := fmt.Sprintf("%s#nynxDeployments.%s.%s", cfg, job, attr)
41-
value, err := runJSON[string]("nix", "eval", "--json", attrPath)
41+
value, debug, err := runJSON[string]("nix", "eval", "--json", attrPath)
4242
if err != nil {
43-
return ""
43+
return "", debug, fmt.Errorf("failed to evaluate attribute %s for job %s: %w", attr, job, err)
4444
}
45-
return value
45+
return value, debug, nil
4646
}
4747

48-
func evalDeployments(cfg string) (map[string]JobSpec, error) {
48+
func evalDeployments(cfg string) (map[string]JobSpec, []*DebugInfo, error) {
49+
var debugInfos []*DebugInfo
4950
flakeReference := fmt.Sprintf("%s#nynxDeployments", cfg)
5051

5152
// Get raw output since nix-eval-jobs uses JSON Lines format
53+
cmdStr := fmt.Sprintf("nix-eval-jobs --force-recurse --flake %s", flakeReference)
5254
c := exec.Command("nix-eval-jobs", "--force-recurse", "--flake", flakeReference)
5355
out, err := c.Output()
56+
57+
debug := &DebugInfo{
58+
Command: cmdStr,
59+
WasSuccess: err == nil,
60+
}
61+
5462
if err != nil {
5563
if ee, ok := err.(*exec.ExitError); ok {
56-
return nil, fmt.Errorf("failed to run nix-eval-jobs on %s: %s", cfg, string(ee.Stderr))
64+
debug.StdErr = string(ee.Stderr)
65+
debugInfos = append(debugInfos, debug)
66+
return nil, debugInfos, fmt.Errorf("failed to run nix-eval-jobs on %s: %s", cfg, string(ee.Stderr))
5767
}
58-
return nil, fmt.Errorf("failed to run nix-eval-jobs on %s: %v", cfg, err)
68+
debugInfos = append(debugInfos, debug)
69+
return nil, debugInfos, fmt.Errorf("failed to run nix-eval-jobs on %s: %v", cfg, err)
5970
}
6071

72+
debug.StdOut = string(out)
73+
debugInfos = append(debugInfos, debug)
74+
6175
// Parse JSON Lines format
6276
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
6377
results := make([]NixEvalJobsResult, 0, len(lines))
6478
for _, line := range lines {
6579
var result NixEvalJobsResult
6680
if err := json.Unmarshal([]byte(line), &result); err != nil {
67-
return nil, fmt.Errorf("failed to parse JSON line from nix-eval-jobs: %w\nLine: %s", err, line)
81+
return nil, debugInfos, fmt.Errorf("invalid JSON output: %w", err)
6882
}
6983
results = append(results, result)
7084
}
@@ -74,13 +88,13 @@ func evalDeployments(cfg string) (map[string]JobSpec, error) {
7488
// First pass: create basic job specs
7589
for _, result := range results {
7690
if len(result.AttrPath) < 2 {
77-
return nil, fmt.Errorf("invalid attrPath format: %v", result.AttrPath)
91+
return nil, debugInfos, fmt.Errorf("malformed attrPath: %v", result.AttrPath)
7892
}
7993

8094
jobName := result.AttrPath[0]
8195
outputPath, ok := result.Outputs["out"]
8296
if !ok {
83-
return nil, fmt.Errorf("missing 'out' output for job: %s", jobName)
97+
return nil, debugInfos, fmt.Errorf("job %s: missing 'out' output", jobName)
8498
}
8599

86100
jobs[jobName] = JobSpec{
@@ -93,19 +107,27 @@ func evalDeployments(cfg string) (map[string]JobSpec, error) {
93107

94108
// Second pass: enrich specs with additional attributes
95109
for jobName, spec := range jobs {
96-
if hostname := getConfigAttr(cfg, jobName, "hostname"); hostname != "" {
110+
if hostname, debug, err := getConfigAttr(cfg, jobName, "hostname"); err == nil && hostname != "" {
97111
spec.Hostname = hostname
112+
debugInfos = append(debugInfos, debug)
98113
}
99114

100-
spec.User = getConfigAttr(cfg, jobName, "user")
101-
spec.Type = getConfigAttr(cfg, jobName, "type")
115+
if user, debug, err := getConfigAttr(cfg, jobName, "user"); err == nil {
116+
spec.User = user
117+
debugInfos = append(debugInfos, debug)
118+
}
119+
120+
if sysType, debug, err := getConfigAttr(cfg, jobName, "type"); err == nil {
121+
spec.Type = sysType
122+
debugInfos = append(debugInfos, debug)
123+
}
102124

103125
if spec.Output == "" {
104-
return nil, fmt.Errorf("missing 'output' for job: %s", jobName)
126+
return nil, debugInfos, fmt.Errorf("job %s: missing output path", jobName)
105127
}
106128

107129
if spec.User == "" {
108-
return nil, fmt.Errorf("missing 'user' for job: %s", jobName)
130+
return nil, debugInfos, fmt.Errorf("job %s: missing user attribute", jobName)
109131
}
110132

111133
// Infer Type if missing
@@ -125,46 +147,62 @@ func evalDeployments(cfg string) (map[string]JobSpec, error) {
125147
case strings.Contains(systemFound, "linux"):
126148
spec.Type = "nixos"
127149
default:
128-
return nil, fmt.Errorf("could not infer system type for job '%s' from system '%s'", jobName, systemFound)
150+
return nil, debugInfos, fmt.Errorf("job %s: unknown system type %s", jobName, systemFound)
129151
}
130152
}
131153

132154
if spec.Type != "nixos" && spec.Type != "darwin" {
133-
return nil, fmt.Errorf("unsupported system type '%s' for job: %s", spec.Type, jobName)
155+
return nil, debugInfos, fmt.Errorf("job %s: unsupported system type %s", jobName, spec.Type)
134156
}
135157

136158
jobs[jobName] = spec
137159
}
138160

139-
return jobs, nil
161+
return jobs, debugInfos, nil
140162
}
141163

142-
func run(cmd string, args ...string) ([]byte, error) {
143-
c := exec.Command(cmd, args...)
144-
out, err := c.CombinedOutput()
164+
func run(cmd string, args ...string) ([]byte, *DebugInfo, error) {
165+
cmdStr := fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
166+
debug := &DebugInfo{
167+
Command: cmdStr,
168+
}
169+
170+
out, err := exec.Command(cmd, args...).CombinedOutput()
171+
debug.StdOut = string(out)
172+
debug.WasSuccess = err == nil
173+
145174
if err != nil {
146-
return out, fmt.Errorf("`%s %v` failed: %s", cmd, args, string(out))
175+
return out, debug, fmt.Errorf("%s failed: %s", cmdStr, strings.TrimSpace(string(out)))
147176
}
148-
return out, nil
177+
return out, debug, nil
149178
}
150179

151-
func runJSON[T any](cmd string, args ...string) (T, error) {
180+
func runJSON[T any](cmd string, args ...string) (T, *DebugInfo, error) {
152181
var result T
182+
cmdStr := fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
183+
debug := &DebugInfo{
184+
Command: cmdStr,
185+
}
186+
153187
c := exec.Command(cmd, args...)
154188
out, err := c.Output() // only capture stdout
189+
debug.StdOut = string(out)
190+
debug.WasSuccess = err == nil
191+
155192
if err != nil {
156193
if ee, ok := err.(*exec.ExitError); ok {
157-
return result, fmt.Errorf("`%s %v` failed: %s", cmd, args, string(ee.Stderr))
194+
debug.StdErr = string(ee.Stderr)
195+
return result, debug, fmt.Errorf("%s failed: %s", cmdStr, strings.TrimSpace(string(ee.Stderr)))
158196
}
159-
return result, fmt.Errorf("`%s %v` failed: %v", cmd, args, err)
197+
return result, debug, fmt.Errorf("%s failed: %v", cmdStr, err)
160198
}
161199

162200
if err := json.Unmarshal(out, &result); err != nil {
163-
return result, fmt.Errorf("failed to unmarshal JSON output from `%s %v`: %w\nRaw output: %s",
164-
cmd, args, err, string(out))
201+
return result, debug, fmt.Errorf("failed to unmarshal JSON output from %s: %w\nRaw output: %s",
202+
cmdStr, err, strings.TrimSpace(string(out)))
165203
}
166204

167-
return result, nil
205+
return result, debug, nil
168206
}
169207

170208
func validateOperations(jobs map[string]JobSpec, op string) ([]string, error) {
@@ -194,13 +232,3 @@ func validateOperations(jobs map[string]JobSpec, op string) ([]string, error) {
194232
}
195233
return warnings, nil
196234
}
197-
198-
func verboseInfo(verbose bool, format string, args ...any) {
199-
if verbose {
200-
info(format, args...)
201-
}
202-
}
203-
204-
func warn(format string, args ...any) {
205-
fmt.Fprintf(os.Stderr, "[nynx] Warning: "+format+"\n", args...)
206-
}

0 commit comments

Comments
 (0)