Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion cmd/llar/internal/make.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package internal
import (
"archive/zip"
"context"
"encoding/json"
"fmt"
stdbuild "go/build"
"io"
Expand All @@ -24,6 +25,11 @@ import (
var makeVerbose bool
var makeOutput string

type artifactMetadata struct {
Metadata string `json:"metadata"`
Deps []string `json:"deps,omitempty"`
}

// newRemoteStore creates the remote formula store. Overridable for testing.
var newRemoteStore = func() (repo.Store, error) {
formulaDir, err := repo.DefaultDir()
Expand Down Expand Up @@ -186,7 +192,7 @@ func buildModule(ctx context.Context, store repo.Store, modPath, version, matrix
fmt.Println(main.Metadata)
}
if makeOutput != "" {
if err := outputResult(main.OutputDir, makeOutput); err != nil {
if err := outputArtifact(main.OutputDir, makeOutput, main.Metadata, artifactDeps(mods)); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
}
Expand Down Expand Up @@ -257,6 +263,55 @@ func parseModuleArg(arg string) (pattern, version string, isLocal bool, err erro
return
}

func writeArtifactMetadata(installDir, metadata string, deps []module.Version) error {
body, err := json.MarshalIndent(artifactMetadata{
Metadata: metadata,
Deps: artifactDepStrings(deps),
}, "", " ")
if err != nil {
return err
}

metaDir := filepath.Join(installDir, ".llar")
if err := os.MkdirAll(metaDir, 0o755); err != nil {
return err
}
return os.WriteFile(filepath.Join(metaDir, "metadata.json"), append(body, '\n'), 0o644)
}

func artifactDeps(mods []*modules.Module) []module.Version {
if len(mods) <= 1 {
return nil
}
deps := make([]module.Version, 0, len(mods)-1)
main := mods[0]
for _, mod := range mods[1:] {
if mod.Path == main.Path && mod.Version == main.Version {
continue
}
deps = append(deps, module.Version{Path: mod.Path, Version: mod.Version})
}
return deps
}

func artifactDepStrings(deps []module.Version) []string {
if len(deps) == 0 {
return nil
}
out := make([]string, 0, len(deps))
for _, dep := range deps {
out = append(out, dep.Path+"@"+dep.Version)
}
return out
}

func outputArtifact(srcDir, dest, metadata string, deps []module.Version) error {
if err := writeArtifactMetadata(srcDir, metadata, deps); err != nil {
return err
}
return outputResult(srcDir, dest)
}

// outputResult writes the build output to dest.
// If dest ends with ".zip", creates a zip archive; otherwise copies the directory.
func outputResult(srcDir, dest string) error {
Expand Down
112 changes: 112 additions & 0 deletions cmd/llar/internal/make_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
Expand Down Expand Up @@ -129,6 +130,117 @@ func setupTestSrcDir(t *testing.T) string {
return src
}

func TestWriteArtifactMetadataWritesMetadataAndDeps(t *testing.T) {
installDir := t.TempDir()
metadata := fmt.Sprintf("-I%s -L%s -lz", filepath.Join(installDir, "include"), filepath.Join(installDir, "lib"))
deps := []module.Version{{Path: "madler/zlib", Version: "v1.3.1"}}

if err := writeArtifactMetadata(installDir, metadata, deps); err != nil {
t.Fatalf("writeArtifactMetadata: %v", err)
}

data, err := os.ReadFile(filepath.Join(installDir, ".llar", "metadata.json"))
if err != nil {
t.Fatalf("read metadata.json: %v", err)
}
var got struct {
Metadata string `json:"metadata"`
Deps []string `json:"deps"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("metadata.json is invalid JSON: %v", err)
}
if got.Metadata != metadata {
t.Fatalf("metadata = %q, want %q", got.Metadata, metadata)
}
wantDeps := []string{"madler/zlib@v1.3.1"}
if !reflect.DeepEqual(got.Deps, wantDeps) {
t.Fatalf("deps = %+v, want %+v", got.Deps, wantDeps)
}
}

func TestWriteArtifactMetadataOmitsEmptyDeps(t *testing.T) {
installDir := t.TempDir()
if err := writeArtifactMetadata(installDir, "-lstandalone", nil); err != nil {
t.Fatalf("writeArtifactMetadata: %v", err)
}

data, err := os.ReadFile(filepath.Join(installDir, ".llar", "metadata.json"))
if err != nil {
t.Fatalf("read metadata.json: %v", err)
}
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
t.Fatalf("metadata.json is invalid JSON: %v", err)
}
if _, ok := raw["metadata"]; !ok {
t.Fatal("metadata.json missing metadata field")
}
if _, ok := raw["deps"]; ok {
t.Fatalf("metadata.json contains deps for standalone artifact: %s", data)
}
}

func TestOutputArtifactCopiesMetadataDirectory(t *testing.T) {
src := setupTestSrcDir(t)
dest := filepath.Join(t.TempDir(), "out")

if err := outputArtifact(src, dest, "-lfoo", nil); err != nil {
t.Fatalf("outputArtifact: %v", err)
}

data, err := os.ReadFile(filepath.Join(dest, ".llar", "metadata.json"))
if err != nil {
t.Fatalf("read output metadata.json: %v", err)
}
var got artifactMetadata
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("metadata.json is invalid JSON: %v", err)
}
if got.Metadata != "-lfoo" {
t.Fatalf("metadata = %q, want %q", got.Metadata, "-lfoo")
}
}

func TestOutputArtifactZipsMetadataDirectory(t *testing.T) {
src := setupTestSrcDir(t)
dest := filepath.Join(t.TempDir(), "out.zip")

if err := outputArtifact(src, dest, "-lfoo", nil); err != nil {
t.Fatalf("outputArtifact: %v", err)
}

r, err := zip.OpenReader(dest)
if err != nil {
t.Fatalf("open zip: %v", err)
}
defer r.Close()

for _, f := range r.File {
if f.Name != ".llar/metadata.json" {
continue
}
rc, err := f.Open()
if err != nil {
t.Fatalf("open metadata entry: %v", err)
}
defer rc.Close()
data, err := io.ReadAll(rc)
if err != nil {
t.Fatalf("read metadata entry: %v", err)
}
var got artifactMetadata
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("metadata.json is invalid JSON: %v", err)
}
if got.Metadata != "-lfoo" {
t.Fatalf("metadata = %q, want %q", got.Metadata, "-lfoo")
}
return
}
t.Fatal("zip missing .llar/metadata.json")
}

func TestOutputResult_CopyDir(t *testing.T) {
src := setupTestSrcDir(t)
dest := filepath.Join(t.TempDir(), "out")
Expand Down
Loading