From efb70b3662da4bb072ea91d36740689314905fd3 Mon Sep 17 00:00:00 2001 From: Jocadbz Date: Sat, 13 Sep 2025 19:39:35 -0300 Subject: [PATCH] Initial commit --- builder/builder.v | 161 +++++++++++++++++++++++++++++++++ config/config.v | 181 ++++++++++++++++++++++++++++++++++++++ deps/deps.v | 71 +++++++++++++++ help/help.v | 33 +++++++ initializer/initializer.v | 138 +++++++++++++++++++++++++++++ lana.v | 28 ++++++ runner/runner.v | 33 +++++++ 7 files changed, 645 insertions(+) create mode 100644 builder/builder.v create mode 100644 config/config.v create mode 100644 deps/deps.v create mode 100644 help/help.v create mode 100644 initializer/initializer.v create mode 100644 lana.v create mode 100644 runner/runner.v diff --git a/builder/builder.v b/builder/builder.v new file mode 100644 index 0000000..c3b655e --- /dev/null +++ b/builder/builder.v @@ -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}') + } +} \ No newline at end of file diff --git a/config/config.v b/config/config.v new file mode 100644 index 0000000..6f5754f --- /dev/null +++ b/config/config.v @@ -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 +} \ No newline at end of file diff --git a/deps/deps.v b/deps/deps.v new file mode 100644 index 0000000..a30b7f2 --- /dev/null +++ b/deps/deps.v @@ -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 { } +} \ No newline at end of file diff --git a/help/help.v b/help/help.v new file mode 100644 index 0000000..ae9a893 --- /dev/null +++ b/help/help.v @@ -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 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 Add include directory') + println(' -l Add library') + println(' --config 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') +} \ No newline at end of file diff --git a/initializer/initializer.v b/initializer/initializer.v new file mode 100644 index 0000000..4db4535 --- /dev/null +++ b/initializer/initializer.v @@ -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 + +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 Add include directory + -l Add library + -o Set output name + --config 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') +} \ No newline at end of file diff --git a/lana.v b/lana.v new file mode 100644 index 0000000..9ff1576 --- /dev/null +++ b/lana.v @@ -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() } + } +} \ No newline at end of file diff --git a/runner/runner.v b/runner/runner.v new file mode 100644 index 0000000..f813d6a --- /dev/null +++ b/runner/runner.v @@ -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}') + } + } +} \ No newline at end of file