Compare commits
5 Commits
0a94753748
...
3aa51748bd
| Author | SHA1 | Date |
|---|---|---|
|
|
3aa51748bd | |
|
|
fc3799e9ac | |
|
|
84f90e83c3 | |
|
|
abb2315341 | |
|
|
0783e68b0a |
30
DOCS.md
30
DOCS.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
153
config/config.v
153
config/config.v
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
7
lana.v
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue