How KCL Module Loading Works: Search Path Resolution and Module Discovery
KCL resolves module imports through a multi-phase loader that converts dotted import paths to file system locations using an ordered search path consisting of the entry directory, the KCL_PATH environment variable, the built-in standard library, and the current working directory.
KCL (Kusion Configuration Language) uses a sophisticated module loading system to discover and compile dependencies before evaluation begins. Understanding how the module search path is constructed and how import resolution works is essential for organizing large configuration projects. The loading mechanism is implemented in the crates/loader directory of the KCL source code and centers around the KCLModuleCache structure for caching parsed modules.
The Module Loading Architecture
The KCL compiler delegates dependency discovery to a dedicated loader crate that operates before the evaluator runs. This architecture ensures that all imported modules are parsed and cached before execution begins.
KCLModuleCache and Entry Points
The KCLModuleCache structure, defined in crates/loader/src/lib.rs, serves as the central registry for parsed modules. It stores Module objects keyed by their absolute file paths to avoid re-parsing the same file multiple times during a compilation session.
The primary entry point is the load_program function, which accepts the entry file path and orchestrates the loading process. This function initiates an AST traversal using the ImportVisitor to identify all import statements within the entry module.
The Three-Phase Loading Process
The loader executes in distinct phases:
- Parse the entry module — The file supplied to the CLI (
kcl run <file>) is parsed into an AST. - Collect import statements — The
ImportVisitorwalks the AST and records every import clause. - Resolve dependent modules — For each import, the
resolve_importfunction (located incrates/loader/src/resolver.rs) probes the search path, parses found files intoModuleobjects, and inserts them intoKCLModuleCache.
Circular imports are detected during phase three and reported as diagnostics before evaluation begins.
How the Module Search Path is Constructed
The module search path is an ordered list of directories that the resolver scans when locating a module file. The KCLSearchPath struct in crates/loader/src/search_path.rs constructs this list at startup.
Search Path Priority Order
KCL resolves imports by checking directories in the following strict priority:
- The directory containing the entry file — Relative imports (e.g.,
import .subdir.mod) resolve against this location first. KCL_PATHenvironment variable — A platform-specific delimited list (:on Unix,;on Windows).- Built-in standard library — Compiled into the binary and referenced as
kcl/std(located undercrates/stdlib). - Current working directory — Used when the entry file is specified as a relative path.
KCL_PATH Environment Variable
Developers can extend the search path by setting the KCL_PATH variable:
export KCL_PATH=$HOME/.kcl/packages:/opt/kcl/stdlib
kcl run project/main.k
The KCLSearchPath::new implementation constructs the directory list as follows:
impl KCLSearchPath {
pub fn new(entry_dir: &Path) -> Self {
let mut dirs = vec![entry_dir.to_path_buf()];
if let Ok(env) = std::env::var("KCL_PATH") {
dirs.extend(env.split(PATH_SEPARATOR).map(PathBuf::from));
}
dirs.push(PathBuf::from(crate::stdlib::STD_LIB_PATH));
dirs
}
}
Import Resolution Mechanics
When the loader encounters an import statement, it must transform the dotted module name into a concrete file system path.
From Dotted Names to File Paths
The resolve_import function in crates/loader/src/resolver.rs performs the following conversion:
- The import string
my_pkg.utilsbecomes the relative pathmy_pkg/utils.k. - The resolver iterates over the ordered search path directories.
- The first directory containing the target file wins.
For example, given import helper in project/main.k with KCL_PATH set to $(pwd)/lib:
- Check
project/helper.k— not found. - Check
project/lib/helper.k— found and parsed.
Circular Import Detection
The loader tracks module loading state within KCLModuleCache to detect circular dependencies. If module A imports B, and B attempts to import A during the same loading phase, the resolver identifies the cycle and emits a diagnostic error rather than entering infinite recursion.
Practical Configuration Example
Consider a project with the following layout:
project/
├── main.k
├── my_pkg/
│ └── utils.k
└── lib/
└── helper.k
main.k contains:
import my_pkg.utils # Resolves to ./my_pkg/utils.k
import helper # Resolves via KCL_PATH to ./lib/helper.k
Execution:
export KCL_PATH=$(pwd)/project/lib
kcl run project/main.k
The loader resolves my_pkg.utils from the entry directory and helper from the KCL_PATH extension. If a module cannot be located, the compiler produces an error message specifying the import source and the search path locations checked:
Cannot find the module 'nonexistent' from /path/to/project/main.k
Summary
- KCLModuleCache in
crates/loader/src/lib.rsstores parsed modules keyed by absolute paths to prevent duplicate parsing. - The search path priority is: entry file directory →
KCL_PATH→ built-in standard library → current working directory. - Import resolution converts dotted names (e.g.,
pkg.mod) to file paths (e.g.,pkg/mod.k) and scans the search path sequentially. - The
KCL_PATHenvironment variable uses colon or semicolon delimiters to specify additional module directories. - Circular imports are detected early in the loading phase and reported as errors before evaluation.
Frequently Asked Questions
How does KCL handle relative imports?
Relative imports (starting with .) are resolved against the directory containing the file that contains the import statement. This allows modules to import sibling or child modules without relying on the global KCL_PATH environment variable.
What file extension does KCL use for module files?
KCL module files use the .k extension. When resolving an import like import my_pkg.utils, the loader searches for my_pkg/utils.k within the search path directories.
Can I override the standard library location?
The built-in standard library path is compiled into the binary and referenced as crate::stdlib::STD_LIB_PATH in crates/loader/src/search_path.rs. While you cannot override this specific entry, you can place modules with identical names earlier in the search path (via the entry directory or KCL_PATH) to shadow standard library modules.
What happens if the same module is imported from multiple files?
The KCLModuleCache structure ensures that each absolute file path is parsed only once per compilation. Subsequent imports of the same module retrieve the cached Module object, improving performance and ensuring consistent state across the program.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →