Loading Files
Use Starlark's load statement to share values and rules between build files. xx supports built-in package imports and local file imports.
Relative Imports
A path without a special prefix is relative to the file containing the load, not relative to the entrypoint and not relative to the current shell directory.
.xx/
|-- main.star
|-- one/
| |-- rules.star
| `-- shared.star
`-- two/
|-- rules.star
`-- shared.star
Inside .xx/one/rules.star, this loads .xx/one/shared.star:
load("shared.star", "value")
The same statement inside .xx/two/rules.star loads .xx/two/shared.star.
Project-root Imports
Paths beginning with // are relative to project root:
load("//build/common.star", "common_rule")
This resolves to build/common.star, even when the importing file is nested below .xx/.
Importing and Renaming Symbols
Import a symbol under the same name:
load("rules.star", "build_app")
Rename a symbol locally when names would conflict:
load("rules.star", app_rule="build_app")
Only requested symbols enter the importing file's scope.
Built-in Packages
A path beginning with @ selects a versioned built-in package:
load("@go@1.26.5", "go_binary")
load("@os@1", "env_set", "run")
The form is always @package@version. Missing versions, unknown packages, and unsupported versions are errors.
Built-in symbols are scoped to the file that loads them. If main.star loads go_binary and then loads rules.star, rules.star cannot use go_binary unless it also loads the Go package:
# rules.star
load("@go@1.26.5", "go_binary")
def build_tool():
return go_binary(entry_point="cmd/tool")
This explicit scope makes each file's dependencies visible at its top.
Evaluation and Caching
Within one entrypoint execution, xx resolves a file to its canonical path and evaluates it once. If two modules load the same shared file, both receive the same exported globals without running its top-level statements twice.
Built-in package loads are also cached by their complete specifier during that execution. Different version specifiers remain different packages.
Each entrypoint passed to xx run has its own module evaluation. Shared tool setup, such as provisioning one Go version, can still be reused across those entrypoints.
Import cycles are rejected. For example, a.star loading b.star while b.star loads a.star produces a cycle error.
Path Rules and Project Boundary
Local import paths must:
- use forward slashes, including on Windows;
- name an existing regular file;
- be relative or start with
//; - remain inside project root after cleaning and resolving symlinks.
Absolute paths are unsupported. .. may navigate within the project, but cannot escape it. A symlink inside the project that points to a file outside the project is also rejected.
These rules make loaded source part of the project instead of an undeclared dependency on an arbitrary machine path.
Entrypoints Versus Modules
xx run build searches .xx/ for exactly one of:
build.star
build.build
build.starlark
build.bzl
build.bazel
If none exists, xx reports a missing entrypoint. If multiple supported extensions exist for the same name, xx reports an ambiguous entrypoint.
Loaded modules can use any location inside project root. They do not need to live in .xx/ and do not become command-line entrypoints merely because they use a supported extension.