Skip to content
Draft
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
107 changes: 107 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# LLAR Project Guide

## Design Rules

1. No useless abstraction - don't abstract if not necessary
2. Abstracted modules must have wide usage - only extract when broadly used
3. All modules must be clean - each module should only handle its own responsibility

## Project Overview

LLAR is a multi-language module manager built with XGo (gop) and xgo. It uses classfile mechanism for defining build formulas.

## XGO Classfile Mechanism

### What is Classfile?

Classfile is a DSL (Domain Specific Language) mechanism in xgo that allows defining custom file extensions with specific behavior. Each classfile extension maps to a Go struct that acts as a "class".

### How Classfile Works

1. **Registration**: Classfiles are registered via `xgobuild.RegisterProject()` in `internal/ixgo/classfile.go`

2. **File Extension Mapping**:
- `_llar.gox` -> `ModuleF` class (formula/classfile.go)
- `_cmp.gox` -> `CmpApp` class (formula/classfile.go)

3. **Code Generation**: When a `.gox` file is processed:
- The filename prefix (before `_`) becomes the struct name
- Example: `hello_llar.gox` generates struct `hello` embedding `ModuleF`
- A `MainEntry()` method is generated containing the DSL code
- A `Main()` method calls `Gopt_ModuleF_Main(this)`

### Example

Source file `hello_llar.gox`:
```gox
id "DaveGamble/cJSON"
fromVer "v1.0.0"

onRequire (proj, deps) => {
echo "hello"
}

onBuild (ctx, proj, out) => {
echo "hello"
}
```

Generated Go code:
```go
package main

import (
"fmt"
"github.com/goplus/llar/formula"
)

type hello struct {
formula.ModuleF
}

func (this *hello) MainEntry() {
this.Id("DaveGamble/cJSON")
this.FromVer("v1.0.0")
this.OnRequire(func(proj *formula.Project, deps *formula.ModuleDeps) {
fmt.Println("hello")
})
this.OnBuild(func(ctx *formula.Context, proj *formula.Project, out *formula.BuildResult) {
fmt.Println("hello")
})
}

func (this *hello) Main() {
formula.Gopt_ModuleF_Main(this)
}

func main() {
new(hello).Main()
}
```

### Key Points

1. **Struct Name Derivation**: The struct name comes from `strings.Cut(filename, "_")` - the part before the first underscore

2. **Class Entry Point**: `Gopt_<ClassName>_Main` is the classfile entry point that:
- Calls `MainEntry()` to execute DSL code
- Initializes the embedded `gsh.App`

3. **Error Handling**: If a file doesn't match any registered classfile pattern (wrong suffix), `BuildFile` returns "undefined" errors for DSL functions

## Build & Test

```bash
# Run tests with required ldflags (Go 1.24+)
go test -ldflags="-checklinkname=0" ./...

# Run specific package tests with coverage
go test -ldflags="-checklinkname=0" -cover ./internal/formula/...
```

## Project Structure

- `formula/` - Classfile definitions (ModuleF, CmpApp, Context, Project, etc.)
- `internal/formula/` - Formula loading and interpretation logic
- `internal/ixgo/` - ixgo classfile registration
- `mod/` - Module and version handling
28 changes: 20 additions & 8 deletions cmd/llar/internal/make.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

var makeVerbose bool
var makeOutput string
var makeMatrix string

// newRemoteStore creates the remote formula store. Overridable for testing.
var newRemoteStore = func() (repo.Store, error) {
Expand All @@ -48,6 +49,8 @@ var makeCmd = &cobra.Command{
func init() {
makeCmd.Flags().BoolVarP(&makeVerbose, "verbose", "v", false, "Enable verbose build output")
makeCmd.Flags().StringVarP(&makeOutput, "output", "o", "", "Output path (directory or .zip file)")
makeCmd.Flags().StringVar(&makeMatrix, "matrix", "", "Override matrix combination (internal)")
_ = makeCmd.Flags().MarkHidden("matrix")
rootCmd.AddCommand(makeCmd)
}

Expand All @@ -68,13 +71,16 @@ func runMake(cmd *cobra.Command, args []string) error {
makeOutput = abs
}

matrix := formula.Matrix{
Require: map[string][]string{
"os": {runtime.GOOS},
"arch": {runtime.GOARCH},
},
matrixStr := makeMatrix
if matrixStr == "" {
matrix := formula.Matrix{
Require: map[string][]string{
"os": {runtime.GOOS},
"arch": {runtime.GOARCH},
},
}
matrixStr = matrix.Combinations()[0]
Comment on lines +76 to +82
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This logic for creating a default matrix from the current runtime OS and architecture is duplicated in cmd/llar/internal/test.go. To improve maintainability and avoid code duplication, this logic should be extracted into a single, shared helper function. The defaultRuntimeMatrix() function in test.go could be moved to a shared location and reused here.

}
matrixStr := matrix.Combinations()[0]

// Set up remote formula store (always needed for deps)
remoteStore, err := newRemoteStore()
Expand All @@ -83,7 +89,7 @@ func runMake(cmd *cobra.Command, args []string) error {
}

if !isLocal {
return buildModule(ctx, remoteStore, pattern, version, matrixStr)
return buildModuleWithRunTest(ctx, remoteStore, pattern, version, matrixStr, false)
}

// Resolve local pattern
Expand All @@ -109,7 +115,7 @@ func runMake(cmd *cobra.Command, args []string) error {
if ver == "" {
ver = version // global @version from arg
}
if err := buildModule(ctx, store, m.Path, ver, matrixStr); err != nil {
if err := buildModuleWithRunTest(ctx, store, m.Path, ver, matrixStr, false); err != nil {
return err
}
}
Expand All @@ -118,6 +124,11 @@ func runMake(cmd *cobra.Command, args []string) error {

// buildModule loads and builds a single module.
func buildModule(ctx context.Context, store repo.Store, modPath, version, matrixStr string) error {
return buildModuleWithRunTest(ctx, store, modPath, version, matrixStr, false)
}

// buildModuleWithRunTest loads and builds a single module.
func buildModuleWithRunTest(ctx context.Context, store repo.Store, modPath, version, matrixStr string, runTest bool) error {
mods, err := modules.Load(ctx, module.Version{Path: modPath, Version: version}, modules.Options{
FormulaStore: store,
})
Expand Down Expand Up @@ -151,6 +162,7 @@ func buildModule(ctx context.Context, store repo.Store, modPath, version, matrix
buildOpts := build.Options{
Store: store,
MatrixStr: matrixStr,
RunTest: runTest,
}
if makeOutput != "" {
tmpDir, err := os.MkdirTemp("", "llar-make-*")
Expand Down
Loading
Loading