Initial commit

main
Joca 2025-09-13 19:39:35 -03:00
commit efb70b3662
Signed by: jocadbz
GPG Key ID: B1836DCE2F50BDF7
7 changed files with 645 additions and 0 deletions

161
builder/builder.v Normal file
View File

@ -0,0 +1,161 @@
module builder
import os
import config
import deps
pub fn build(mut build_config config.BuildConfig) ! {
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') }
// Find all source files
source_files := find_source_files(build_config.src_dir) or {
return error('Failed to find source files in ${build_config.src_dir}')
}
if source_files.len == 0 {
return error('No source files found in ${build_config.src_dir}')
}
mut object_files := []string{}
// Compile each source file
for src_file in source_files {
obj_file := src_file.replace(build_config.src_dir, build_config.build_dir).replace('.cpp', '.o')
// Create object directory if needed
obj_dir := os.dir(obj_file)
os.mkdir_all(obj_dir) or { return error('Failed to create object directory: ${obj_dir}') }
// Check if we need to recompile
if needs_recompile(src_file, obj_file) {
println('Compiling ${src_file}...')
object_files << compile_file(src_file, obj_file, build_config) or {
return error('Failed to compile ${src_file}')
}
} else {
if build_config.verbose {
println('Using cached ${obj_file}')
}
object_files << obj_file
}
}
// Link object files
executable := os.join_path(build_config.bin_dir, build_config.project_name)
println('Linking ${executable}...')
link_objects(object_files, executable, build_config) or { return error('Failed to link executable') }
println('Build completed successfully!')
}
pub fn clean(build_config config.BuildConfig) {
println('Cleaning build files...')
// Remove build directory
if os.is_dir(build_config.build_dir) {
os.rmdir_all(build_config.build_dir) or {
println('Warning: Failed to remove ${build_config.build_dir}: ${err}')
}
println('Removed ${build_config.build_dir}')
}
// Remove executable
executable := os.join_path(build_config.bin_dir, build_config.project_name)
if os.is_file(executable) {
os.rm(executable) or {
println('Warning: Failed to remove ${executable}: ${err}')
}
println('Removed ${executable}')
}
println('Clean completed!')
}
fn find_source_files(dir string) ![]string {
mut files := []string{}
if !os.is_dir(dir) {
return error('Source directory does not exist: ${dir}')
}
items := os.ls(dir) or { return error('Failed to list directory: ${dir}') }
for item in items {
full_path := os.join_path(dir, item)
if os.is_file(full_path) {
if item.ends_with('.cpp') || item.ends_with('.cc') || item.ends_with('.cxx') {
files << full_path
}
} else if os.is_dir(full_path) {
// Recursively search subdirectories
sub_files := find_source_files(full_path)!
files << sub_files
}
}
return files
}
fn needs_recompile(source_file string, object_file string) bool {
src_mtime := os.file_last_mod_unix(source_file)
obj_mtime := if os.is_file(object_file) {
os.file_last_mod_unix(object_file)
} else {
0
}
// Source is newer than object
if src_mtime > obj_mtime {
return true
}
// Check dependencies
dependencies := deps.extract_dependencies(source_file) or { return true }
for dep in dependencies {
if !os.is_file(dep) {
return true
}
dep_mtime := os.file_last_mod_unix(dep)
if dep_mtime > obj_mtime {
return true
}
}
return false
}
fn compile_file(source_file string, object_file string, build_config config.BuildConfig) !string {
cmd := config.build_compiler_command(source_file, object_file, build_config)
if build_config.verbose {
println('Compile command: ${cmd}')
}
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Compilation failed with exit code ${res.exit_code}:\n${res.output}')
}
// Generate dependency file
dep_file := object_file.replace('.o', '.d')
deps.generate_dependency_file(source_file, object_file, dep_file)
return object_file
}
fn link_objects(object_files []string, executable string, build_config config.BuildConfig) ! {
cmd := config.build_linker_command(object_files, executable, build_config)
if build_config.verbose {
println('Link command: ${cmd}')
}
res := os.execute(cmd)
if res.exit_code != 0 {
return error('Linking failed with exit code ${res.exit_code}:\n${res.output}')
}
}

181
config/config.v Normal file
View File

@ -0,0 +1,181 @@
module config
import os
// BuildConfig holds the configuration for the project
pub struct BuildConfig {
pub mut:
project_name string
src_dir string
build_dir string
bin_dir string
include_dirs []string
libraries []string
cflags []string
ldflags []string
debug bool
optimize bool
verbose bool
}
// default config
pub const default_config = BuildConfig{
project_name: 'project'
src_dir: 'src'
build_dir: 'build'
bin_dir: 'bin'
include_dirs: []
libraries: []
cflags: []
ldflags: []
debug: true
optimize: false
verbose: false
}
pub fn parse_args() !BuildConfig {
mut build_config := default_config
mut i := 2 // Skip program name and command
for i < os.args.len {
arg := os.args[i]
match arg {
'-d', '--debug' { build_config.debug = true; build_config.optimize = false }
'-O', '--optimize' { build_config.optimize = true; build_config.debug = false }
'-v', '--verbose' { build_config.verbose = true }
'-o', '--output' {
if i + 1 < os.args.len {
build_config.project_name = os.args[i + 1]
i++
}
}
'-I' {
if i + 1 < os.args.len {
build_config.include_dirs << os.args[i + 1]
i++
}
}
'-l' {
if i + 1 < os.args.len {
build_config.libraries << os.args[i + 1]
i++
}
}
'--config' {
if i + 1 < os.args.len {
build_config = parse_config_file(os.args[i + 1])!
i++
}
}
else {
if !arg.starts_with('-') {
build_config.project_name = arg
}
}
}
i++
}
return build_config
}
fn parse_config_file(filename string) !BuildConfig {
content := os.read_file(filename) or { return error('Cannot read config file: ${filename}') }
mut build_config := default_config
lines := content.split_into_lines()
for line in lines {
if line.starts_with('#') || line.trim_space() == '' { continue }
parts := line.split('=')
if parts.len == 2 {
key := parts[0].trim_space()
value := parts[1].trim_space().trim('"\'')
match key {
'project_name' { build_config.project_name = value }
'src_dir' { build_config.src_dir = value }
'build_dir' { build_config.build_dir = value }
'bin_dir' { build_config.bin_dir = value }
'debug' { build_config.debug = value == 'true' }
'optimize' { build_config.optimize = value == 'true' }
'verbose' { build_config.verbose = value == 'true' }
'include_dirs' {
dirs := value.split(',')
for dir in dirs { build_config.include_dirs << dir.trim_space() }
}
'libraries' {
libs := value.split(',')
for lib in libs { build_config.libraries << lib.trim_space() }
}
'cflags' {
flags := value.split(' ')
for flag in flags { build_config.cflags << flag.trim_space() }
}
'ldflags' {
flags := value.split(' ')
for flag in flags { build_config.ldflags << flag.trim_space() }
}
else {}
}
}
}
return build_config
}
// Utility function to build compiler command
pub fn build_compiler_command(source_file string, object_file string, build_config BuildConfig) string {
mut cmd := 'g++ -c'
// Add include directories
for include_dir in build_config.include_dirs {
cmd += ' -I${include_dir}'
}
// Add debug/optimization flags
if build_config.debug {
cmd += ' -g -O0'
} else if build_config.optimize {
cmd += ' -O3'
} else {
cmd += ' -O2'
}
// Add standard flags
cmd += ' -Wall -Wextra -std=c++17'
// Add custom CFLAGS
for flag in build_config.cflags {
cmd += ' ${flag}'
}
cmd += ' ${source_file} -o ${object_file}'
return cmd
}
// Utility function to build linker command
pub fn build_linker_command(object_files []string, executable string, build_config BuildConfig) string {
mut cmd := 'g++'
// Add debug flags for linking
if build_config.debug {
cmd += ' -g'
}
// Add object files
for obj_file in object_files {
cmd += ' ${obj_file}'
}
// Add libraries
for library in build_config.libraries {
cmd += ' -l${library}'
}
// Add custom LDFLAGS
for flag in build_config.ldflags {
cmd += ' ${flag}'
}
cmd += ' -o ${executable}'
return cmd
}

71
deps/deps.v vendored Normal file
View File

@ -0,0 +1,71 @@
module deps
import os
pub fn extract_dependencies(source_file string) ![]string {
mut dependencies := []string{}
content := os.read_file(source_file) or { return []string{} }
mut in_string := false
mut current_string_char := rune(0)
mut i := 0
for i < content.len {
c := content[i]
// Handle string literals
if (c == `"` || c == `'`) && !in_string {
in_string = true
current_string_char = c
} else if c == current_string_char && in_string {
in_string = false
current_string_char = rune(0)
} else if !in_string {
if c == `#` && i + 1 < content.len && content[i + 1] == `i` {
// Found #include
i += 7 // skip "#include"
for i < content.len && content[i].is_space() {
i++
}
if i < content.len && (content[i] == `"` || content[i] == `<`) {
mut quote_char := content[i]
i++
mut include_path := []u8{}
for i < content.len && content[i] != quote_char {
include_path << content[i]
i++
}
if include_path.len > 0 {
include_name := include_path.bytestr()
if include_name.contains('/') || include_name.contains('\\') {
// Relative path
dependencies << include_name
} else {
// System include - we could search standard paths
// but for now just add the name
dependencies << include_name
}
}
}
}
}
i++
}
return dependencies
}
pub fn generate_dependency_file(source_file string, object_file string, dep_file string) {
dependencies := extract_dependencies(source_file) or { return }
mut content := '${object_file}: ${source_file}\n'
for dep in dependencies {
content += '\t${dep}\n'
}
os.write_file(dep_file, content) or { }
}

33
help/help.v Normal file
View File

@ -0,0 +1,33 @@
module help
pub fn show_help() {
println('lana - Vlang C++ Build System')
println('Usage: lana [command] [options]')
println('')
println('Commands:')
println(' build Build the project')
println(' clean Clean build files')
println(' run Build and run the project')
println(' init <name> Initialize new project')
println('')
println('Options:')
println(' -d, --debug Enable debug mode')
println(' -O, --optimize Enable optimization')
println(' -v, --verbose Verbose output')
println(' -o, --output Set output name')
println(' -I <dir> Add include directory')
println(' -l <lib> Add library')
println(' --config <file> Use config file')
println('')
println('Examples:')
println(' lana build -d -I include/mylib')
println(' lana run')
println(' lana init myproject')
println('')
println('Project Structure:')
println(' src/ - Source files (.cpp, .cc, .cxx)')
println(' include/ - Header files (.h, .hpp)')
println(' build/ - Object files and intermediates')
println(' bin/ - Executable output')
println(' config.ini - Build configuration')
}

138
initializer/initializer.v Normal file
View File

@ -0,0 +1,138 @@
module initializer
import os
pub fn init_project(project_name string) {
println('Initializing C++ project: ${project_name}')
// Create directory structure
dirs := ['src', 'include', 'build', 'bin']
for dir in dirs {
full_path := os.join_path(project_name, dir)
os.mkdir_all(full_path) or {
println('Warning: Failed to create ${full_path}: ${err}')
}
}
// Create basic CMakeLists.txt
cmake_content := r'
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(${project_name})
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Add executable
add_executable(${project_name} src/main.cpp)
# Add include directories
target_include_directories(${project_name} PRIVATE include)
'
os.write_file(os.join_path(project_name, 'CMakeLists.txt'), cmake_content) or { }
// Create main.cpp
main_content := r'
#include <iostream>
int main() {
std::cout << "Hello, ${project_name}!" << std::endl;
return 0;
}
'
os.write_file(os.join_path(project_name, 'src', 'main.cpp'), main_content) or { }
// Create .gitignore
gitignore_content := r'
# Build files
build/
bin/
*.o
*.exe
*.dSYM
*.d
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
'
os.write_file(os.join_path(project_name, '.gitignore'), gitignore_content) or { }
// Create config.ini
config_content := r'
# ${project_name} lana build configuration
project_name = ${project_name}
src_dir = src
build_dir = build
bin_dir = bin
debug = true
optimize = false
verbose = false
include_dirs = include
'
os.write_file(os.join_path(project_name, 'config.ini'), config_content) or { }
// Create README.md
readme_content := r'
# ${project_name}
A C++ project built with lana (Vlang C++ Build System)
## Getting Started
### Build the project
```bash
lana build
```
### Run the project
```bash
lana run
```
### Clean build files
```bash
lana clean
```
## Project Structure
- `src/` - Source files
- `include/` - Header files
- `build/` - Object files and intermediate build files
- `bin/` - Executable output
## Configuration
Edit `config.ini` to customize build settings:
- `debug` - Enable/disable debug mode
- `optimize` - Enable/disable optimization
- `include_dirs` - Additional include directories
- `libraries` - Linker libraries to include
## Command Line Options
```bash
lana build [options]
-d, --debug Enable debug mode
-O, --optimize Enable optimization
-v, --verbose Verbose output
-I <dir> Add include directory
-l <lib> Add library
-o <name> Set output name
--config <file> Use custom config file
```
'
os.write_file(os.join_path(project_name, 'README.md'), readme_content) or { }
println('Project initialized successfully!')
println('Created directory structure and template files')
println('')
println('Usage:')
println(' cd ${project_name}')
println(' lana build')
println(' lana run')
}

28
lana.v Normal file
View File

@ -0,0 +1,28 @@
module main
import os
import config
import builder
import runner
import initializer
import help
fn main() {
mut config_data := config.parse_args() or { config.default_config }
if os.args.len < 2 {
help.show_help()
return
}
match os.args[1] {
'build' { builder.build(mut config_data) or { return } }
'clean' { builder.clean(config_data) }
'run' {
builder.build(mut config_data) or { return }
runner.run_executable(config_data)
}
'init' { initializer.init_project(os.args[2] or { 'myproject' }) }
else { help.show_help() }
}
}

33
runner/runner.v Normal file
View File

@ -0,0 +1,33 @@
module runner
import os
import config
pub fn run_executable(build_config config.BuildConfig) {
executable := os.join_path(build_config.bin_dir, build_config.project_name)
if !os.is_file(executable) {
println('Executable not found: ${executable}')
println('Please run "lana build" first')
return
}
println('Running ${executable}...')
res := os.execute('${executable}')
if res.exit_code != 0 {
println('Failed to execute: ${res.output}')
return
}
if res.exit_code == 0 {
println('Execution completed successfully!')
if res.output.len > 0 {
println(res.output)
}
} else {
println('Execution failed with exit code ${res.exit_code}')
if res.output.len > 0 {
println('Output:\n${res.output}')
}
}
}