Compare commits

...

5 Commits

5 changed files with 486 additions and 105 deletions

30
DOCS.md
View File

@ -149,6 +149,36 @@ lana --help
```
Shows commands, options, and config examples.
### Setup (dependencies)
```bash
lana setup
```
Fetches and extracts external dependencies declared in `config.ini` under `[dependencies]` sections. Each dependency supports the following keys:
- `name` - logical name for the dependency (required)
- `url` - download URL or git repository (optional)
- `archive` - optional filename to save the downloaded archive under `dependencies/tmp`
- `checksum` - optional sha256 checksum to verify the archive
- `extract_to` - directory under `dependencies/` where files should be extracted or cloned
- `build_cmds` - optional semicolon-separated shell commands to build/install the dependency
Notes:
- Only `name` is required. If `url` is omitted Lana will skip any download/clone and extraction steps — this is useful for dependencies that are generated locally or that only require running project-local commands.
- If `url` points to a git repository (ends with `.git`), `lana setup` will perform a shallow clone into `dependencies/<extract_to>`.
- For archive URLs `lana setup` will try `curl` then `wget` to download, will verify checksum if provided, and will extract common archive types (`.tar.gz`, `.tar.xz`, `.zip`).
- When `build_cmds` are present they are executed either inside `dependencies/<extract_to>` (if `extract_to` is set or a clone/extract was performed) or in the project root (if no extract directory is available).
- The current implementation performs a best-effort download/extract and prints warnings/errors; it is intentionally simple and can be extended or replaced by a more robust script if needed.
Example (only `name` + `build_cmds`):
```ini
[dependencies]
name = generate_headers
build_cmds = tools/gen_headers.sh; cp -r generated/include ../../include/
```
In this example `lana setup` will not try to download anything — it will run the `build_cmds` from the project root, allowing you to run arbitrary local build or generation steps.
## Configuration
`config.ini` handles **global** settings (overridden by directives for per-file needs). Edit it in your project root.

View File

@ -107,71 +107,82 @@ fn run_compile_tasks(tasks []CompileTask, build_config config.BuildConfig) ![]st
}
pub fn build(mut build_config config.BuildConfig) ! {
// Run build flow and ensure that if any error occurs we print its message
println('Building ${build_config.project_name}...')
// Create directories
os.mkdir_all(build_config.build_dir) or { return error('Failed to create build directory') }
os.mkdir_all(build_config.bin_dir) or { return error('Failed to create bin directory') }
os.mkdir_all('${build_config.bin_dir}/lib') or { return error('Failed to create lib directory') }
os.mkdir_all('${build_config.bin_dir}/tools') or { return error('Failed to create tools directory') }
if build_config.shaders_dir != '' && build_config.shaders_dir != 'bin/shaders' {
os.mkdir_all(build_config.shaders_dir) or { return error('Failed to create shaders directory') }
}
// Auto-discover sources if not specified
auto_discover_sources(mut build_config)
// Build shared libraries first (from config)
mut shared_libs_built := []string{}
for mut lib_config in build_config.shared_libs {
if lib_config.sources.len == 0 {
run_build := fn (mut build_config config.BuildConfig) ! {
// Create directories
os.mkdir_all(build_config.build_dir) or { return error('Failed to create build directory') }
os.mkdir_all(build_config.bin_dir) or { return error('Failed to create bin directory') }
os.mkdir_all('${build_config.bin_dir}/lib') or { return error('Failed to create lib directory') }
os.mkdir_all('${build_config.bin_dir}/tools') or { return error('Failed to create tools directory') }
if build_config.shaders_dir != '' && build_config.shaders_dir != 'bin/shaders' {
os.mkdir_all(build_config.shaders_dir) or { return error('Failed to create shaders directory') }
}
// Auto-discover sources if not specified
auto_discover_sources(mut build_config)
// Build shared libraries first (from config)
mut shared_libs_built := []string{}
for mut lib_config in build_config.shared_libs {
if lib_config.sources.len == 0 {
if build_config.verbose {
println('Skipping empty shared library: ${lib_config.name}')
}
continue
}
println('Building shared library: ${lib_config.name}')
build_shared_library(mut lib_config, build_config) or {
return error('Failed to build shared library ${lib_config.name}: ${err}')
}
shared_libs_built << lib_config.name
if build_config.verbose {
println('Skipping empty shared library: ${lib_config.name}')
println('Built shared library: ${lib_config.name}')
}
continue
}
println('Building shared library: ${lib_config.name}')
build_shared_library(mut lib_config, build_config) or {
return error('Failed to build shared library ${lib_config.name}: ${err}')
}
shared_libs_built << lib_config.name
if build_config.verbose {
println('Built shared library: ${lib_config.name}')
}
}
// Build targets from build directives
build_from_directives(mut build_config, mut shared_libs_built)!
// Build tools/executables from config
for mut tool_config in build_config.tools {
if tool_config.sources.len == 0 {
// Build targets from build directives
build_from_directives(mut build_config, mut shared_libs_built)!
// Build tools/executables from config
for mut tool_config in build_config.tools {
if tool_config.sources.len == 0 {
if build_config.verbose {
println('Skipping empty tool: ${tool_config.name}')
}
continue
}
println('Building tool: ${tool_config.name}')
build_tool(mut tool_config, build_config) or {
return error('Failed to build tool ${tool_config.name}: ${err}')
}
if build_config.verbose {
println('Skipping empty tool: ${tool_config.name}')
}
continue
}
println('Building tool: ${tool_config.name}')
build_tool(mut tool_config, build_config) or {
return error('Failed to build tool ${tool_config.name}: ${err}')
}
if build_config.verbose {
println('Built tool: ${tool_config.name}')
}
}
// Build shaders if configured and directory exists
if build_config.shaders_dir != '' {
compile_shaders(build_config) or {
if !build_config.verbose {
println('Warning: Failed to compile shaders: ${err}')
println('Built tool: ${tool_config.name}')
}
}
// Build shaders if configured and directory exists
if build_config.shaders_dir != '' {
compile_shaders(build_config) or {
if !build_config.verbose {
println('Warning: Failed to compile shaders: ${err}')
}
}
}
println('Build completed successfully!')
return
}
// Execute build and show full error output if something fails
run_build(mut build_config) or {
// Print error message to help debugging
println('Build failed: ${err}')
return err
}
println('Build completed successfully!')
}
// Build targets based on build directives from source files
@ -202,21 +213,42 @@ fn build_from_directives(mut build_config config.BuildConfig, mut shared_libs_bu
// Find source file for this unit
mut source_file := ''
for src_path in directive.unit_name.split('/') {
source_file = os.join_path(build_config.src_dir, src_path + '.cpp')
if os.is_file(source_file) {
break
}
source_file = os.join_path(build_config.src_dir, src_path + '.cc')
if os.is_file(source_file) {
break
}
source_file = os.join_path(build_config.src_dir, src_path + '.cxx')
if os.is_file(source_file) {
break
// First try the full unit path (e.g., src/lib/file.cpp)
mut candidate := os.join_path(build_config.src_dir, directive.unit_name + '.cpp')
if os.is_file(candidate) {
source_file = candidate
} else {
candidate = os.join_path(build_config.src_dir, directive.unit_name + '.cc')
if os.is_file(candidate) {
source_file = candidate
} else {
candidate = os.join_path(build_config.src_dir, directive.unit_name + '.cxx')
if os.is_file(candidate) {
source_file = candidate
}
}
}
// Fallback: try only the basename (e.g., src/file.cpp) for legacy layouts
if source_file == '' {
parts := directive.unit_name.split('/')
base := if parts.len > 0 { parts[parts.len - 1] } else { directive.unit_name }
candidate = os.join_path(build_config.src_dir, base + '.cpp')
if os.is_file(candidate) {
source_file = candidate
} else {
candidate = os.join_path(build_config.src_dir, base + '.cc')
if os.is_file(candidate) {
source_file = candidate
} else {
candidate = os.join_path(build_config.src_dir, base + '.cxx')
if os.is_file(candidate) {
source_file = candidate
}
}
}
}
if source_file == '' {
if build_config.verbose {
println('Warning: Source file not found for unit ${unit_name}')
@ -254,9 +286,17 @@ fn build_from_directives(mut build_config config.BuildConfig, mut shared_libs_bu
// Link executable or shared library
if directive.is_shared {
// Link shared library
lib_output := os.join_path(build_config.bin_dir, 'lib', directive.unit_name)
println('Linking shared library: ${lib_output}')
link_shared_library([obj_file], directive.unit_name, lib_output, build_config, config.SharedLibConfig{
// place shared libs directly under bin/lib (not nested by unit name)
lib_output_dir := os.join_path(build_config.bin_dir, 'lib')
// ensure output directory exists
os.mkdir_all(lib_output_dir) or { return error('Failed to create shared lib output directory: ${lib_output_dir}') }
println('Linking shared library: ${lib_output_dir}/${directive.unit_name.split('/').last()}.so')
if build_config.verbose {
// show contents of lib dir for debugging
files := os.ls(lib_output_dir) or { []string{} }
println('Contents of ${lib_output_dir}: ${files}')
}
link_shared_library([obj_file], directive.unit_name, lib_output_dir, build_config, config.SharedLibConfig{
name: directive.unit_name
libraries: directive.link_libs
debug: build_config.debug
@ -457,6 +497,11 @@ fn build_shared_library(mut lib_config config.SharedLibConfig, build_config conf
if needs_recompile(src_file, obj_file) {
println('Compiling ${lib_config.name}: ${src_file}...')
lib_target_config := config.TargetConfig(lib_config)
// show compile command if verbose
if lib_config.verbose || build_config.verbose {
cmd_preview := config.build_shared_compiler_command(src_file, obj_file, build_config, lib_target_config)
println('Compile command (preview): ${cmd_preview}')
}
compile_tasks << CompileTask{source: src_file, obj: obj_file, target_config: lib_target_config}
} else {
if lib_config.verbose {
@ -477,9 +522,12 @@ fn build_shared_library(mut lib_config config.SharedLibConfig, build_config conf
}
// Link shared library
lib_output := os.join_path(lib_config.output_dir, lib_config.name)
println('Linking shared library: ${lib_output}')
link_shared_library(object_files, lib_config.name, lib_output, build_config, lib_config) or {
// place shared libs directly under the configured output dir
lib_output_dir := lib_config.output_dir
// ensure output directory exists
os.mkdir_all(lib_output_dir) or { return error('Failed to create shared lib output directory: ${lib_output_dir}') }
println('Linking shared library: ${lib_output_dir}/${lib_config.name.split('/').last()}.so')
link_shared_library(object_files, lib_config.name, lib_output_dir, build_config, lib_config) or {
return error('Failed to link shared library ${lib_config.name}')
}
@ -522,6 +570,11 @@ fn build_tool(mut tool_config config.ToolConfig, build_config config.BuildConfig
if needs_recompile(src_file, obj_file) {
println('Compiling ${tool_config.name}: ${src_file}...')
tool_target_config := config.TargetConfig(tool_config)
// show compile command if verbose
if tool_config.verbose || build_config.verbose {
cmd_preview := config.build_shared_compiler_command(src_file, obj_file, build_config, tool_target_config)
println('Compile command (preview): ${cmd_preview}')
}
compile_tasks << CompileTask{source: src_file, obj: obj_file, target_config: tool_target_config}
} else {
if tool_config.verbose {
@ -569,16 +622,20 @@ fn get_target_verbose(target_config config.TargetConfig) bool {
fn compile_file(source_file string, object_file string, build_config config.BuildConfig, target_config config.TargetConfig) !string {
cmd := config.build_shared_compiler_command(source_file, object_file, build_config, target_config)
target_verbose := get_target_verbose(target_config)
if target_verbose {
println('Compile command: ${cmd}')
}
// Execute compile
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Compilation failed with exit code ${res.exit_code}:\n${res.output}')
// Print compile command and raw output to aid debugging
println('Compile command: ${cmd}')
println('Compiler output:\n${res.output}')
return error('Compilation failed with exit code ${res.exit_code}: ${res.output}')
}
// Generate dependency file
@ -594,10 +651,14 @@ fn link_shared_library(object_files []string, library_name string, output_path s
if lib_config.verbose {
println('Shared lib link command: ${cmd}')
}
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Shared library linking failed with exit code ${res.exit_code}:\n${res.output}')
// Always print the linker command and its raw output to aid debugging
println('Linker command: ${cmd}')
// print raw output (may contain stdout and stderr merged by os.execute)
println('Linker output:\n${res.output}')
return error('Shared library linking failed with exit code ${res.exit_code}: ${res.output}')
}
}
@ -607,10 +668,13 @@ fn link_tool(object_files []string, executable string, build_config config.Build
if tool_config.verbose {
println('Tool link command: ${cmd}')
}
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Tool linking failed with exit code ${res.exit_code}:\n${res.output}')
// Always print the linker command and its raw output to aid debugging
println('Linker command: ${cmd}')
println('Linker output:\n${res.output}')
return error('Tool linking failed with exit code ${res.exit_code}: ${res.output}')
}
}
@ -734,9 +798,22 @@ fn find_glslc() !string {
}
fn get_object_file(source_file string, object_dir string) string {
// Replace src_dir with object_dir and change extension to .o
mut obj_file := source_file.replace('src', object_dir)
obj_file = obj_file.replace('.cpp', '.o').replace('.cc', '.o').replace('.cxx', '.o')
// Compute object file path by preserving the path under src/ and placing it under object_dir
// e.g., src/lib/file.cpp -> <object_dir>/lib/file.o
// Detect the 'src' prefix and compute relative path
rel := if source_file.starts_with('src/') {
source_file[4..]
} else if source_file.starts_with('./src/') {
source_file[6..]
} else {
// fallback: use basename
os.base(source_file)
}
// strip extension and add .o using basename to avoid nested paths under object_dir
rel_no_ext := rel.replace('.cpp', '').replace('.cc', '').replace('.cxx', '')
base_name := os.base(rel_no_ext)
obj_file := os.join_path(object_dir, base_name + '.o')
return obj_file
}
@ -766,6 +843,11 @@ fn find_source_files(dir string) ![]string {
}
fn needs_recompile(source_file string, object_file string) bool {
if !os.is_file(source_file) {
// source missing, signal recompile to allow upstream code to handle error
return true
}
src_mtime := os.file_last_mod_unix(source_file)
obj_mtime := if os.is_file(object_file) {
os.file_last_mod_unix(object_file)

View File

@ -47,6 +47,17 @@ __global:
verbose bool
}
// Dependency represents an external dependency to download/extract
pub struct Dependency {
__global:
name string
url string // download URL
archive string // relative archive path to save (under dependencies/tmp)
checksum string // optional checksum to verify
extract_to string // destination directory under dependencies/
build_cmds []string // optional semicolon-separated build commands
}
// BuildConfig holds the configuration for the project
pub struct BuildConfig {
__global:
@ -70,6 +81,7 @@ __global:
shaders_dir string = 'bin/shaders' // for shader compilation
dependencies_dir string = 'dependencies' // external dependencies
parallel_compilation bool = true // enable parallel builds
dependencies []Dependency
// Build directives from source files
build_directives []BuildDirective
@ -77,7 +89,7 @@ __global:
// default config
pub const default_config = BuildConfig{
project_name: 'project'
project_name: ''
src_dir: 'src'
build_dir: 'build'
bin_dir: 'bin'
@ -90,6 +102,7 @@ pub const default_config = BuildConfig{
verbose: false
shared_libs: []
tools: []
dependencies: []
}
// Parse build directives from source files
@ -122,7 +135,8 @@ pub fn (mut build_config BuildConfig) parse_build_directives() ! {
continue
}
parts := line[17..].trim_space().split('(')
// slice after the prefix '// build-directive:' (19 characters)
parts := line[19..].trim_space().split('(')
if parts.len != 2 {
continue
}
@ -194,6 +208,11 @@ pub fn (mut build_config BuildConfig) parse_build_directives() ! {
pub fn parse_args() !BuildConfig {
mut build_config := default_config
// Auto-load config.ini if present in current directory
if os.is_file('config.ini') {
build_config = parse_config_file('config.ini') or { build_config }
}
mut i := 2 // Skip program name and command
for i < os.args.len {
@ -274,10 +293,10 @@ pub fn parse_args() !BuildConfig {
else {
if !arg.starts_with('-') {
// Treat as project name or first source file
if build_config.project_name == 'project' {
if build_config.project_name == '' {
build_config.project_name = arg
} else {
// Add as default tool
// Add as default tool using the project name
mut default_tool := ToolConfig{
name: build_config.project_name
sources: [arg]
@ -319,17 +338,21 @@ pub fn parse_config_file(filename string) !BuildConfig {
mut current_section := ''
mut current_lib_index := 0
mut current_tool_index := 0
mut current_dep_index := 0
for line in lines {
if line.starts_with('#') || line.trim_space() == '' { continue }
if line.starts_with('[') && line.ends_with(']') {
current_section = line[1..line.len - 1]
// Reset indices when entering new sections
// keep the brackets in current_section to match existing match arms
current_section = '[' + line[1..line.len - 1] + ']'
// Point indices to the next entry index for repeated sections
if current_section == '[shared_libs]' {
current_lib_index = 0
current_lib_index = build_config.shared_libs.len
} else if current_section == '[tools]' {
current_tool_index = 0
current_tool_index = build_config.tools.len
} else if current_section == '[dependencies]' {
current_dep_index = build_config.dependencies.len
}
continue
}
@ -425,7 +448,7 @@ pub fn parse_config_file(filename string) !BuildConfig {
}
}
}
current_lib_index++
// keys for this shared_lib section are populated into the same struct
}
'[tools]' {
// Ensure we have a tool config to modify
@ -476,7 +499,34 @@ pub fn parse_config_file(filename string) !BuildConfig {
}
}
}
current_tool_index++
// keys for this tool section are populated into the same struct
}
'[dependencies]' {
// Ensure we have a dependency entry to modify
if current_dep_index >= build_config.dependencies.len {
build_config.dependencies << Dependency{}
}
mut dep := &build_config.dependencies[current_dep_index]
match key {
'name' { dep.name = value }
'url' { dep.url = value }
'archive' { dep.archive = value }
'checksum' { dep.checksum = value }
'build_cmds' {
cmds := value.split(';')
for c in cmds {
dep.build_cmds << c.trim_space()
}
}
'extract_to' { dep.extract_to = value }
else {
if build_config.verbose {
println('Warning: Unknown dependency config key: ${key}')
}
}
}
// keys for this dependency section are populated into the same struct
}
else {
if build_config.verbose {
@ -504,6 +554,53 @@ pub fn parse_config_file(filename string) !BuildConfig {
if !tool.optimize { tool.optimize = build_config.optimize }
if !tool.verbose { tool.verbose = build_config.verbose }
}
// Merge global flags and includes into individual shared libs and tools
for mut lib in build_config.shared_libs {
// merge include dirs
for inc in build_config.include_dirs {
if inc != '' && inc !in lib.include_dirs {
lib.include_dirs << inc
}
}
// merge cflags
for flag in build_config.cflags {
if flag != '' && flag !in lib.cflags {
lib.cflags << flag
}
}
// merge ldflags
for flag in build_config.ldflags {
if flag != '' && flag !in lib.ldflags {
lib.ldflags << flag
}
}
// Do NOT merge global libraries into shared lib 'libraries' here.
// Global libraries should be linked globally during linker command assembly, not as per-target shared lib dependencies.
}
for mut tool in build_config.tools {
// merge include dirs
for inc in build_config.include_dirs {
if inc != '' && inc !in tool.include_dirs {
tool.include_dirs << inc
}
}
// merge cflags
for flag in build_config.cflags {
if flag != '' && flag !in tool.cflags {
tool.cflags << flag
}
}
// merge ldflags
for flag in build_config.ldflags {
if flag != '' && flag !in tool.ldflags {
tool.ldflags << flag
}
}
// Do NOT merge global libraries into tool 'libraries' here.
// Keep per-tool libraries strictly those declared for the tool; global libraries will be added at link time.
}
return build_config
}
@ -581,7 +678,7 @@ pub fn build_shared_compiler_command(source_file string, object_file string, bui
}
// Add standard flags
cmd += ' -Wall -Wextra -std=c++17'
cmd += ' -Wall -Wextra'
// Add global CFLAGS
for flag in build_config.cflags {
@ -626,10 +723,19 @@ pub fn build_shared_linker_command(object_files []string, library_name string, o
}
}
// Add this library's dependencies
// Add this library's dependencies (hardcode to filename form: -l:<name>.so)
for library in lib_config.libraries {
if library != '' {
cmd += ' -l:${library}.so'
// strip any existing .so or lib/ prefix and ensure we request the explicit filename
mut libfile := library
if libfile.starts_with('lib/') {
parts := libfile.split('/')
libfile = parts[parts.len - 1]
}
if libfile.ends_with('.so') {
libfile = libfile.replace('.so', '')
}
cmd += ' -l:${libfile}.so'
}
}
@ -642,13 +748,16 @@ pub fn build_shared_linker_command(object_files []string, library_name string, o
}
// Set output name (with platform-specific extension)
mut lib_name := library_name
// Use only the basename of the library to avoid duplicating path segments
parts := library_name.split('/')
base_name := if parts.len > 0 { parts[parts.len - 1] } else { library_name }
mut lib_name := base_name
$if windows {
lib_name += '.dll'
} $else {
lib_name += '.so'
}
cmd += ' -o ${output_path}/${lib_name}'
return cmd
}
@ -680,10 +789,18 @@ pub fn build_tool_linker_command(object_files []string, executable string, build
}
}
// Add this tool's dependencies (shared libs)
// Add this tool's dependencies (shared libs) — hardcode to explicit .so filenames
for library in tool_config.libraries {
if library != '' {
cmd += ' -l:${library}.so'
mut libfile := library
if libfile.starts_with('lib/') {
parts := libfile.split('/')
libfile = parts[parts.len - 1]
}
if libfile.ends_with('.so') {
libfile = libfile.replace('.so', '')
}
cmd += ' -l:${libfile}.so'
}
}
@ -723,7 +840,7 @@ pub fn build_compiler_command(source_file string, object_file string, build_conf
}
// Add standard flags
cmd += ' -Wall -Wextra -std=c++17'
cmd += ' -Wall -Wextra'
// Add custom CFLAGS
for flag in build_config.cflags {

145
deps/deps.v vendored
View File

@ -1,6 +1,8 @@
module deps
import os
import config
import os.cmdline
pub fn extract_dependencies(source_file string) ![]string {
mut dependencies := []string{}
@ -68,4 +70,147 @@ pub fn generate_dependency_file(source_file string, object_file string, dep_file
}
os.write_file(dep_file, content) or { }
}
// Fetch and extract dependencies declared in the build config
pub fn fetch_dependencies(build_config config.BuildConfig) ! {
if build_config.dependencies.len == 0 {
println('No dependencies declared in config')
return
}
tmp_dir := os.join_path(build_config.dependencies_dir, 'tmp')
os.mkdir_all(tmp_dir) or { return error('Failed to create tmp dir: ${err}') }
deps_dir := build_config.dependencies_dir
os.mkdir_all(deps_dir) or { return error('Failed to create dependencies dir: ${err}') }
for dep in build_config.dependencies {
if dep.name == '' {
println('Skipping unnamed dependency')
continue
}
println('Processing dependency: ${dep.name}')
println(' parsed: url="${dep.url}", archive="${dep.archive}", extract_to="${dep.extract_to}"')
// Allow dependencies with only a name. If no URL is provided, skip
// download/extract/clone steps and only run any provided build_cmds.
url_trim := dep.url.trim_space()
// Decide if URL is a git repo or an archive (only if URL present)
is_git := url_trim != '' && (url_trim.ends_with('.git') || url_trim.starts_with('git://'))
extract_to := if dep.extract_to != '' { os.join_path(deps_dir, dep.extract_to) } else { os.join_path(deps_dir, dep.name) }
if url_trim == '' {
println('No url provided for ${dep.name}, skipping download/extract; will only run build_cmds if present')
} else {
if is_git {
// Clone repository if needed
if os.is_dir(extract_to) {
println('Dependency already cloned at ${extract_to}, skipping clone')
} else {
cmd := 'git clone --depth 1 ${url_trim} ${extract_to}'
println('Running: ${cmd}')
code := os.system(cmd)
if code != 0 {
return error('Failed to clone ${url_trim}: exit ${code}')
}
}
} else {
// Archive download path
archive_name := if dep.archive != '' { dep.archive } else { os.file_name(url_trim) }
archive_path := os.join_path(tmp_dir, archive_name)
if !os.is_file(archive_path) {
println('Downloading ${url_trim} -> ${archive_path}')
// Prefer curl, fall back to wget
mut code := os.system('curl -L -o ${archive_path} ${url_trim}')
if code != 0 {
code = os.system('wget -O ${archive_path} ${url_trim}')
if code != 0 {
return error('Failed to download ${url_trim}: curl/wget exit ${code}')
}
}
} else {
println('Archive already exists: ${archive_path}')
}
// Optionally verify checksum
if dep.checksum != '' {
// Use sha256sum if available
res := os.execute('sha256sum ${archive_path}')
if res.exit_code != 0 {
println('Warning: sha256sum not available to verify checksum')
} else {
parts := res.output.split(' ')
if parts.len > 0 && parts[0].trim_space() != dep.checksum {
return error('Checksum mismatch for ${archive_path}')
}
}
}
// Extract archive
if os.is_dir(extract_to) {
println('Already extracted to ${extract_to}, skipping')
} else {
os.mkdir_all(extract_to) or { return error('Failed to create ${extract_to}: ${err}') }
// Basic extraction handling by extension
lower := archive_path.to_lower()
if lower.ends_with('.tar.gz') || lower.ends_with('.tgz') || lower.ends_with('.tar.xz') || lower.ends_with('.tar') {
cmd := 'tar -xf ${archive_path} -C ${deps_dir}'
println('Extracting with: ${cmd}')
code := os.system(cmd)
if code != 0 {
return error('Failed to extract ${archive_path}: exit ${code}')
}
// If the archive created a top-level dir, caller should set extract_to to match archive content.
} else if lower.ends_with('.zip') {
cmd := 'unzip -q ${archive_path} -d ${extract_to}'
println('Extracting zip with: ${cmd}')
code := os.system(cmd)
if code != 0 {
return error('Failed to unzip ${archive_path}: exit ${code}')
}
} else {
println('Unknown archive format for ${archive_path}, skipping extraction')
}
}
}
}
// Run build commands if provided, otherwise run package-specific defaults
if dep.build_cmds.len > 0 {
// Choose where to run build commands:
// - If extract_to was provided, run inside deps/<extract_to> (create it if missing)
// - Otherwise run from project root (os.getwd())
mut run_dir := os.getwd()
if dep.extract_to != '' {
run_dir = os.join_path(deps_dir, dep.extract_to)
os.mkdir_all(run_dir) or { println('Warning: Failed to create build dir ${run_dir}: ${err}') }
}
for cmd_line in dep.build_cmds {
println('Running build command for ${dep.name}: ${cmd_line}')
old_cwd := os.getwd()
os.chdir(run_dir) or { return error('Failed to chdir: ${err}') }
code := os.system(cmd_line)
os.chdir(old_cwd) or { }
if code != 0 {
return error('Build command failed for ${dep.name}: exit ${code}')
}
}
} else {
// No package-specific defaults: if build_cmds are absent we do nothing.
if build_config.verbose {
println('No default build steps for dependency: ${dep.name}; provide build_cmds in config.ini to build it')
}
}
}
println('Dependencies processed successfully')
// Clean up temporary download directory
if os.is_dir(tmp_dir) {
os.rmdir_all(tmp_dir) or { println('Warning: Failed to remove tmp dir: ${err}') }
}
}

7
lana.v
View File

@ -5,6 +5,7 @@ import config
import builder
import runner
import initializer
import deps
import help
const (
@ -45,6 +46,12 @@ fn main() {
}
}
'init' { initializer.init_project(os.args[2] or { 'myproject' }) }
'setup' {
deps.fetch_dependencies(config_data) or {
eprintln('Failed to fetch dependencies: ${err}')
exit(1)
}
}
else { help.show_help() }
}
}