Skip to content

Proposal: pack Directive for gox.mod

xushiwei edited this page Apr 12, 2026 · 6 revisions

1. Summary

This proposal introduces a new pack directive to gox.mod, enabling classfile framework packages to declare that an asset directory should be automatically packed (via xgo pack) before an XGo project is run, built, or tested. The directive specifies both the target directory and the exact index file name to use, eliminating any ambiguity about configuration format. The pack directive is project-scoped — it follows a project declaration and applies exclusively to that project, just like class and import. At most one pack directive is permitted per project block.


2. Motivation

2.1 Background

xgo pack merges a tree of scattered index.* configuration files into a single index_pack.* file, eliminating many small filesystem or network round-trips when loading asset metadata at runtime. This is especially important for web-deployed XGo applications (e.g., games built on the spx framework) where latency is sensitive to the number of individual resource fetches.

2.2 Problem

Currently, calling xgo pack is a manual, out-of-band step. There is no mechanism for a classfile framework to declare that packing is required, or to specify which directory should be packed. As a result:

  • Developers frequently forget to run xgo pack before xgo run, leading to stale or missing index_pack.* files.
  • CI pipelines must be configured separately and redundantly to invoke xgo pack, duplicating knowledge that the framework itself already possesses.
  • New users following a framework's getting-started guide face an unexpected extra step with no clear diagnostic when they skip it.

Additionally, the existing xgo pack command must probe for index.json, index.yml, and index.yaml in each directory it visits, because it does not know in advance which format a given project uses. A framework author always knows the exact format — encoding this in gox.mod avoids the probing entirely.

2.3 Solution

The pack directive is placed inside a project block — alongside class and import — and takes two arguments: the directory to pack and the exact index file name. The toolchain uses this information to invoke xgo pack automatically before compilation, and passes the explicit file name so that xgo pack never needs to guess the format.


3. Proposal

3.1 New Directive: pack

pack <directory> <indexfile>

Scope: Project-scoped. Must appear after a project declaration and before the next project declaration (or end of file). It is a sibling of class and import within the same project block.

Constraint: At most one pack directive is permitted per project. A second pack within the same project block is a parse error.

Arguments:

Argument Required Description
directory Required A path relative to the project root (the directory passed to xgo run/xgo build) that xgo pack should be invoked on.
indexfile Required The exact file name of the index files in the tree (e.g. index.json, index.yaml). This name is used uniformly at every level of the directory tree.

3.2 Updated Directive Table

Directive Scope Purpose
xgo File Declares the required XGo version
project File Declares a classfile project entry point
class Project Declares a work class within the current project
import Project Declares auto-imported packages for the current project
pack Project Declares the asset directory and index file name to pack before running the project

3.3 Syntax Example

xgo 1.6.0
 
project main.spx Game github.com/goplus/spx/v2 math
 
class -embed *.spx SpriteImpl
pack assets index.json

This tells the XGo toolchain: when running any project that uses the spx classfile framework, invoke xgo pack on the assets directory, treating index.json as the index file at every level of the tree.

3.4 Multiple Projects in One gox.mod

When a gox.mod declares multiple project blocks, each may independently declare its own pack (or none at all):

xgo 1.6.0
 
project main.spx Game github.com/goplus/spx/v2 math
 
class -embed *.spx SpriteImpl
pack assets index.json
 
project .yap YapApp github.com/goplus/yap
 
import "github.com/goplus/yap/test"
# no pack directive — this project needs no asset packing

The toolchain packs only the directory declared for the matched project's classfile type.


4. Toolchain Behavior

4.1 Trigger Points

The pack directive is honored by the following toolchain commands:

Command Behavior
xgo run Packs before running (subject to skip logic described in §4.3).
xgo build Packs before building (subject to skip logic described in §4.3).
xgo install Packs before installing (subject to skip logic described in §4.3).
xgo test Packs before testing (subject to skip logic described in §4.3).

The pack step is not triggered by xgo fmt, xgo vet, xgo mod, or other non-compilation commands.

4.2 Execution Order

For any invocation that triggers packing, the toolchain follows this order:

  1. Resolve the project's go.mod and identify all //xgo:class dependencies.
  2. For each such dependency, locate and parse its gox.mod.
  3. Identify which project block applies to the current project's source files (based on file extension matching).
  4. If that project block contains a pack directive, apply the skip logic (§4.3).
  5. If packing proceeds, resolve the target directory relative to the project root and invoke xgo pack <directory>, passing <indexfile> as the index file name.
  6. Proceed with compilation.

4.3 Skip Logic and --pack Flag

To keep xgo run fast in the normal development loop, the toolchain applies the following rule before invoking xgo pack:

If a packed output file (index_pack.<ext>) already exists at the root of the target directory, the pack step is skipped.

The extension <ext> is derived from <indexfile> — for example, index.json implies index_pack.json.

This is an existence check only: the toolchain does not inspect modification times or compare file contents. The rationale is that asset trees are typically stable during a development session, and the overhead of a full freshness check on every xgo run is unnecessary for most workflows.

Forcing a pack run: To override the skip and unconditionally re-pack, pass the --pack flag:

xgo run --pack
xgo build --pack

The --pack flag bypasses the existence check and always invokes xgo pack, even when index_pack.* already exists. This is useful after adding, removing, or renaming asset files.

Summary of skip behavior:

Condition Default (xgo run) With --pack (xgo run --pack)
index_pack.* does not exist Runs xgo pack Runs xgo pack
index_pack.* already exists Skips xgo pack Runs xgo pack

4.4 Path Resolution

The directory argument is always resolved relative to the project root — that is, the directory explicitly passed to (or inferred by) xgo run/xgo build/etc. as the project to compile. This is the directory that contains the project's source files (e.g. main.spx), which may or may not be the same directory as go.mod.

Example:

xgo run /home/user/mygame

With the directive pack assets index.json, the toolchain resolves the pack target as:

xgo pack /home/user/mygame/assets

If the user runs from a subdirectory:

xgo run /home/user/mygame/levels/world1

Then the pack target becomes:

xgo pack /home/user/mygame/levels/world1/assets

This design ensures that each project instance packs its own co-located assets, independent of where go.mod lives in the module hierarchy.

4.5 Error Handling

Condition Behavior
Target directory does not exist Non-fatal warning; packing is skipped.
Target directory contains no <indexfile> at its root Follows existing xgo pack behavior (warning; no output produced).
xgo pack fails on the target directory Fatal error; compilation does not proceed; error message includes the directory path and the underlying pack error.

5. gox.mod Grammar Changes

PackDir is added as a project-level directive alongside ClassDir and ImportDir:

GoxMod       = XgoDir? ProjectBlock*
ProjectBlock = ProjectDir ProjectItem*
ProjectItem  = ClassDir | ImportDir | PackDir
 
PackDir      = "pack" Directory IndexFile
Directory    = string   // relative path, forward-slash separated, no ".." components
IndexFile    = string   // plain file name, no path separators (e.g. "index.json")

Constraints enforced at parse time:

  • A pack directive appearing before any project declaration is a parse error.
  • A second pack directive within the same project block is a parse error; the error message identifies the line numbers of both occurrences.
  • .. path components in Directory are not permitted.
  • Path separators (/ or \) in IndexFile are not permitted; IndexFile must be a plain file name.

6. Real-World Example: SPX Game Framework

The updated gox.mod for spx would read:

xgo 1.6.0
 
project main.spx Game github.com/goplus/spx/v2 math
 
class -embed *.spx SpriteImpl
pack assets index.json

A typical spx project layout:

mygame/
├── go.mod              # requires github.com/goplus/spx/v2 //xgo:class
├── main.spx            # project file → compiled as Game
├── Cat.spx             # work file → compiled as SpriteImpl
├── Dog.spx             # work file → compiled as SpriteImpl
└── assets/
    ├── index.json           # Stage/Game config  ← pack root
    ├── index_pack.json      # generated by xgo pack (once present, skipped on next run)
    ├── sprites/
    │   ├── Cat/
    │   │   └── index.json
    │   └── Dog/
    │       └── index.json
    └── sounds/
        └── bgm/
            └── index.json

First xgo runindex_pack.json does not yet exist:

  1. Reads go.mod, finds spx/v2 annotated //xgo:class.
  2. Loads spx/v2's gox.mod, matches the project block for *.spx files, finds pack assets index.json.
  3. Resolves assets relative to the project root (mygame/); index_pack.json is absent → proceeds.
  4. Runs xgo pack mygame/assets with index file index.json, producing assets/index_pack.json.
  5. Compiles and runs the project.

Subsequent xgo runindex_pack.json already exists:

  • Step 3: index_pack.json is present → skips xgo pack.
  • Proceeds directly to compilation.

After adding a new sprite asset:

xgo run --pack   # forces re-pack to include the new sprite config

7. Interaction with xgo pack -t (CI Mode)

The pack directive integrates naturally with the existing -t (test) mode of xgo pack. CI pipelines that want to verify rather than regenerate packed files can still call xgo pack -t ./assets independently. The pack directive in gox.mod does not interfere with this workflow; it only affects the behavior of xgo run and related build commands.


8. Alternatives Considered

8.1 File-Level pack Directive

An earlier design treated pack as a file-level directive (a sibling of project rather than a child of it). This was rejected because the asset directory and index format are properties of a specific classfile project type, not of the framework module as a whole. A file-level directive would also be ambiguous in gox.mod files with multiple project declarations.

8.2 Single-Argument pack <directory> (No Index File)

An earlier revision of this proposal used pack <directory> without an explicit index file name, leaving xgo pack to probe for index.json, index.yml, or index.yaml. This was rejected because:

  • A framework author always knows the exact format their framework uses; probing is unnecessary.
  • Probing adds I/O overhead, particularly in deeply nested asset trees or on network-mounted filesystems.
  • An explicit indexfile argument makes the contract between the framework and the toolchain precise and self-documenting.

8.3 Staleness-Based Skip (Modification Time Check)

An earlier revision performed a modification-time comparison before deciding whether to skip xgo pack. This was replaced by the simpler existence-only check because:

  • Traversing the entire asset tree to collect modification times imposes I/O cost on every xgo run, even when assets are unchanged.
  • Most developers have a clear mental model of when assets change and can use --pack explicitly when needed.
  • The existence check covers the most common case (first run after a fresh checkout) with zero extra overhead for all subsequent runs.

8.4 Convention-Based Auto-Detection

The toolchain could automatically scan for assets/ (or any directory containing index files) and pack them without any explicit directive. This was rejected because it is non-deterministic, imposes implicit cost on all projects, and couples the toolchain to naming conventions that frameworks should own.


9. Backward Compatibility

The pack directive is purely additive. Existing gox.mod files without the directive are unaffected. The --pack flag is also new and has no effect on projects whose gox.mod contains no pack directive.


10. Summary of Changes

Area Change
gox.mod grammar New project-scoped pack <directory> <indexfile> directive; at most one per project block
gox.mod parser Parse and validate pack directives; error on duplicate within same project; reject .. in directory and path separators in indexfile
xgo run / xgo build / xgo install / xgo test Invoke xgo pack when index_pack.* is absent; skip when it already exists
--pack flag New flag for xgo run, xgo build, xgo install, xgo test; forces xgo pack regardless of whether index_pack.* exists
Error handling Fatal error on pack failure; warning on missing directory
Documentation Update gox.mod reference; update xgo run command reference

Clone this wiki locally