diff --git a/internal/builder/binary_name_test.go b/internal/builder/binary_name_test.go new file mode 100644 index 0000000..de0bfd2 --- /dev/null +++ b/internal/builder/binary_name_test.go @@ -0,0 +1,44 @@ +package builder + +import ( + "testing" + + "github.com/novox/mesh-controller/internal/catalogue" +) + +// The name a machine runs a binary by is not always the name of the package that built it. The host's +// command is cmd/mesh-host and every machine runs it as nox-mesh-host — the path it is installed at, +// the name in its unit, and the name its launcher looks for inside a delivered version. +// +// A bundle carrying the package's name was delivered to a machine correctly, reported "created … 1 +// file(s)", and was invisible to the launcher (novox/hq 04-ISSUES/142). Found by reading the delivered +// directory rather than by trusting the line that said it worked. + +func TestACompiledArtifactNamesTheBinaryAMachineWillRun(t *testing.T) { + got := binaryName(catalogue.Artifact{ + Name: "host-arch", From: "cmd/mesh-host", Binary: "nox-mesh-host", + }) + if got != "nox-mesh-host" { + t.Fatalf("the binary is named %q, and the launcher looks for nox-mesh-host", got) + } +} + +func TestSayingNothingKeepsWhatTheCompilerWouldHaveChosen(t *testing.T) { + // go build names its output after the package, so an artifact that says nothing gets the same + // thing it got before this existed. + if got := binaryName(catalogue.Artifact{Name: "host-arch", From: "cmd/mesh-host"}); got != "mesh-host" { + t.Fatalf("an artifact naming no binary produced %q", got) + } + if got := binaryName(catalogue.Artifact{Name: "host-arch", From: "./cmd/agent/"}); got != "agent" { + t.Fatalf("a from with slashes produced %q", got) + } +} + +func TestABundleBuiltFromTheModuleRootFallsBackToItsArtifactName(t *testing.T) { + // A single-command repository names no package, and `go build -o ` would then write a file + // named after the module directory — which is not something the manifest states. The artifact's + // own name is what the manifest does state. + if got := binaryName(catalogue.Artifact{Name: "tool"}); got != "tool" { + t.Fatalf("a bundle built from the root produced %q", got) + } +} diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 2519378..02e12de 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -878,7 +878,16 @@ func compile(ctx context.Context, run Runner, tree string, chain Toolchain, } invocation = append(invocation, chain.Compile...) if chain.OutputFlag != "" { - invocation = append(invocation, chain.OutputFlag, out) + // A compiler pointed at a package is told the file to write, not the directory: the name a + // machine runs it by is not always the name of the package that built it. The host's command + // is `cmd/mesh-host` and every machine runs it as `nox-mesh-host` — so a bundle carrying the + // package's name lands correctly, reports success, and is invisible to whatever looks for it + // (novox/hq 04-ISSUES/142). + target := out + if chain.Unit == UnitPackage { + target = filepath.Join(out, binaryName(a)) + } + invocation = append(invocation, chain.OutputFlag, target) } // What to compile. Named by the module rather than discovered, so adding a file does not // silently change what a build produces. @@ -1067,3 +1076,15 @@ func readBy(manifest catalogue.Manifest) []catalogue.ArtifactContext { }) return out } + +// binaryName is what a compiled bundle's executable is called: what the artifact says, or the name of +// the package it is built from, which is what a compiler would have chosen anyway. +func binaryName(a catalogue.Artifact) string { + if name := strings.TrimSpace(a.Binary); name != "" { + return name + } + if from := strings.Trim(a.From, "./"); from != "" { + return filepath.Base(from) + } + return a.Name +} diff --git a/internal/catalogue/manifest.go b/internal/catalogue/manifest.go index 27c80ac..eb2f554 100644 --- a/internal/catalogue/manifest.go +++ b/internal/catalogue/manifest.go @@ -597,6 +597,16 @@ type Artifact struct { // Empty for every other kind, which do not compile. Language string `json:"language,omitempty"` + // Binary is what the compiled executable is called, for a bundle in a language that compiles to + // one. Empty means the package's own name, which is what a compiler does by default. + // + // **Because the name a machine runs it by is not always the name of the package that built it.** + // The host's command is `cmd/mesh-host` and every machine runs it as `nox-mesh-host` — the path + // it is installed at, the name in its unit, and the name its launcher looks for inside a + // delivered version. A bundle that carried the package's name was delivered correctly, reported + // success, and was invisible to the launcher (novox/hq 04-ISSUES/142). + Binary string `json:"binary,omitempty"` + // Entrypoints are the compiled files a tool host should load from this module, relative to the // bundle's root. //