Implement build graph structure and dependency resolution for shared libraries and tools
parent
b2cdeaebd6
commit
1ab3f9c175
|
|
@ -38,11 +38,39 @@ struct CompileResult {
|
||||||
err string
|
err string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BuildNode {
|
||||||
|
id string
|
||||||
|
name string
|
||||||
|
target BuildTarget
|
||||||
|
raw_dependencies []string
|
||||||
|
mut:
|
||||||
|
dependencies []string
|
||||||
|
shared_lib_idx int = -1
|
||||||
|
tool_idx int = -1
|
||||||
|
is_directive bool
|
||||||
|
directive config.BuildDirective
|
||||||
|
output_path string
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BuildGraph {
|
||||||
|
nodes []BuildNode
|
||||||
|
node_index map[string]int
|
||||||
|
order []int
|
||||||
|
unresolved map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DirectiveBuildContext {
|
||||||
|
source string
|
||||||
|
object string
|
||||||
|
target_config config.TargetConfig
|
||||||
|
}
|
||||||
|
|
||||||
const ansi_reset = '\x1b[0m'
|
const ansi_reset = '\x1b[0m'
|
||||||
const ansi_red = '\x1b[31m'
|
const ansi_red = '\x1b[31m'
|
||||||
const ansi_green = '\x1b[32m'
|
const ansi_green = '\x1b[32m'
|
||||||
const ansi_yellow = '\x1b[33m'
|
const ansi_yellow = '\x1b[33m'
|
||||||
const ansi_cyan = '\x1b[36m'
|
const ansi_cyan = '\x1b[36m'
|
||||||
|
const skip_directive_err = 'skip_directive'
|
||||||
|
|
||||||
fn should_use_color() bool {
|
fn should_use_color() bool {
|
||||||
return os.getenv('NO_COLOR') == '' && os.is_atty(1) != 0
|
return os.getenv('NO_COLOR') == '' && os.is_atty(1) != 0
|
||||||
|
|
@ -55,6 +83,89 @@ fn colorize(text string, color string) string {
|
||||||
return '${color}${text}${ansi_reset}'
|
return '${color}${text}${ansi_reset}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn register_alias(mut alias_map map[string]string, alias string, id string) {
|
||||||
|
trimmed := alias.trim_space()
|
||||||
|
if trimmed == '' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if trimmed in alias_map {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
alias_map[trimmed] = id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_dependency(alias_map map[string]string, dep string) string {
|
||||||
|
trimmed := dep.trim_space()
|
||||||
|
if trimmed == '' {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
mut candidates := []string{}
|
||||||
|
candidates << trimmed
|
||||||
|
if trimmed.ends_with('.so') && trimmed.len > 3 {
|
||||||
|
base := trimmed[..trimmed.len - 3]
|
||||||
|
candidates << base
|
||||||
|
if base.starts_with('lib/') && base.len > 4 {
|
||||||
|
candidates << base[4..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if trimmed.starts_with('lib/') && trimmed.len > 4 {
|
||||||
|
candidates << trimmed[4..]
|
||||||
|
}
|
||||||
|
if trimmed.contains('/') {
|
||||||
|
parts := trimmed.split('/')
|
||||||
|
if parts.len > 0 {
|
||||||
|
candidates << parts[parts.len - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for candidate in candidates {
|
||||||
|
if candidate in alias_map {
|
||||||
|
return alias_map[candidate]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
fn topo_sort_nodes(nodes []BuildNode, node_index map[string]int) ![]int {
|
||||||
|
mut indegree := []int{len: nodes.len, init: 0}
|
||||||
|
mut adjacency := [][]int{len: nodes.len}
|
||||||
|
for idx, node in nodes {
|
||||||
|
for dep_id in node.dependencies {
|
||||||
|
dep_idx := node_index[dep_id] or {
|
||||||
|
return error('Unknown dependency ${dep_id} referenced by node ${node.id}')
|
||||||
|
}
|
||||||
|
adjacency[dep_idx] << idx
|
||||||
|
indegree[idx] += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mut queue := []int{}
|
||||||
|
for idx, deg in indegree {
|
||||||
|
if deg == 0 {
|
||||||
|
queue << idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mut order := []int{}
|
||||||
|
mut head := 0
|
||||||
|
for head < queue.len {
|
||||||
|
current := queue[head]
|
||||||
|
head += 1
|
||||||
|
order << current
|
||||||
|
for neighbor in adjacency[current] {
|
||||||
|
indegree[neighbor] -= 1
|
||||||
|
if indegree[neighbor] == 0 {
|
||||||
|
queue << neighbor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.len != nodes.len {
|
||||||
|
return error('Build graph contains a cycle or unresolved dependency')
|
||||||
|
}
|
||||||
|
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
||||||
fn run_compile_tasks(tasks []CompileTask, build_config config.BuildConfig) ![]string {
|
fn run_compile_tasks(tasks []CompileTask, build_config config.BuildConfig) ![]string {
|
||||||
mut object_files := []string{}
|
mut object_files := []string{}
|
||||||
if tasks.len == 0 {
|
if tasks.len == 0 {
|
||||||
|
|
@ -124,6 +235,327 @@ fn run_compile_tasks(tasks []CompileTask, build_config config.BuildConfig) ![]st
|
||||||
return object_files
|
return object_files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn plan_build_graph(build_config &config.BuildConfig) !BuildGraph {
|
||||||
|
mut nodes := []BuildNode{}
|
||||||
|
mut alias_map := map[string]string{}
|
||||||
|
|
||||||
|
for idx, lib_config in build_config.shared_libs {
|
||||||
|
if lib_config.sources.len == 0 {
|
||||||
|
if build_config.debug || build_config.verbose || lib_config.debug || lib_config.verbose {
|
||||||
|
println('Skipping empty shared library: ${lib_config.name}')
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node_id := 'shared:${lib_config.name}'
|
||||||
|
node := BuildNode{
|
||||||
|
id: node_id
|
||||||
|
name: lib_config.name
|
||||||
|
target: BuildTarget.shared_lib
|
||||||
|
raw_dependencies: lib_config.libraries.clone()
|
||||||
|
shared_lib_idx: idx
|
||||||
|
}
|
||||||
|
nodes << node
|
||||||
|
register_alias(mut alias_map, lib_config.name, node_id)
|
||||||
|
register_alias(mut alias_map, 'lib/${lib_config.name}', node_id)
|
||||||
|
register_alias(mut alias_map, '${lib_config.name}.so', node_id)
|
||||||
|
register_alias(mut alias_map, 'lib/${lib_config.name}.so', node_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
for directive in build_config.build_directives {
|
||||||
|
node_id := 'directive:${directive.unit_name}'
|
||||||
|
node := BuildNode{
|
||||||
|
id: node_id
|
||||||
|
name: directive.unit_name
|
||||||
|
target: if directive.is_shared { BuildTarget.shared_lib } else { BuildTarget.tool }
|
||||||
|
raw_dependencies: directive.depends_units.clone()
|
||||||
|
is_directive: true
|
||||||
|
directive: directive
|
||||||
|
output_path: directive.output_path
|
||||||
|
}
|
||||||
|
nodes << node
|
||||||
|
register_alias(mut alias_map, directive.unit_name, node_id)
|
||||||
|
parts := directive.unit_name.split('/')
|
||||||
|
if parts.len > 0 {
|
||||||
|
base := parts[parts.len - 1]
|
||||||
|
register_alias(mut alias_map, base, node_id)
|
||||||
|
if directive.is_shared {
|
||||||
|
register_alias(mut alias_map, '${base}.so', node_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if directive.output_path != '' {
|
||||||
|
register_alias(mut alias_map, directive.output_path, node_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, tool_config in build_config.tools {
|
||||||
|
if tool_config.sources.len == 0 {
|
||||||
|
if build_config.debug || build_config.verbose || tool_config.debug || tool_config.verbose {
|
||||||
|
println('Skipping empty tool: ${tool_config.name}')
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node_id := 'tool:${tool_config.name}'
|
||||||
|
node := BuildNode{
|
||||||
|
id: node_id
|
||||||
|
name: tool_config.name
|
||||||
|
target: BuildTarget.tool
|
||||||
|
raw_dependencies: tool_config.libraries.clone()
|
||||||
|
tool_idx: idx
|
||||||
|
}
|
||||||
|
nodes << node
|
||||||
|
register_alias(mut alias_map, tool_config.name, node_id)
|
||||||
|
register_alias(mut alias_map, 'tools/${tool_config.name}', node_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
mut node_index := map[string]int{}
|
||||||
|
for idx, node in nodes {
|
||||||
|
if node.id in node_index {
|
||||||
|
return error('Duplicate node id detected in build graph: ${node.id}')
|
||||||
|
}
|
||||||
|
node_index[node.id] = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
mut unresolved := map[string][]string{}
|
||||||
|
for idx in 0 .. nodes.len {
|
||||||
|
mut resolved := []string{}
|
||||||
|
mut missing := []string{}
|
||||||
|
for dep in nodes[idx].raw_dependencies {
|
||||||
|
dep_id := resolve_dependency(alias_map, dep)
|
||||||
|
if dep_id == '' {
|
||||||
|
missing << dep
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if dep_id !in resolved {
|
||||||
|
resolved << dep_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes[idx].dependencies = resolved
|
||||||
|
if missing.len > 0 {
|
||||||
|
unresolved[nodes[idx].id] = missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
order := if nodes.len > 0 { topo_sort_nodes(nodes, node_index)! } else { []int{} }
|
||||||
|
|
||||||
|
return BuildGraph{
|
||||||
|
nodes: nodes
|
||||||
|
node_index: node_index
|
||||||
|
order: order
|
||||||
|
unresolved: unresolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_build_graph(mut build_config config.BuildConfig, graph BuildGraph) ! {
|
||||||
|
for node_idx in graph.order {
|
||||||
|
node := graph.nodes[node_idx]
|
||||||
|
if node.id in graph.unresolved && build_config.verbose {
|
||||||
|
missing := graph.unresolved[node.id]
|
||||||
|
println(colorize('Warning: Unresolved dependencies for ${node.name}: ${missing.join(", ")}', ansi_yellow))
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.is_directive && (build_config.debug || build_config.verbose) {
|
||||||
|
println('Building unit: ${node.name}')
|
||||||
|
}
|
||||||
|
|
||||||
|
match node.target {
|
||||||
|
.shared_lib {
|
||||||
|
if node.is_directive {
|
||||||
|
build_directive_shared(node.directive, build_config) or {
|
||||||
|
return error('Failed to build shared directive ${node.name}: ${err}')
|
||||||
|
}
|
||||||
|
} else if node.shared_lib_idx >= 0 {
|
||||||
|
mut lib_config := &build_config.shared_libs[node.shared_lib_idx]
|
||||||
|
if build_config.debug || build_config.verbose || lib_config.debug || lib_config.verbose {
|
||||||
|
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}')
|
||||||
|
}
|
||||||
|
if build_config.verbose {
|
||||||
|
println('Built shared library: ${lib_config.name}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tool {
|
||||||
|
if node.is_directive {
|
||||||
|
build_directive_tool(node.directive, build_config) or {
|
||||||
|
return error('Failed to build tool directive ${node.name}: ${err}')
|
||||||
|
}
|
||||||
|
} else if node.tool_idx >= 0 {
|
||||||
|
mut tool_config := &build_config.tools[node.tool_idx]
|
||||||
|
if build_config.debug || build_config.verbose || tool_config.debug || tool_config.verbose {
|
||||||
|
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}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_directive_source(directive config.BuildDirective, build_config config.BuildConfig) string {
|
||||||
|
extensions := ['.cpp', '.cc', '.cxx']
|
||||||
|
for ext in extensions {
|
||||||
|
candidate := os.join_path(build_config.src_dir, directive.unit_name + ext)
|
||||||
|
if os.is_file(candidate) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := directive.unit_name.split('/')
|
||||||
|
base := if parts.len > 0 { parts[parts.len - 1] } else { directive.unit_name }
|
||||||
|
for ext in extensions {
|
||||||
|
candidate := os.join_path(build_config.src_dir, base + ext)
|
||||||
|
if os.is_file(candidate) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_directive_build(directive config.BuildDirective, build_config config.BuildConfig) !DirectiveBuildContext {
|
||||||
|
source_file := resolve_directive_source(directive, build_config)
|
||||||
|
if source_file == '' {
|
||||||
|
if build_config.verbose {
|
||||||
|
println(colorize('Warning: Source file not found for unit ${directive.unit_name}', ansi_yellow))
|
||||||
|
}
|
||||||
|
return error(skip_directive_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
object_dir := os.join_path(build_config.build_dir, directive.unit_name)
|
||||||
|
os.mkdir_all(object_dir) or {
|
||||||
|
return error('Failed to create object directory: ${object_dir}')
|
||||||
|
}
|
||||||
|
|
||||||
|
obj_file := get_object_file(source_file, object_dir)
|
||||||
|
obj_path := os.dir(obj_file)
|
||||||
|
os.mkdir_all(obj_path) or {
|
||||||
|
return error('Failed to create object directory: ${obj_path}')
|
||||||
|
}
|
||||||
|
|
||||||
|
target_config := if directive.is_shared {
|
||||||
|
config.TargetConfig(config.SharedLibConfig{
|
||||||
|
name: directive.unit_name
|
||||||
|
sources: [source_file]
|
||||||
|
libraries: directive.link_libs
|
||||||
|
cflags: directive.cflags
|
||||||
|
ldflags: directive.ldflags
|
||||||
|
debug: build_config.debug
|
||||||
|
optimize: build_config.optimize
|
||||||
|
verbose: build_config.verbose
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
config.TargetConfig(config.ToolConfig{
|
||||||
|
name: directive.unit_name
|
||||||
|
sources: [source_file]
|
||||||
|
libraries: directive.link_libs
|
||||||
|
cflags: directive.cflags
|
||||||
|
ldflags: directive.ldflags
|
||||||
|
debug: build_config.debug
|
||||||
|
optimize: build_config.optimize
|
||||||
|
verbose: build_config.verbose
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return DirectiveBuildContext{
|
||||||
|
source: source_file
|
||||||
|
object: obj_file
|
||||||
|
target_config: target_config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_directive_shared(directive config.BuildDirective, build_config config.BuildConfig) ! {
|
||||||
|
ctx := prepare_directive_build(directive, build_config) or {
|
||||||
|
if err.msg() == skip_directive_err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return error(err.msg())
|
||||||
|
}
|
||||||
|
|
||||||
|
if needs_recompile(ctx.source, ctx.object) {
|
||||||
|
if build_config.debug || build_config.verbose {
|
||||||
|
println('Compiling ${directive.unit_name}: ${ctx.source}...')
|
||||||
|
}
|
||||||
|
compile_file(ctx.source, ctx.object, build_config, ctx.target_config) or {
|
||||||
|
return error('Failed to compile ${ctx.source} for ${directive.unit_name}')
|
||||||
|
}
|
||||||
|
} else if build_config.verbose {
|
||||||
|
println('Using cached ${ctx.object} for ${directive.unit_name}')
|
||||||
|
}
|
||||||
|
|
||||||
|
lib_output_dir := os.join_path(build_config.bin_dir, 'lib')
|
||||||
|
os.mkdir_all(lib_output_dir) or {
|
||||||
|
return error('Failed to create shared lib output directory: ${lib_output_dir}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if build_config.debug || build_config.verbose {
|
||||||
|
parts := directive.unit_name.split('/')
|
||||||
|
base := if parts.len > 0 { parts[parts.len - 1] } else { directive.unit_name }
|
||||||
|
println('Linking shared library: ${lib_output_dir}/${base}.so')
|
||||||
|
}
|
||||||
|
|
||||||
|
link_shared_library([ctx.object], directive.unit_name, lib_output_dir, build_config, config.SharedLibConfig{
|
||||||
|
name: directive.unit_name
|
||||||
|
libraries: directive.link_libs
|
||||||
|
debug: build_config.debug
|
||||||
|
optimize: build_config.optimize
|
||||||
|
verbose: build_config.verbose
|
||||||
|
ldflags: directive.ldflags
|
||||||
|
}) or {
|
||||||
|
return error('Failed to link shared library ${directive.unit_name}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if build_config.verbose {
|
||||||
|
println('Successfully built unit: ${directive.unit_name}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_directive_tool(directive config.BuildDirective, build_config config.BuildConfig) ! {
|
||||||
|
ctx := prepare_directive_build(directive, build_config) or {
|
||||||
|
if err.msg() == skip_directive_err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return error(err.msg())
|
||||||
|
}
|
||||||
|
|
||||||
|
if needs_recompile(ctx.source, ctx.object) {
|
||||||
|
if build_config.debug || build_config.verbose {
|
||||||
|
println('Compiling ${directive.unit_name}: ${ctx.source}...')
|
||||||
|
}
|
||||||
|
compile_file(ctx.source, ctx.object, build_config, ctx.target_config) or {
|
||||||
|
return error('Failed to compile ${ctx.source} for ${directive.unit_name}')
|
||||||
|
}
|
||||||
|
} else if build_config.verbose {
|
||||||
|
println('Using cached ${ctx.object} for ${directive.unit_name}')
|
||||||
|
}
|
||||||
|
|
||||||
|
executable := os.join_path(build_config.bin_dir, directive.output_path)
|
||||||
|
if build_config.debug || build_config.verbose {
|
||||||
|
println('Linking executable: ${executable}')
|
||||||
|
}
|
||||||
|
|
||||||
|
link_tool([ctx.object], executable, build_config, config.ToolConfig{
|
||||||
|
name: directive.unit_name
|
||||||
|
libraries: directive.link_libs
|
||||||
|
debug: build_config.debug
|
||||||
|
optimize: build_config.optimize
|
||||||
|
verbose: build_config.verbose
|
||||||
|
ldflags: directive.ldflags
|
||||||
|
}) or {
|
||||||
|
return error('Failed to link executable ${directive.unit_name}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if build_config.verbose {
|
||||||
|
println('Successfully built unit: ${directive.unit_name}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(mut build_config config.BuildConfig) ! {
|
pub fn build(mut build_config config.BuildConfig) ! {
|
||||||
// Run build flow and ensure that if any error occurs we print its message
|
// Run build flow and ensure that if any error occurs we print its message
|
||||||
start_time := time.now()
|
start_time := time.now()
|
||||||
|
|
@ -138,51 +570,8 @@ pub fn build(mut build_config config.BuildConfig) ! {
|
||||||
|
|
||||||
// Auto-discover sources if not specified
|
// Auto-discover sources if not specified
|
||||||
auto_discover_sources(mut build_config)
|
auto_discover_sources(mut build_config)
|
||||||
|
graph := plan_build_graph(&build_config)!
|
||||||
// Build shared libraries first (from config)
|
execute_build_graph(mut build_config, graph)!
|
||||||
mut shared_libs_built := []string{}
|
|
||||||
for mut lib_config in build_config.shared_libs {
|
|
||||||
if lib_config.sources.len == 0 {
|
|
||||||
if build_config.debug || build_config.verbose || lib_config.debug || lib_config.verbose {
|
|
||||||
println('Skipping empty shared library: ${lib_config.name}')
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if build_config.debug || build_config.verbose || lib_config.debug || lib_config.verbose {
|
|
||||||
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 {
|
|
||||||
if build_config.debug || build_config.verbose || tool_config.debug || tool_config.verbose {
|
|
||||||
println('Skipping empty tool: ${tool_config.name}')
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if build_config.debug || build_config.verbose || tool_config.debug || tool_config.verbose {
|
|
||||||
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}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -201,183 +590,6 @@ pub fn build(mut build_config config.BuildConfig) ! {
|
||||||
println('Build time: ${elapsed.seconds():.2f}s')
|
println('Build time: ${elapsed.seconds():.2f}s')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build targets based on build directives from source files
|
|
||||||
fn build_from_directives(mut build_config config.BuildConfig, mut shared_libs_built []string) ! {
|
|
||||||
// Build a dependency graph from directives
|
|
||||||
mut dep_graph := map[string]config.BuildDirective{}
|
|
||||||
mut build_order := []string{}
|
|
||||||
mut built_units := []string{}
|
|
||||||
|
|
||||||
// Initialize graph with all directives
|
|
||||||
for directive in build_config.build_directives {
|
|
||||||
dep_graph[directive.unit_name] = directive
|
|
||||||
}
|
|
||||||
|
|
||||||
// Topological sort to determine build order
|
|
||||||
for unit_name, _ in dep_graph {
|
|
||||||
if unit_name in built_units {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
build_unit_recursive(unit_name, dep_graph, mut build_order, mut built_units, mut build_config, shared_libs_built)!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build units in determined order
|
|
||||||
for unit_name in build_order {
|
|
||||||
directive := dep_graph[unit_name]
|
|
||||||
|
|
||||||
if build_config.debug || build_config.verbose {
|
|
||||||
println('Building unit: ${unit_name}')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find source file for this unit
|
|
||||||
mut source_file := ''
|
|
||||||
// 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(colorize('Warning: Source file not found for unit ${unit_name}', ansi_yellow))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create object directory
|
|
||||||
object_dir := os.join_path(build_config.build_dir, directive.unit_name)
|
|
||||||
os.mkdir_all(object_dir) or { return error('Failed to create object directory: ${object_dir}') }
|
|
||||||
|
|
||||||
obj_file := get_object_file(source_file, object_dir)
|
|
||||||
|
|
||||||
// Compile source file
|
|
||||||
if needs_recompile(source_file, obj_file) {
|
|
||||||
if build_config.debug || build_config.verbose {
|
|
||||||
println('Compiling ${unit_name}: ${source_file}...')
|
|
||||||
}
|
|
||||||
target_config := config.TargetConfig(config.ToolConfig{
|
|
||||||
name: unit_name
|
|
||||||
sources: [source_file]
|
|
||||||
debug: build_config.debug
|
|
||||||
optimize: build_config.optimize
|
|
||||||
verbose: build_config.verbose
|
|
||||||
cflags: directive.cflags
|
|
||||||
ldflags: directive.ldflags
|
|
||||||
})
|
|
||||||
compile_file(source_file, obj_file, build_config, target_config) or {
|
|
||||||
return error('Failed to compile ${source_file} for ${unit_name}')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if build_config.verbose {
|
|
||||||
println('Using cached ${obj_file} for ${unit_name}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link executable or shared library
|
|
||||||
if directive.is_shared {
|
|
||||||
// Link shared library
|
|
||||||
// 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}') }
|
|
||||||
if build_config.debug || build_config.verbose {
|
|
||||||
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
|
|
||||||
optimize: build_config.optimize
|
|
||||||
verbose: build_config.verbose
|
|
||||||
ldflags: directive.ldflags
|
|
||||||
}) or {
|
|
||||||
return error('Failed to link shared library ${unit_name}')
|
|
||||||
}
|
|
||||||
shared_libs_built << directive.unit_name
|
|
||||||
} else {
|
|
||||||
// Link executable
|
|
||||||
executable := os.join_path(build_config.bin_dir, directive.output_path)
|
|
||||||
if build_config.debug || build_config.verbose {
|
|
||||||
println('Linking executable: ${executable}')
|
|
||||||
}
|
|
||||||
link_tool([obj_file], executable, build_config, config.ToolConfig{
|
|
||||||
name: directive.unit_name
|
|
||||||
libraries: directive.link_libs
|
|
||||||
debug: build_config.debug
|
|
||||||
optimize: build_config.optimize
|
|
||||||
verbose: build_config.verbose
|
|
||||||
ldflags: directive.ldflags
|
|
||||||
}) or {
|
|
||||||
return error('Failed to link executable ${unit_name}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if build_config.verbose {
|
|
||||||
println('Successfully built unit: ${unit_name}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively build unit and its dependencies
|
|
||||||
fn build_unit_recursive(unit_name string, dep_graph map[string]config.BuildDirective, mut build_order []string, mut built_units []string, mut build_config config.BuildConfig, shared_libs_built []string) ! {
|
|
||||||
if unit_name in built_units {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build dependencies first
|
|
||||||
directive := dep_graph[unit_name]
|
|
||||||
for dep_unit in directive.depends_units {
|
|
||||||
if dep_unit in dep_graph {
|
|
||||||
build_unit_recursive(dep_unit, dep_graph, mut build_order, mut built_units, mut build_config, shared_libs_built)!
|
|
||||||
} else if !dep_unit.ends_with('.so') && !dep_unit.contains('.') {
|
|
||||||
// Look for library in shared_libs_built
|
|
||||||
lib_name := 'lib/${dep_unit}'
|
|
||||||
if lib_name !in shared_libs_built {
|
|
||||||
if build_config.verbose {
|
|
||||||
println(colorize('Warning: Dependency ${dep_unit} not found for ${unit_name}', ansi_yellow))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build_order << unit_name
|
|
||||||
built_units << unit_name
|
|
||||||
}
|
|
||||||
|
|
||||||
fn auto_discover_sources(mut build_config config.BuildConfig) {
|
fn auto_discover_sources(mut build_config config.BuildConfig) {
|
||||||
// Auto-discover shared library sources
|
// Auto-discover shared library sources
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue