패키지 매니저
Package Manager Design
Overview
This document describes the design of the Vais package manager (vais pkg), which provides dependency management through a vais.toml configuration file.
Goals
- Simple Configuration: Easy-to-understand
vais.tomlformat - Local Dependencies: Support for local path dependencies during development
- Version Management: Semantic versioning for packages
- Reproducible Builds: Lock file for deterministic dependency resolution
- Minimal Overhead: Fast resolution and compilation
vais.toml Format
[package]
name = "my-project"
version = "0.1.0"
authors = ["Author Name <author@example.com>"]
description = "A Vais project"
license = "MIT"
[dependencies]
# Local path dependency
my-lib = { path = "../my-lib" }
# Future: Registry dependency (not in initial implementation)
# json-parser = "1.0.0"
# http-client = { version = "2.1.0", features = ["tls"] }
[dev-dependencies]
# Test-only dependencies
test-utils = { path = "../test-utils" }
[build]
# Compiler options
opt-level = 2
debug = false
target = "native"
Directory Structure
my-project/
├── vais.toml # Package manifest
├── vais.lock # Locked dependency versions (generated)
├── src/
│ ├── main.vais # Binary entry point (optional)
│ └── lib.vais # Library entry point (optional)
├── tests/
│ └── test_*.vais # Test files
└── .vais-cache/ # Build artifacts (gitignored)
├── deps/ # Compiled dependencies
└── build/ # Intermediate build files
Commands
vais pkg init
Initialize a new package in the current directory.
vais pkg init
# Creates vais.toml with default values
vais pkg build
Build the package and its dependencies.
vais pkg build # Build with default options
vais pkg build --release # Build with optimizations
vais pkg build --debug # Build with debug info
vais pkg check
Type-check the package without generating binaries.
vais pkg check
vais pkg test
Run package tests.
vais pkg test # Run all tests
vais pkg test test_name # Run specific test
vais pkg clean
Remove build artifacts.
vais pkg clean
vais pkg add
Add a dependency.
vais pkg add my-lib --path ../my-lib
# Future: vais pkg add json-parser@1.0.0
vais pkg remove
Remove a dependency.
vais pkg remove my-lib
Dependency Resolution
Phase 1 (Initial Implementation)
- Parse
vais.tomlto get dependencies - For each path dependency:
- Resolve the path relative to the current package
- Recursively process its
vais.toml - Build dependency graph
- Topological sort dependencies
- Compile in order (dependencies first)
Resolution Algorithm
function resolve(package):
deps = []
for each dependency in package.dependencies:
if dependency.path exists:
dep_package = load_toml(dependency.path / "vais.toml")
dep_deps = resolve(dep_package)
deps.extend(dep_deps)
deps.append(dep_package)
return deduplicate(deps)
Lock File Format
# vais.lock - DO NOT EDIT
# This file is automatically generated by the package manager
[[package]]
name = "my-lib"
path = "../my-lib"
checksum = "sha256:abc123..."
[[package]]
name = "other-lib"
path = "../other-lib"
checksum = "sha256:def456..."
Build Process
-
Dependency Resolution
- Parse vais.toml
- Resolve all dependencies (transitive)
- Check for cycles
- Generate topological order
-
Incremental Check
- Compare source checksums with .vais-cache
- Skip unchanged packages
-
Compilation
- For each package in order:
- Set include paths for dependencies
- Compile to .ll files
- Link with dependency objects
- For each package in order:
-
Linking
- Combine all objects
- Generate final binary
Module Resolution
When compiling with the package manager:
U my-lib/parser # Resolves to <my-lib-path>/src/parser.vais
U my-lib # Resolves to <my-lib-path>/src/lib.vais
Resolution order:
- Check dependencies in vais.toml
- If found, use the dependency's path
- Otherwise, fall back to relative path resolution
Implementation Plan
Phase 1: Basic Package Management
- Design document (this file)
- vais.toml parsing (toml crate)
-
vais pkg initcommand - Path dependency resolution
-
vais pkg buildfor single package
Phase 2: Dependency Graph
- Recursive dependency resolution
- Cycle detection
- Topological sorting
- Lock file generation
Phase 3: Incremental Build
- Checksum-based change detection
- .vais-cache management
- Parallel compilation
Phase 4: Developer Experience
-
vais pkg add/removecommands -
vais pkg testcommand - Better error messages
- Progress reporting
Phase 5: Registry
- Package registry design
- Version resolution (SemVer)
- Publishing workflow
- Workspace support
- Lockfile deterministic serialization
Error Handling
Common Errors
-
Missing vais.toml
error: could not find `vais.toml` in current directory or parent directories -
Invalid toml
error: failed to parse vais.toml --> vais.toml:5:1 | 5 | name = | ^ expected value -
Missing dependency
error: dependency `my-lib` not found --> vais.toml:8:1 | 8 | my-lib = { path = "../my-lib" } | path `../my-lib` does not exist -
Dependency cycle
error: cyclic dependency detected package-a -> package-b -> package-a
Example Usage
Creating a New Project
mkdir my-project && cd my-project
vais pkg init
# Edit vais.toml to add dependencies
# Create src/main.vais
vais pkg build
./target/my-project
Using a Local Library
# In library directory
cd my-lib
vais pkg init
# Create src/lib.vais with exports
# In application directory
cd my-app
vais pkg add my-lib --path ../my-lib
# Import in src/main.vais: U my-lib/utils
vais pkg build
Semantic Versioning
Vais uses semantic versioning (SemVer) for package version resolution:
Version Specifiers
- Caret (
^):^1.2.3— compatible updates (>=1.2.3, <2.0.0) - Tilde (
~):~1.2.3— patch updates only (>=1.2.3, <1.3.0) - Exact:
=1.2.3— exact version required - Wildcard:
1.*— any patch/minor within major version
Conflict Resolution
When multiple packages depend on different versions:
- Attempt to find a compatible version that satisfies all constraints
- If no compatible version exists, report a dependency conflict error
- Use the highest compatible version that satisfies all constraints
Diamond Dependencies
A
/ \
B C
\ /
D
If B depends on D ^1.0 and C depends on D ^1.2, resolve to highest compatible (e.g., D 1.5.0).
Pre-release Versions
Pre-release versions (e.g., 1.0.0-alpha.1) are only used if explicitly specified:
vais-json = "1.0.0-beta.3" # Must specify pre-release explicitly
Workspace Support
Workspaces allow managing multiple packages in a monorepo:
Workspace Configuration
# workspace-root/vais.toml
[workspace]
members = [
"crates/vais-core",
"crates/vais-utils",
"crates/vais-cli"
]
[workspace.dependencies]
# Shared dependencies for all workspace members
serde = "1.0"
Member Packages
# crates/vais-core/vais.toml
[package]
name = "vais-core"
version = "0.1.0"
[dependencies]
vais-utils = { workspace = true } # Use workspace member
serde = { workspace = true } # Use workspace dependency
Workspace Commands
vais pkg build --workspace # Build all workspace members
vais pkg test --workspace # Test all workspace members
Lockfile Format
The lockfile (vais.lock) ensures deterministic builds:
# vais.lock - DO NOT EDIT
# This file is automatically generated by the package manager
version = 1
[[package]]
name = "vais-json"
version = "1.2.3"
source = "registry+https://registry.vais.dev/"
checksum = "sha256:abc123def456..."
dependencies = [
"vais-core 0.1.0",
]
[[package]]
name = "vais-core"
version = "0.1.0"
source = "path+../vais-core"
checksum = "sha256:789xyz012..."
dependencies = []
Lockfile Properties
- Version — lockfile format version
- Deterministic — packages sorted alphabetically for stable diffs
- Checksums — SHA-256 hash of package contents
- Source — registry URL or local path
- Transitive — includes all transitive dependencies
Reproducible Builds
The lockfile guarantees:
- Same dependency versions across all developers
- Same build output given same source
- Protection against upstream changes
Registry E2E Testing
The package manager includes comprehensive end-to-end tests:
Test Categories (130 E2E tests)
- Init (5 tests) — package creation, default names, duplicate failures, roundtrip, build integration
- Install (6 tests) — path dependencies, transitive deps, binary installation, library failures
- Publish (3 tests) — request structure, archive creation, server unavailable handling
- SemVer (7 tests) — caret/tilde ranges, conflicts, diamond dependencies, pre-release handling
- Workspace (3 tests) — member management, inter-member dependencies, manifest parsing
- Lockfile (3 tests) — format validation, deterministic serialization, reproducible builds
- Templates (4 tests) — binary/library templates, gitignore generation, build verification
- Documentation (3 tests) — markdown generation, HTML output, error handling
Running E2E Tests
cargo test --package vaisc registry_e2e_tests
Future Considerations
- Features: Conditional compilation with feature flags
- Build Scripts: Custom build steps (
build.vais) - Binary Distribution: Pre-compiled packages for faster installation
- Mirrors: Alternative registry mirrors for reliability
- Private Registries: Enterprise-internal package hosting