本文へ移動
cccskills
無料GitHub で公開

Module and Import System

Comprehensive guide to the Chemical module and import resolution system — how chemical.mod packages map to build.lab jobs, how local/package/remote/conditional imports become module dependencies, remote git import download and version pinning, link libraries, and the per-module AST records that flow through compilation. Load when working on import resolution, chemical.mod conversion, remote imports, the dependency graph, or module metadata.

日本語の概要は準備中です。原文の説明を表示しています。

chemicallang/chemicalスター 31(GitHub 31 + cccskills 0)

実行スコア 未検証

インストール方法を見る

含まれるファイル(1)

  • SKILL.md30.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Module and Import System

Chemical's module system turns declarative chemical.mod / build.lab files into a graph of LabModule objects, each compiled independently and linked into a job. This skill documents the whole path: module declaration → import resolution → dependency graph → job execution. The user-facing syntax lives in chemical_mod; this skill focuses on how the compiler implements it.

Overview: The Module Model

A build is a tree of modules: one compilation unit with a name and scope, its own source files, its own ModuleScope symbol namespace, and a list of dependencies.

ConceptC++ typeMeaning
Package kindPackageKind (compiler/lab/PackageKind.h:7)Library (main mangled) vs Application (main is entry)
Module kindLabModuleType (compiler/lab/LabModuleType.h:5)Files, CFile, CPPFile, ObjFile, Directory
ModuleLabModule (compiler/lab/LabModule.h:17)Name/scope, paths, direct files, dependencies, link info
Module namespaceModuleScope (ast/structures/ModuleScope.h:10)scope_name + module_name + back-pointer to LabModule
Module registryModuleStorage (compiler/lab/ModuleStorage.h:10)Owns every LabModule, indexed by scope:name
Dependency edgeModuleDependency (compiler/lab/import_model/ModuleDependency.h:9){ LabModule* module; DependencySymbolInfo* info; }
Remote importRemoteImport (compiler/lab/import_model/RemoteImport.h:15)Git repo request + merged requesters + built module cache
Dependency symbolsDependencySymbolInfo (compiler/lab/import_model/DependencySymbolInfo.h:8)Which symbols an import exposes (alias/items)
Import itemImportSymbol (compiler/lab/import_model/ImportSymbol.h:8)One symbol path + alias

A module maps to a job only at the top: an executable/library job holds a flat list of module dependencies, and ModuleStorage (per LabBuildCompiler) ensures a module built once is reused.

chemical.mod ─(ModToLabConverter)→ build.lab ─(TinyCC JIT)→ build() → LabModule tree
                                          job.dependencies → flatten_dedupe_sorted → [dep, ..., root]

How modules become jobs

  1. build_module_build_file_no_alloc (LabBuildCompiler.cpp:3436) JIT-builds the root .mod/.lab and calls its generated build(ctx, job), which returns the root LabModule* (:3493).
  2. process_remote_imports (:4735) downloads pending git imports and attaches them.
  3. flatten_dedupe_sorted(exe->dependencies) (:1643) yields a dependency-first module vector.
  4. Each module is parsed, sym-resolved, type-verified, translated and codegen'd (process_module_tcc/process_module_gen, :776/:1044). With --arg-interpret, an Interpretation job builds the same graph but interprets main() (do_interpretation_job, :4788).

Key Files

FileRole
compiler/lab/mod_conv/ModToLabConverter.cppchemical.mod → build.lab code generation (convertToBuildLab, :89)
compiler/lab/LabBuildCompiler.cppBuild orchestration, create_module_for_dependency (:2301), build_module_from_mod_file (:2497), remote imports (:3830–:4786)
compiler/lab/LabBuildContext.cpp/.hnew_module, build_exe, module registration into ModuleStorage
compiler/lab/import_model/*ImportSymbol, ModuleDependency, RemoteImport, DependencySymbolInfo
compiler/lab/LabModule.hLabModule struct (paths, deps, direct files, options)
compiler/lab/LabModuleOptions.hMirrors the Chemical ModuleOptions struct (safety/checks/opt level)
compiler/lab/ModuleStorage.hOwns and indexes all modules
compiler/lab/LabJob.hJob deps, remote_imports, remote_import_index, built_files, conflict strategy
preprocess/ImportPathHandler.cpp/.hresolve_mod_dep_import (:266), @ directive + libs/ resolution
compiler/processor/ModuleFileData.hParsed .mod fields (sources_list, link_libs, c_files, ...)
compiler/processor/ASTFileMetaData.hPer-file id, module, abs path, import stmt, result
compiler/processor/ASTFileResult.hFull file parse/analysis result
compiler/processor/ModuleDependencyRecord.h{ module_dir_path } intermediate dependency record
compiler/ASTProcessor.cppdetermine_module_files (:133), figure_out_direct_imports (:866), sym_res_module dep declaration (:430)
compiler/ASTCompiler.cppshallow_dedupe_sorted (:1620) for direct-dep processing order
lang/libs/lab/src/lab.chChemical BuildContext API: new_package, add_dependency, fetch_mod_dependency, ImportRepo
parser/statements/Import.cppImport statement parser (items, alias, version/subdir/branch/commit/orphan, if)
parser/statements/LexStatement.cppsource/link/link c/ship/include mod parsers

chemical.mod → build.lab

convertToBuildLab(const ModuleFileData&, std::ostream&) (compiler/lab/mod_conv/ModToLabConverter.cpp:89) is a source-to-source code generator. It walks ModuleFileData::scope.body.nodes and emits Chemical code, then that code is JIT-compiled by TinyCC exactly like a hand-written build.lab.

The generated file has a fixed shape — it imports lab/std, imports each local/package dependency's build.lab under a generated identifier (__mod_N_stmt, or __mod_<alias> when aliased), and defines build() which creates the package and attaches dependencies:

import lab; import std;
import "<path>/build.lab" as __mod_0_stmt;   // relative;  "@<pkg>/build.lab" for packages

public func build(ctx : *mut BuildContext, __chx_job : *mut LabJob) : *mut Module {
    var __curr_lab_path = lab::get_my_path();
    const __chx_already_exists = ctx.get_cached(__chx_job, &__curr_lab_path);
    if(__chx_already_exists != null) { return __chx_already_exists; }
    const deps : []ModuleDependency = [
        ModuleDependency { module: __mod_0_stmt.build(ctx, __chx_job),
                           info: &mut DependencySymbolInfo { ... } },
    ];
    const mod = ctx.new_package(ModuleType.Directory, PackageKind.Application,
        "<scope>", "<name>", std::span<ModuleDependency>(deps, N));
    ctx.set_cached(__chx_job, &__curr_lab_path, mod);
    // then: fetch_mod_dependency | add_path | c_file_module + add_dependency
    //       link_system_lib / add_lib_search_path | ship_file | options | interfaces
    return mod;
}

Conversion rules (with source line refs)

.mod constructGenerated build.labRef
import "../lib"import "../lib/build.lab" as __mod_N_stmt + ModuleDependency entryModToLabConverter.cpp:98-125, :143-166
import std (native/pkg)import "@std/build.lab" as ... (note the @)ModToLabConverter.cpp:107-110
import { a.b as c } from "..."DependencySymbolInfo { symbols: [ImportSymbol{parts, alias}], alias, location }ModToLabConverter.cpp:54-87
remote importskipped in the import/deps pass; emitted later as ctx.fetch_mod_dependencyModToLabConverter.cpp:103-106, :171-230
source "src"{ var rel_path = lab::rel_path_to("src"); ctx.add_path(mod, rel_path.to_view()); }ModToLabConverter.cpp:233-249
link c "f.c"ctx.c_file_module(scope, name_cfile_N, path, ...) + ctx.add_dependency(job, c_file_mod, null)ModToLabConverter.cpp:251-306
link "mylib"ctx.link_system_lib(__chx_job, "mylib", mod)ModToLabConverter.cpp:308-334
link path "./libs"ctx.add_lib_search_path(__chx_job, rel_path.to_view(), null)ModToLabConverter.cpp:320-328
ship "res"ctx.ship_file(__chx_job, rel_path.to_view())ModToLabConverter.cpp:336-355
option safety = ...__mod_opts.<key> = <value>;ModToLabConverter.cpp:357-381
interface Xctx.add_compiler_interface(mod, "X")ModToLabConverter.cpp:383-388

The import identifier is derived by writeAsIdentifier (ModToLabConverter.cpp:21): __mod_<topLevelAlias> when an alias exists, otherwise __mod_<index>_stmt. Conditions are emitted by writeIfConditional (:35) as __chx_job.getTarget().<flag> (with ! and &&/|| supported).

The get_cached/set_cached pair is what makes the generated build() idempotent: two importers of the same .mod path share one LabModule.

There is a second, runtime path: when a .mod is imported by a .ch file or another .lab, ASTProcessor::import_mod_file_as_lab (compiler/ASTProcessor.cpp:950) converts it in-memory to build.lab text and parses that.

The Dependency Graph

Import classification

At the AST level, every import is an ImportStatement (ast/statements/Import.h:47) with ImportStatementKind = NativeLib or LocalOrRemote, plus remote metadata. Resolution predicates decide its fate:

PredicateFile:lineTrue for
isNativeLibImport()Import.h:155import std (keyword form)
isFileImport()Import.h:202source ends in .ch/.lab/.mod
isRemoteImportPredict()Import.h:162has version/commit/branch/subdir, or http(s):///git@, or non-relative path
isRemoteModuleImport()Import.h:209remote and not a .lab/.mod file
isLocalModuleImport()Import.h:217native lib, or relative non-file path
isLocalFileImport()Import.h:226local .ch/.lab/.mod file

Import syntax is parsed in parser/statements/Import.cpp: brace {...} from "src" form (:92), string "path" as alias form (:126), identifier form (:131), then global alias (:170), remote metadata (parseImportMetadata, :57), then if guard (:179).

Resolution entry point

ImportPathHandler::resolve_mod_dep_import (preprocess/ImportPathHandler.cpp:266) is the single dispatcher used by both .mod loading (LabBuildCompiler.cpp:2651) and build.lab loading (:2832). It returns an ImportedModuleDepResult (ImportPathHandler.h:19) with either:

  • directory_path — a local module directory to build, or
  • error_message — hard error, or
  • both empty → skipped (import disabled by its if condition).

For a remote module import it converts the statement into a RemoteImportCBI (ImportPathHandler.cpp:287-316) and calls BuildContextfetch_mod_dependency — remote imports never produce a directory_path; they are queued on the job instead.

Resolving each import type to a concrete module

Import typeResolutionResult
Relative "../lib"resolve_sibling(base_path, sourcePath) (ImportPathHandler.cpp:337-339)directory path → create_module_for_dependency
Native/package import stdresolve_native_lib("std") → libs/std (ImportPathHandler.cpp:96, :332)package directory
@ directive "@system/..." / "@std/..."replace_at_in_path (:215), get_mod_identifier_from_import_path (:80)system header or libs/<name>/...
Conditionalresolve_target_condition first (:276-285)skipped or resolved
Remote gitBuildContextfetch_mod_dependency (:316)queued on job, downloaded later

For local/package import resolution, create_module_for_dependency (LabBuildCompiler.cpp:2301) checks in order:

  1. <dir>/build.lab exists → JIT it and call chemical_lab_build(&context, job) (:2307-2351).
  2. Else <dir>/chemical.mod exists → build_module_from_mod_file (:2355-2380).
  3. Else → hard error: "doesn't contain a 'build.lab' or 'chemical.mod'" (:2384).

Both branches cache the result in job->built_files keyed by canonical path (:2314-2321, :2347-2348), so the same dependency is never built twice within a job.

build_module_from_mod_file (LabBuildCompiler.cpp:2497):

  1. Parses the .mod into a ModuleFileData (import_chemical_mod_file).
  2. context.new_module(scope_name, module_name, package_kind) (:2530).
  3. Appends resolved source paths, applying resolve_target_condition_nullable (:2533-2542).
  4. Creates one CFile module per link c file (with include dirs) and adds it to the job (:2558-2591).
  5. Applies link libs / search paths, ship files, and ModuleOptions (:2593-2633).
  6. Two-phase dependency resolution (:2635-2676): first parse all ImportStmts into ModuleDependencyRecords, then call create_module_for_dependency — because building a dependency can clear the module allocator that holds the parsed .mod nodes.
  7. Adds each LabModule* to module->dependencies and clears allocators.

Dependency symbol visibility (which symbols an import exposes)

Each ModuleDependency may carry a DependencySymbolInfo (DependencySymbolInfo.h:8): symbols (selected paths + aliases), alias (whole-module alias), location. During symbol resolution, ASTProcessor::sym_res_module (compiler/ASTProcessor.cpp:437-464) walks direct dependencies, builds a ChildrenMapNode from each dependency module's public nodes, and declareChildren (:396-412) decides visibility:

  • dep.info == nullptr → declare all symbols (declare_or_shadow).
  • info->alias non-empty → declare the whole module under that name (does not shadow).
  • info->symbols non-empty → declare only those symbols via declareImportedSymbol.
  • otherwise → declare all symbols.

The same logic is implemented for source-level import at compiler/symres/DeclareTopLevel.cpp:37 (declareChildren) and TopLevelDeclSymDeclare::VisitImportStmt (:57), which handles import results that are File or Module.

Remote Imports

Syntax

import "github.com/owner/repo"
import "owner/repo"                       # origin defaults to github.com
import "github.com/owner/repo" version "1.2.3"
import "github.com/owner/repo" branch "main"
import "github.com/owner/repo" commit "abc1234"
import "github.com/owner/repo" subdir "libs/foo"
import "github.com/owner/repo" orphan branch "feature-x"
import "github.com/owner/repo" version "1.2.3" if posix

Recognised metadata keys are version, subdir, branch, commit, plus the orphan flag (parser/statements/Import.cpp:57-85). An if condition may follow the metadata (Import.cpp:179).

Parsing the repo identity

parse_remote_import_from (LabBuildCompiler.cpp:3830) splits from into origin, mod_scope, mod_name: "owner/repo" and "github.com/owner/repo" both give origin="github.com", scope="owner", name="repo". If scope/name are supplied (from CBI) and only origin is missing, it defaults to github.com.

Registration, dedup and conflict resolution

add_remote_import (LabBuildCompiler.cpp:4370) builds a key origin/scope/name, additionally including the branch when orphan_branch is set and the subdir (:4377-4396). Notably, version is not part of the key — that is what lets two different versions collide and be resolved by policy.

  • New key → heap-allocated RemoteImport in job->remote_imports, indexed by job->remote_import_index (:4427-4430).
  • Existing key → resolve_remote_import_conflict (:4434); requesters are merged so every importer links to the single built module (:4414-4423). If already built (built_module != null), requesters attach immediately (:4405-4412).

RemoteImport (RemoteImport.h:15) holds requesters (RemoteImportRequester: requesting LabModule* + DependencySymbolInfo*) and the cached built_module.

ConflictResolutionStrategy (LabJob.h:30): Default, PreferNewerVersion (job default), PreferOlderVersion, RaiseError, OverridePrevious, KeepPrevious. Versions compare via compare_remote_versions (:3799) — a v-prefix-tolerant numeric dotted comparison returning -2 for non-semver strings, which falls back to RaiseError (:4466-4470).

Download and caching

download_remote_import (LabBuildCompiler.cpp:4614):

  • Storage dir: <job.build_dir>/remote/<origin>/<scope>/<name>[@branch][@version][@commit] (get_remote_repo_info, :3880); orphan branches get an @branch suffix (:3890-3893). If the dir already exists, nothing is downloaded (:4646).
  • Otherwise it shells out to git (run_git_and_capture, :4581), prefixing https:// unless the URL already has a scheme or is git@ (:4649-4652). Shallow clones: clone --quiet --depth 1 [--branch <version-or-branch>]; for a commit instead init+remote add+fetch --depth 1 <sha>+checkout FETCH_HEAD (:4663-4677).
  • Non-interactive (GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=true SSH_ASKPASS=true, stdin from /dev/null, :4655-4660); subdir selects <target>/<subdir> (:4694-4699).
  • The downloaded module is built via create_module_for_dependency, cached in import->built_module, and attached to every requester (:4701-4730).

process_remote_imports (:4735) processes imports in waves so newly-discovered transitive remote imports are handled next iteration. It uses a thread pool, a progress bar, and a per-target-dir download_mutex + condition variable to avoid concurrent downloads (:4627-4644).

For chemical run <remote> / local_or_remote_project_to_module, downloaded sources live under ~/.chemical/commands/... (get_commands_cache_dir, :3520) and the build dir is deleted after a successful run (run_job, :3941).

Conditional Dependencies and Target-Specific Deps

if conditions parse into an IffyBase tree (ModFileIfBase in compiler/processor/ModuleFileData.h:15). They appear on source, import, link, link c, ship, and include. Flags come from TargetData (compiler/lab/TargetData.h:7) and mirror the Chemical TargetData (lang/libs/lab/src/target_data.ch): windows, linux, macos, posix, x86_64, aarch64, tcc, clang, debug, release, test, cbi, lsp, etc.

Two evaluation paths exist:

  • In generated build.lab — writeIfConditional emits if(__chx_job.getTarget().<flag>) { ... }, evaluated by the build script at runtime (ModToLabConverter.cpp:35).
  • In build_module_from_mod_file — resolve_target_condition_nullable runs in C++ (LabBuildCompiler.cpp:2534, :2561, :2595, :2615; resolve_target_condition in compiler/lab/LabJob.cpp:99). An unresolvable condition (e.g. a bogus flag) is an error.

resolve_mod_dep_import also evaluates the import's own if_condition and returns a skipped result when false (ImportPathHandler.cpp:276-285). This is how the same .mod can list source "win" if windows / source "posix" if !windows and only one is compiled.

Link Libraries, C Files, Includes, Ship Files

These attach to the job (or a dedicated C module), not to the Chemical module being compiled:

  • link "name" → ctx.link_system_lib(job, name, mod), appended to job->link_libs (LabBuildCompiler.cpp:2594-2606). Chemical uses :libfoo.so.0 to link an exact soname.
  • link path "./dir" → job->lib_search_paths (:2603-2605).
  • link c "file.c" (parser/statements/LexStatement.cpp:621-709) → a synthetic LabModuleType::CFile module named <module>_cfile_<N> with its path and include dirs, added to the job as a dependency (LabBuildCompiler.cpp:2558-2591). C files are compiled to separate objects and linked; the compiler does not parse them, so declare functions with @extern.
  • include "dir" → ModuleFileData::include_dirs, copied onto C-file modules (:2569-2586).
  • ship "path" → job->ship_files, copied to the output dir (:2613-2624).
  • option key = value → LabModuleOptions (mirror of ModuleOptions in lab.ch:20-26), applied by apply_module_options via an FNV-1a key switch (:2448-2495, :2630-2633).

LabModuleOptions and Chemical's ModuleOptions must stay field-for-field in sync (compiler/lab/LabModuleOptions.h:23, lang/libs/lab/src/lab.ch:20), since the struct is crossed through CBI.

Dependency Graph Traversal, Dedup and Cycles

Flattening (transitive, dependency-first)

flatten_dedupe_sorted (LabBuildCompiler.cpp:156) walks ModuleDependency edges with recursive_dedupe (:141): it recurses into every dependency first, then appends the module if not already seen. Result: a flat vector where a module always appears after everything it depends on. Used by process_job_tcc/process_module_gen and do_interpretation_job.

shallow_dedupe_sorted (compiler/ASTCompiler.cpp:1620) is the codegen-time variant: it only emits modules that are among the requested direct dependencies (transitives are used for ordering but not emitted separately), preventing a dependency's code from being emitted twice.

Dedup points

MechanismScopeFile:line
flatten_dedupe_sorted / recursive_dedupemodule pointerLabBuildCompiler.cpp:141-163
job->built_files (canonical path).mod/.lab buildLabBuildCompiler.cpp:2314, :2347
remote_import_indexremote repo identityLabJob.h:129, LabBuildCompiler.cpp:4398
RemoteImport::built_modulealready-built remoteRemoteImport.h:36
ModuleStorage indexscope:name globallyModuleStorage.h:22
ASTFileResult cache (cache map)file idASTProcessor (:825)
ASTFileResult::importsper-file direct importsASTFileResult.h:33

Circular dependencies

Cycle detection is file-level, over the import tree of build.lab files: check_imports_for_cycles (LabBuildCompiler.cpp:272-336) walks ASTFileResult::imports and reports direct (a imports a) and indirect cycles (via the parents stack), printing each link in the chain. A detected cycle aborts the build (:2785). Recursion in check_imports_for_cycles is guarded against infinite descent once a cycle is found (:295).

Module-level cycles are avoided implicitly: the generated build() uses ctx.get_cached/ctx.set_cached, and create_module_for_dependency caches by canonical path, so re-entrant imports reuse the in-progress module pointer. There is no separate module-graph cycle diagnostic — if you construct one that bypasses the cache, expect a stack overflow.

Transitive remote imports

process_remote_imports loops in waves: building a downloaded module may call ctx.fetch_mod_dependency, appending to job->remote_imports; the loop re-scans from processed_count and processes the new entries (LabBuildCompiler.cpp:4744-4783).

Per-Module Records and Metadata Flow

ModuleFileData — the parsed .mod

ModuleFileData (compiler/processor/ModuleFileData.h:79) holds the parser output for a chemical.mod: scope_name, module_name, package_kind, sources_list (ModFileSource), link_libs (ModFileLinkLib), c_files (ModFileCFile), include_dirs, ship_files, compiler_interfaces, options and diagnostics. ModuleFileDataUnit (:187) bundles an ASTAllocator + ASTFileMetaData + ModuleFileData and is reusable via clear().

ASTFileMetaData and ASTFileResult — per source file

ASTFileMetaData (compiler/processor/ASTFileMetaData.h:16) carries file_id, the owning ModuleScope* module, abs_path, the ImportStatement* stmt that pulled the file in, the private symbol range, and a back-pointer to ASTFileResult* result.

ASTFileResult (compiler/processor/ASTFileResult.h:12) extends it with the parsed ASTUnit, imports (deduped direct imports as ASTFileMetaData), the sig_result, diagnostics and benchmarks. It is the unit of parallel work in ASTProcessor.

ModuleDependencyRecord — the intermediate edge

ModuleDependencyRecord (compiler/processor/ModuleDependencyRecord.h:13) is just { std::string module_dir_path; }. It is produced by resolve_mod_dep_import before the module directory is actually built, so that building a dependency (which may clear allocators) can happen after all import statements have been parsed (LabBuildCompiler.cpp:2639-2676).

Metadata flow through compilation passes

chemical.mod
   │  import_chemical_mod_file → ModuleFileData
   ▼
build_module_from_mod_file → LabModule + dependencies (LabModule*)
   ▼
ASTProcessor::determine_module_files(mod)   # ASTProcessor.cpp:133
   │  Files/Directory → direct_files: vector<ASTFileMetaData>
   ▼
import_module_files_direct → import_chemical_file_recursive  # ASTProcessor.cpp:1038
   │  lex+parse each file; figure_out_direct_imports (# :866) fills ASTFileResult::imports
   ▼
sym_res_module  # ASTProcessor.cpp:430
   │  1. declare direct dependency children (DependencySymbolInfo visibility)
   │  2. sym_res_tld_declare_file  (private_symbol_range)
   │  3-6. link sig / gen inst / after-sig / generic bodies / full bodies
   ▼
type_verify_module_parallel → declare_module/implement_module → codegen

determine_module_files is where a module's source set becomes concrete (ASTProcessor.cpp:133): Files treats each paths entry as a .ch file (:144), Directory globs each directory (or single .ch) for .ch files (:161-204), and ObjFile/CFile/CPPFile have no Chemical direct files (:157).

figure_out_direct_imports (:866) only handles isLocalFileImport(); it resolves paths via path_handler.resolve_import_path, and for @-prefixed imports determines the owning module through mod_storage.find_module(atDirective.replaced) (:909). Symbols declared from an external @ module are at least Public, whereas plain relative files are Internal (DeclareTopLevel.cpp:74-79).

Gotchas

  • A module directory must contain build.lab or chemical.mod. create_module_for_dependency hard-errors otherwise (LabBuildCompiler.cpp:2384); relative imports resolve to a directory.
  • import cstd in a .mod becomes import "@cstd/build.lab" in generated code. The leading @ is the package marker, not the @-directive import form.
  • Two-phase resolution is mandatory. create_module_for_dependency can clear the module allocator holding parsed .mod nodes, so collect ModuleDependencyRecords first, then build (LabBuildCompiler.cpp:2635-2676).
  • version is not part of the remote import dedup key (:4377-4396). Different versions of one repo merge into a single RemoteImport and are resolved by ConflictResolutionStrategy (default PreferNewerVersion); non-semver versions raise (compare_remote_versions → -2).
  • Orphan branches and commit are special-cased. Orphan branches are namespaced in the dedup key (:4386) and storage path (@branch, :3890); a commit switches to init+fetch --depth 1 <sha>+checkout FETCH_HEAD instead of a shallow clone (:4663-4676).
  • LabModuleOptions and Chemical's ModuleOptions must stay field-for-field in sync (LabModuleOptions.h:8-13, lang/libs/lab/src/lab.ch:20) — the struct crosses CBI.
  • Cycle detection is file-level only and only runs for build.lab import trees (LabBuildCompiler.cpp:2782-2790). Module-level cycles rely on cache-based re-entrancy.
  • source paths are relative to the .mod/.lab file (resolve_sibling, e.g. LabBuildCompiler.cpp:2537), while build.lab path variables use lab::rel_path_to.
  • The import item list controls symbol visibility — empty info means "all symbols", whereas { a, b } imports only those (declareChildren, ASTProcessor.cpp:396).

Worked Example: Multi-Module Project with a Remote Import

Layout

my_app/chemical.mod + src/main.ch
my_app/libs/greet/chemical.mod + src/greet.ch

my_app/chemical.mod

application my_app

source "src"

# local module
import "./libs/greet"

# remote module, pinned, only on posix, exposing just two symbols
import "chemicallang/example" version "v1.2.0" if posix
import { math.clamp as clamp, math.pi } from "github.com/owner/repo"  # symbol-selective

# native package
import std

link "m"
link c "helper.c" { include "vendor/include" }

my_app/libs/greet/chemical.mod

module greet

source "src"

import cstd
import std

link "m"

What the converter generates (abridged)

import "./libs/greet/build.lab" as __mod_0_stmt;
import "@std/build.lab"         as __mod_1_stmt;
// deps: __mod_0_stmt.build(...) + __mod_1_stmt.build(...)
// mod = ctx.new_package(Directory, Application, "", "my_app", deps)
if(__chx_job.getTarget().posix) {
    ctx.fetch_mod_dependency(__chx_job, mod, ImportRepo {
        from: "chemicallang/example", version: "v1.2.0",
        location: intrinsics::get_raw_location() });
}
ctx.fetch_mod_dependency(__chx_job, mod, ImportRepo {
    from: "github.com/owner/repo",
    symbols: [ ImportSymbol { parts: ["math","clamp"], alias: "clamp" },
               ImportSymbol { parts: ["math","pi"],    alias: "" } ],
    location: intrinsics::get_raw_location() });
// then: add_path("src"), c_file_module("helper.c") + add_include_dir + add_dependency,
//       link_system_lib("m", mod)

What happens at build time

  1. The root build() runs; libs/greet and std are built recursively (each with its own build.lab, cached in built_files).
  2. fetch_mod_dependency registers the two remote imports; chemicallang/example only because posix is true.
  3. process_remote_imports clones <build>/remote/github.com/chemicallang/example@v1.2.0 and <build>/remote/github.com/owner/repo, builds each, and attaches it to mod (or the job for the CLI-form import).
  4. flatten_dedupe_sorted yields [cstd, std, greet, example, repo, my_app] (dependency-first).
  5. Each module is parsed, sym-resolved, type-verified and codegen'd; helper.c becomes its own CFile module; -lm reaches the linker.

Related Skills

  • chemical_mod — user-facing chemical.mod syntax.
  • build_system — LabBuildCompiler, jobs, ASTProcessor, TinyCC JIT.
  • symres — how declared dependency symbols are linked.
  • compiler_bindings — CBI, through which BuildContext is exposed to build.lab.
  • testing — how test modules are wired via imports.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

Comprehensive guide to the Chemical compiler's annotation system (`@name`, `@name(args)`) — how annotations are lexed, parsed, registered in the `AnnotationController`, stored and queried centrally, consumed by the compiler and CBI plugins, and how to add a new one. Load when working on annotations, `AnnotationController`, compiler plugins that define/read annotations, or node attribute flags such as `@extern`, `@test`, `@make`, `@deprecated`, `@no_mangle`, `@inline`, and `@volatile`.

日本語の概要は準備中です。原文の説明を表示しています。

chemicallang/chemical312026年10月26日 更新

The C++ AST node framework of the Chemical compiler — the ASTAny/ASTNode/Value/BaseType hierarchy, node/value/type kind enums and range predicates, the visitor dispatch framework, the arena allocator, TypeBuilder canonicalization, member containers, and cross-reference resolution. Load when adding or modifying AST nodes, writing compiler passes/codegen visitors, debugging kind mismatches or arena/lifetime issues, or extending the compiler API bindings.

日本語の概要は準備中です。原文の説明を表示しています。

chemicallang/chemical312026年10月26日 更新

The Chemical compiler benchmark & release analytics system — how the static GitHub Pages dashboard works, the JSON data model (manifest/daily/release records), the bash collection scripts (bench-*.sh), the GitHub Actions workflows (benchmark-daily/release), and the critical pitfalls (hidden upload dirs, worktree submodules, gh-pages-only data). Load when working on the dashboard, benchmark collection, or analytics workflows.

日本語の概要は準備中です。原文の説明を表示しています。

chemicallang/chemical312026年10月26日 更新

Comprehensive deep-dive into the Chemical build pipeline — how chemical.mod is converted to build.lab, how build scripts are JIT-compiled via TinyCC, how jobs are created and executed, and how ASTProcessor orchestrates the parallel compilation passes.

日本語の概要は準備中です。原文の説明を表示しています。

chemicallang/chemical312026年10月26日 更新

Building

無料

All documentation related to building and running Project or Tests

日本語の概要は準備中です。原文の説明を表示しています。

chemicallang/chemical312026年10月26日 更新

Comprehensive guide to the C translation backend (2c) — how Chemical AST is translated to C code, key patterns, gotchas, and optimization strategies.

日本語の概要は準備中です。原文の説明を表示しています。

chemicallang/chemical312026年10月26日 更新

chemicallang のスキルをすべて見る

このスキルの問題を報告する