From 0435ed375b0c13dc298b456634f08aa1d2828f18 Mon Sep 17 00:00:00 2001 From: Aldo Borrero Date: Thu, 16 Apr 2026 13:02:59 +0000 Subject: [PATCH] resolve: passAsFile importcfg_entries to stay under MAX_ARG_STRLEN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The link drv's importcfg_entries env var holds one packagefile line per transitive dep — ~400KB on torture (3270+ packages), which exceeds Linux's 128KB MAX_ARG_STRLEN and fails the builder exec with "Argument list too long". passAsFile makes Nix write it to a temp file and pass the path instead. Same for per-package compile drvs (smaller but no reason to differ). Dag mode already does this for compileManifestJSON. :house: Remote-Dev: homespace --- go/go2nix/pkg/resolve/builder.go | 11 ++++++----- go/go2nix/pkg/resolve/builder_test.go | 19 ++++++++++++------- go/go2nix/pkg/resolve/resolve.go | 2 ++ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/go/go2nix/pkg/resolve/builder.go b/go/go2nix/pkg/resolve/builder.go index 26935df..c26ecdc 100644 --- a/go/go2nix/pkg/resolve/builder.go +++ b/go/go2nix/pkg/resolve/builder.go @@ -47,9 +47,9 @@ func compileScript(go2nixBin string) string { b.WriteString("export HOME=\"$TMPDIR\"\n") b.WriteString("mkdir -p \"$out\"\n\n") - // Write importcfg from env var (placeholders resolved by Nix at build time). - // Use absolute path since compile-package changes CWD to srcdir. - b.WriteString("printf '%s\\n' \"$importcfg_entries\" > \"$NIX_BUILD_TOP/importcfg\"\n\n") + // Write importcfg from passAsFile (placeholders resolved by Nix at build + // time). passAsFile keeps the derivation's exec env under MAX_ARG_STRLEN. + b.WriteString("cat \"$importcfg_entriesPath\" > \"$NIX_BUILD_TOP/importcfg\"\n\n") // Write compile manifest from env var (JSON generated at derivation creation time). // Replace @@IMPORTCFG@@ placeholder with the actual importcfg path so that @@ -83,8 +83,9 @@ func linkScript(goStorePath, pname, buildMode string) string { b.WriteString("export HOME=\"$TMPDIR\"\n") b.WriteString("mkdir -p \"$out/bin\"\n\n") - // Write importcfg for all transitive deps - b.WriteString("printf '%s\\n' \"$importcfg_entries\" > \"$NIX_BUILD_TOP/importcfg\"\n\n") + // Write importcfg for all transitive deps. passAsFile keeps the exec + // env under MAX_ARG_STRLEN — torture's full closure is ~400KB. + b.WriteString("cat \"$importcfg_entriesPath\" > \"$NIX_BUILD_TOP/importcfg\"\n\n") // Set GOROOT so the linker embeds it as runtime.defaultGOROOT, // enabling runtime.GOROOT() in the resulting binary. diff --git a/go/go2nix/pkg/resolve/builder_test.go b/go/go2nix/pkg/resolve/builder_test.go index e5e17bc..d123754 100644 --- a/go/go2nix/pkg/resolve/builder_test.go +++ b/go/go2nix/pkg/resolve/builder_test.go @@ -87,8 +87,8 @@ func TestCompileScript(t *testing.T) { if !strings.Contains(script, "compile-package") { t.Error("missing compile-package call") } - if !strings.Contains(script, "$importcfg_entries") { - t.Error("missing importcfg_entries reference") + if !strings.Contains(script, "$importcfg_entriesPath") { + t.Error("missing importcfg_entriesPath reference") } if !strings.Contains(script, "${compileManifestJSON//@@IMPORTCFG@@/$NIX_BUILD_TOP/importcfg}") { t.Error("missing compileManifestJSON with @@IMPORTCFG@@ expansion") @@ -331,9 +331,9 @@ func TestBuildImportcfgMissingDrvPath(t *testing.T) { } } -// TestImportcfgEntriesBashWrite verifies that the multi-line importcfg_entries -// env var is correctly written to a file by the bash printf in both -// compileScript and linkScript. +// TestImportcfgEntriesBashWrite verifies that the importcfg_entries content +// (delivered via passAsFile) is correctly copied to NIX_BUILD_TOP/importcfg +// by the bash snippet in both compileScript and linkScript. func TestImportcfgEntriesBashWrite(t *testing.T) { entries := strings.Join([]string{ "packagefile fmt=/nix/store/stdlib/fmt.a", @@ -342,13 +342,18 @@ func TestImportcfgEntriesBashWrite(t *testing.T) { }, "\n") tmpDir := t.TempDir() + inFile := filepath.Join(tmpDir, "passAsFile-importcfg_entries") outFile := filepath.Join(tmpDir, "importcfg") + if err := os.WriteFile(inFile, []byte(entries), 0o644); err != nil { + t.Fatal(err) + } // Run the same bash snippet used by both compileScript and linkScript. - script := `printf '%s\n' "$importcfg_entries" > "$outfile"` + script := `cat "$importcfg_entriesPath" > "$outfile"` cmd := exec.Command("bash", "-c", script) cmd.Env = []string{ - "importcfg_entries=" + entries, + "PATH=" + os.Getenv("PATH"), + "importcfg_entriesPath=" + inFile, "outfile=" + outFile, } if out, err := cmd.CombinedOutput(); err != nil { diff --git a/go/go2nix/pkg/resolve/resolve.go b/go/go2nix/pkg/resolve/resolve.go index f6461d6..2cb94a6 100644 --- a/go/go2nix/pkg/resolve/resolve.go +++ b/go/go2nix/pkg/resolve/resolve.go @@ -685,6 +685,7 @@ func buildPackageDrv( return nil, fmt.Errorf("marshaling compile manifest: %w", err) } drv.SetEnv("compileManifestJSON", string(manifestJSON)) + drv.SetEnv("passAsFile", "importcfg_entries") if pkg.GoVersion != "" { drv.SetEnv("goVersion", compile.LangVersion(pkg.GoVersion)) @@ -1030,6 +1031,7 @@ func buildLinkDrv( } drv.SetEnv("importcfg_entries", strings.Join(importcfgEntries, "\n")) + drv.SetEnv("passAsFile", "importcfg_entries") drv.SetEnv("ldflags", cfg.LDFlags) // Propagate sanitizer flags (-race, -msan, -asan) from gcflags to the