Unified Configuration
Unified Configuration
Section titled “Unified Configuration”Sky tools support unified configuration through project-level config files. Define consistent settings for skytest, skylint, and other tools without repeating flags on every invocation.
Configuration Formats
Section titled “Configuration Formats”Sky supports two configuration formats:
| Format | File | Best For |
|---|---|---|
| TOML | sky.toml | Simple, static configuration |
| Starlark | config.sky (canonical) or sky.star (legacy) | Dynamic, conditional configuration |
Quick Start
Section titled “Quick Start”Create a sky.toml in your project root:
[test]timeout = "60s"parallel = "auto"prelude = ["test/helpers.star"]Create a config.sky for dynamic configuration:
def configure(): ci = getenv("CI", "") != "" return { "test": { "timeout": "120s" if ci else "30s", "parallel": "1" if ci else "auto", }, }Config File Discovery
Section titled “Config File Discovery”Sky automatically discovers configuration files by walking up the directory tree from your current working directory. The search stops at the repository root (the directory containing .git).
Discovery Order
Section titled “Discovery Order”- CLI flag:
--config=path/to/config.sky(highest priority) - Environment variable:
SKY_CONFIG=/path/to/config.sky - Directory walk: Starting from current directory, moving up to repository root
File Priority in Each Directory
Section titled “File Priority in Each Directory”config.sky(canonical Starlark config)sky.star(legacy Starlark config)sky.toml(TOML config)
If no config file is found, sensible defaults are used.
Example Directory Structure
Section titled “Example Directory Structure”my-project/├── .git/├── config.sky # <- Found and used├── src/│ └── lib.star└── tests/ └── lib_test.star # Running skytest here uses ../config.skyCLI Usage
Section titled “CLI Usage”Specifying a Config File
Section titled “Specifying a Config File”# Use a specific config fileskytest --config=ci-config.sky tests/
# Use a custom Starlark execution timeout (default: 5s)skytest --config=config.sky --config-timeout=10s tests/Environment Variable
Section titled “Environment Variable”# Set config path via environmentexport SKY_CONFIG=/path/to/config.skyskytest tests/
# Override per-commandSKY_CONFIG=local.sky skytest tests/Precedence
Section titled “Precedence”CLI flags always override config file settings:
- CLI flags (highest)
- Config file settings
- Built-in defaults (lowest)
TOML Configuration Reference
Section titled “TOML Configuration Reference”Complete Example
Section titled “Complete Example”# sky.toml - Full configuration example
[test]# Per-test timeout (Go duration format: "30s", "1m", "1h30m")timeout = "60s"
# Parallelism: "auto" (use all CPUs), "1" (sequential), or a numberparallel = "auto"
# Prelude files loaded before each test fileprelude = ["test/helpers.star", "test/fixtures.star"]
# Test function prefix (default: "test_")prefix = "test_"
# Stop on first test failurefail_fast = false
# Enable verbose outputverbose = false
[test.coverage]# Enable coverage collection (EXPERIMENTAL)enabled = false
# Fail if coverage falls below this percentagefail_under = 80.0
# Coverage output file pathoutput = "coverage.json"
[lint]# Rules or categories to enableenable = ["all"]
# Rules or patterns to disabledisable = ["native-*"]
# Treat warnings as errorswarnings_as_errors = falseTest Configuration Options
Section titled “Test Configuration Options”| Option | Type | Default | Description |
|---|---|---|---|
timeout | string | "30s" | Per-test timeout in Go duration format |
parallel | string | "" (sequential) | "auto", "1", or a specific number |
prelude | list | [] | Prelude files to load before tests |
prefix | string | "test_" | Test function name prefix |
fail_fast | bool | false | Stop on first failure |
verbose | bool | false | Enable verbose output |
Coverage Configuration Options
Section titled “Coverage Configuration Options”| Option | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable coverage collection |
fail_under | float | 0 | Minimum coverage percentage |
output | string | "coverage.json" | Coverage output file path |
Lint Configuration Options
Section titled “Lint Configuration Options”| Option | Type | Default | Description |
|---|---|---|---|
enable | list | [] | Rules or categories to enable |
disable | list | [] | Rules or patterns to disable |
warnings_as_errors | bool | false | Treat warnings as errors |
Duration Format
Section titled “Duration Format”Durations use Go’s duration format:
| Unit | Example | Description |
|---|---|---|
s | "30s" | Seconds |
m | "5m" | Minutes |
h | "1h" | Hours |
| Combined | "1h30m" | 1 hour and 30 minutes |
| Combined | "2m30s" | 2 minutes and 30 seconds |
Starlark Configuration Reference
Section titled “Starlark Configuration Reference”Starlark config files must define a configure() function that returns a dictionary.
Basic Structure
Section titled “Basic Structure”def configure(): return { "test": { "timeout": "60s", "parallel": "auto", }, "lint": { "enable": ["all"], }, }Available Builtins
Section titled “Available Builtins”Starlark config files have access to these predeclared values and functions:
| Builtin | Type | Description |
|---|---|---|
getenv(name, default="") | function | Get environment variable value |
host_os | string | Current OS ("darwin", "linux", "windows") |
host_arch | string | Current architecture ("amd64", "arm64") |
duration(s) | function | Validate and return a duration string |
struct(**kwargs) | function | Create a dict from keyword arguments |
Builtin Reference
Section titled “Builtin Reference”getenv(name, default="")
Section titled “getenv(name, default="")”Returns the value of an environment variable, or the default if not set.
def configure(): ci = getenv("CI", "") != "" github_actions = getenv("GITHUB_ACTIONS", "") == "true" custom_timeout = getenv("TEST_TIMEOUT", "30s")
return { "test": { "timeout": custom_timeout, "parallel": "1" if ci else "auto", }, }host_os
Section titled “host_os”A string containing the current operating system. Values match Go’s runtime.GOOS:
"darwin"- macOS"linux"- Linux"windows"- Windows
def configure(): # Use more parallelism on Linux servers parallel = "auto" if host_os == "linux": parallel = "8"
return { "test": { "parallel": parallel, }, }host_arch
Section titled “host_arch”A string containing the current CPU architecture. Values match Go’s runtime.GOARCH:
"amd64"- x86-64"arm64"- ARM64/Apple Silicon
def configure(): # Longer timeouts on emulated architectures timeout = "30s" if host_arch == "arm64" and host_os == "linux": timeout = "60s" # Might be running under emulation
return { "test": { "timeout": timeout, }, }duration(s)
Section titled “duration(s)”Validates that a string is a valid Go duration and returns it. Useful for catching typos early.
def configure(): # This will fail at config load time if the format is invalid timeout = duration("60s")
return { "test": { "timeout": timeout, }, }struct(**kwargs)
Section titled “struct(**kwargs)”Creates a dictionary from keyword arguments. Useful for readable nested configuration.
def configure(): return { "test": struct( timeout = "60s", parallel = "auto", coverage = struct( enabled = True, fail_under = 80, ), ), }Execution Environment
Section titled “Execution Environment”Starlark config files run in a sandboxed environment:
- No filesystem access (use
getenvinstead of reading files) - No network access
- No module loading (
load()is not available) - 5-second execution timeout by default (configurable via
--config-timeout)
Real-World Examples
Section titled “Real-World Examples”Basic TOML for Simple Projects
Section titled “Basic TOML for Simple Projects”[test]timeout = "30s"verbose = trueCI/CD Conditional Logic
Section titled “CI/CD Conditional Logic”# config.sky - Different settings for CI vs local development
def configure(): ci = getenv("CI", "") != ""
if ci: return { "test": { # Longer timeout for CI (cold caches, shared resources) "timeout": "120s", # Sequential execution for deterministic results "parallel": "1", # Fail fast to save CI minutes "fail_fast": True, # Coverage required in CI "coverage": { "enabled": True, "fail_under": 80, }, }, } else: return { "test": { # Shorter timeout for local dev "timeout": "30s", # Use all cores locally "parallel": "auto", # See all failures at once "fail_fast": False, }, }OS-Specific Settings
Section titled “OS-Specific Settings”# config.sky - Platform-specific configuration
def configure(): timeout = "30s" parallel = "auto"
# Windows often needs longer timeouts if host_os == "windows": timeout = "60s" parallel = "4" # Windows handles fewer parallel processes well
# macOS with Apple Silicon is fast if host_os == "darwin" and host_arch == "arm64": timeout = "15s" parallel = "auto"
return { "test": { "timeout": timeout, "parallel": parallel, }, }Shared Prelude with Environment-Specific Overrides
Section titled “Shared Prelude with Environment-Specific Overrides”# config.sky - Common prelude with environment tweaks
def configure(): # Common preludes for all environments preludes = [ "test/helpers.star", "test/fixtures.star", ]
# Add mock prelude in CI to avoid external dependencies if getenv("CI", "") != "": preludes.append("test/mocks.star")
return { "test": { "prelude": preludes, "timeout": getenv("TEST_TIMEOUT", "30s"), }, }Monorepo with Team-Specific Settings
Section titled “Monorepo with Team-Specific Settings”# config.sky - Different settings based on team conventions
def configure(): team = getenv("TEAM", "default")
configs = { "platform": { "timeout": "120s", "prefix": "test_", "fail_fast": False, }, "frontend": { "timeout": "30s", "prefix": "spec_", # Different naming convention "verbose": True, }, "default": { "timeout": "60s", "prefix": "test_", }, }
return { "test": configs.get(team, configs["default"]), }Troubleshooting
Section titled “Troubleshooting”Multiple Config Files Error
Section titled “Multiple Config Files Error”Error: multiple config files found in the same directory; use only one
Cause: You have more than one of config.sky, sky.star, or sky.toml in the same directory.
Solution: Remove the extra config files. If migrating from sky.star to config.sky, delete the old file.
# Check for config filesls config.sky sky.star sky.toml 2>/dev/nullConfig Timeout Error
Section titled “Config Timeout Error”Error: execution timeout when loading Starlark config
Cause: Your configure() function is taking too long, possibly due to an infinite loop.
Solution:
- Check for infinite loops in your config
- Increase the timeout:
--config-timeout=10s - Simplify complex computations
# BAD - infinite loopdef configure(): while True: pass # Never returns
# GOOD - simple conditionalsdef configure(): ci = getenv("CI", "") != "" return {"test": {"timeout": "60s" if ci else "30s"}}Invalid Duration Format
Section titled “Invalid Duration Format”Error: invalid duration "60" or invalid duration "one minute"
Cause: Duration strings must use Go’s duration format.
Solution: Use proper duration format with unit suffixes.
# BADtimeout = "60" # Missing unittimeout = "one minute" # Not a valid format
# GOODtimeout = "60s" # 60 secondstimeout = "1m" # 1 minutetimeout = "1m30s" # 1 minute 30 secondsConfig Not Being Found
Section titled “Config Not Being Found”Symptom: Default settings are used even though you have a config file.
Diagnostic steps:
-
Run with verbose mode to see which config is used:
Terminal window skytest -v tests/# Output: skytest: using config /path/to/config.sky -
Check your current directory:
Terminal window pwdls config.sky sky.star sky.toml -
Verify the config file is in the directory tree:
Terminal window # Walk up looking for configwhile [ ! -f config.sky ] && [ ! -f sky.star ] && [ ! -f sky.toml ]; docd ..[ "$PWD" = "/" ] && breakdonels config.sky sky.star sky.toml 2>/dev/null
Starlark Syntax Error
Section titled “Starlark Syntax Error”Error: executing config /path/to/config.sky: ...
Cause: Syntax error in your Starlark config file.
Solution: Check your Starlark syntax. Common issues:
# BAD - Python-style True/False must be capitalized"verbose": true # Starlark uses True
# GOOD"verbose": True
# BAD - missing commareturn { "test": { "timeout": "30s" # Missing comma "parallel": "auto" }}
# GOODreturn { "test": { "timeout": "30s", "parallel": "auto", },}configure() Must Return a Dict
Section titled “configure() Must Return a Dict”Error: configure() must return a dict, got string
Cause: Your configure() function returns something other than a dictionary.
Solution: Ensure you return a dictionary:
# BADdef configure(): return "timeout=60s" # Returns string
# GOODdef configure(): return { "test": { "timeout": "60s", }, }Migration Guide
Section titled “Migration Guide”From No Config to sky.toml
Section titled “From No Config to sky.toml”-
Create
sky.tomlin your project root:[test]timeout = "30s" -
Remove flags from your test commands:
Terminal window # Beforeskytest --timeout=30s tests/# Afterskytest tests/
From sky.toml to config.sky
Section titled “From sky.toml to config.sky”If you need conditional logic:
-
Create
config.skywith equivalent settings:def configure():return {"test": {"timeout": "30s",# Add conditional logic as needed},} -
Delete
sky.toml:Terminal window rm sky.toml
From sky.star to config.sky
Section titled “From sky.star to config.sky”The sky.star filename is still supported but deprecated. To migrate:
mv sky.star config.skyNo changes to the file contents are needed - the format is identical.
Best Practices
Section titled “Best Practices”-
Start simple: Begin with
sky.tomland migrate toconfig.skyonly when you need conditional logic. -
Commit your config: Config files should be version controlled so all team members use the same settings.
-
Document overrides: If team members need to override settings locally, document how:
Terminal window # Override for local developmentSKY_CONFIG=local.sky skytest tests/ -
Use CI detection: The
CIenvironment variable is set by most CI systems (GitHub Actions, GitLab CI, CircleCI, etc.):ci = getenv("CI", "") != "" -
Keep configs fast: Avoid complex computations in
configure(). The function should return quickly. -
Test your config: Run with
-vto verify your config is being loaded correctly:Terminal window skytest -v tests/
Understanding Starlark Dialects
Section titled “Understanding Starlark Dialects”Sky tools need to understand which “flavor” of Starlark you are using. Different tools extend vanilla Starlark with their own builtins, and sky needs to know about these extensions to provide accurate diagnostics.
What is a Dialect?
Section titled “What is a Dialect?”A “dialect” describes how a specific tool extends the core Starlark language. This is similar to how Python frameworks extend Python:
| Python Parallel | Starlark Equivalent |
|---|---|
| Django templates (Python-like but not Python) | Bazel BUILD files (Starlark with build rules) |
| Jinja2 templates | Tiltfiles (Starlark with K8s functions) |
| NumPy array extensions | Buck2’s record and enum types |
Why Dialects Matter for Configuration
Section titled “Why Dialects Matter for Configuration”When sky tools analyze your Starlark files, they need to know:
- Which builtins exist: Is
docker_build()a valid function? (Yes in Tilt, no in Bazel) - What parameters are valid: Does
cc_libraryhave anhdrsorheadersparameter? - Which lint rules apply: Should we enforce Bazel-specific conventions?
How Dialects Relate to sky.toml
Section titled “How Dialects Relate to sky.toml”The sky.toml configuration focuses on tool behavior (timeouts, parallelism, preludes), while dialect configuration in .starlark/config.json focuses on language understanding (builtins, type definitions).
# Tool behavior configuration[test]timeout = "60s"parallel = "auto"
[lint]enable = ["all"]disable = ["native-*"]{ "version": 1, "dialect": "bazel", "settings": { "reportUndefinedNames": true }}Common Dialect Scenarios
Section titled “Common Dialect Scenarios”Bazel monorepo - Default dialect detection usually works:
# sky.toml - no dialect config needed# skyls auto-detects Bazel from WORKSPACE file[lint]enable = ["all"]Tilt project - Need explicit dialect configuration:
{ "version": 1, "rules": [ {"files": ["Tiltfile"], "dialect": "tilt"} ]}Multi-tool monorepo - Map files to dialects:
{ "version": 1, "rules": [ {"files": ["Tiltfile"], "dialect": "tilt"}, {"files": ["*.bara.sky"], "dialect": "copybara"}, {"files": ["BUILD", "**/*.bzl"], "dialect": "bazel"} ]}Related Documentation
Section titled “Related Documentation”- skytest - Test runner documentation
- Configuration Schema - LSP dialect configuration
- Custom Dialects - Understanding and configuring Starlark dialects