Add static linking support and related configuration options
parent
f74b93ddf6
commit
1a233baed3
|
|
@ -592,6 +592,9 @@ fn build_directive_tool(directive config.BuildDirective, build_config config.Bui
|
|||
println('Linking executable: ${executable}')
|
||||
}
|
||||
|
||||
// Directive static(true/false) overrides global config, otherwise inherit global setting
|
||||
use_static := directive.static_link or { build_config.static_link }
|
||||
|
||||
link_tool([ctx.object], executable, build_config, toolchain, config.ToolConfig{
|
||||
name: directive.unit_name
|
||||
libraries: directive.link_libs
|
||||
|
|
@ -599,6 +602,7 @@ fn build_directive_tool(directive config.BuildDirective, build_config config.Bui
|
|||
optimize: build_config.optimize
|
||||
verbose: build_config.verbose
|
||||
ldflags: directive.ldflags
|
||||
static_link: use_static
|
||||
}) or {
|
||||
return error('Failed to link executable ${directive.unit_name}')
|
||||
}
|
||||
|
|
@ -716,9 +720,25 @@ fn auto_discover_sources(mut build_config config.BuildConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
// Also check if the main project exists as a build directive
|
||||
if !main_tool_exists {
|
||||
for directive in build_config.build_directives {
|
||||
// Check if directive unit_name matches project_name or ends with project_name
|
||||
if directive.unit_name == build_config.project_name {
|
||||
main_tool_exists = true
|
||||
break
|
||||
}
|
||||
// Also match "fossvg" to directive "fossvg" or "tools/fossvg"
|
||||
parts := directive.unit_name.split('/')
|
||||
if parts.len > 0 && parts[parts.len - 1] == build_config.project_name {
|
||||
main_tool_exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !main_tool_exists {
|
||||
// Look for src/<project_name>.cpp
|
||||
// Really shit way to do it but meh, we'll figure this out soon enough.
|
||||
main_src := os.join_path(build_config.src_dir, '${build_config.project_name}.cpp')
|
||||
if os.is_file(main_src) {
|
||||
if build_config.verbose {
|
||||
|
|
@ -978,6 +998,49 @@ fn link_shared_library(object_files []string, library_name string, output_path s
|
|||
println(colorize('Linker output:\n${res.output}', ansi_red))
|
||||
return error('Shared library linking failed with exit code ${res.exit_code}: ${res.output}')
|
||||
}
|
||||
|
||||
// Check if static archive is needed: either global static_link or any tool has static_link
|
||||
needs_static_archive := build_config.static_link || any_tool_needs_static_link(build_config)
|
||||
if needs_static_archive {
|
||||
create_static_archive(object_files, library_name, output_path, build_config, lib_config)!
|
||||
}
|
||||
}
|
||||
|
||||
// any_tool_needs_static_link returns true if any tool in the config has static_link enabled
|
||||
pub fn any_tool_needs_static_link(build_config config.BuildConfig) bool {
|
||||
for tool in build_config.tools {
|
||||
if tool.static_link {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// create_static_archive creates a static library (.a) from object files using ar
|
||||
fn create_static_archive(object_files []string, library_name string, output_path string, build_config config.BuildConfig, lib_config config.SharedLibConfig) ! {
|
||||
// Extract base name from library_name (e.g., "lib/cli" -> "cli")
|
||||
parts := library_name.split('/')
|
||||
base_name := if parts.len > 0 { parts[parts.len - 1] } else { library_name }
|
||||
|
||||
archive_path := os.join_path(output_path, '${base_name}.a')
|
||||
|
||||
// Build ar command: ar rcs <archive> <objects...>
|
||||
mut cmd := 'ar rcs ${archive_path}'
|
||||
for obj_file in object_files {
|
||||
cmd += ' ${obj_file}'
|
||||
}
|
||||
|
||||
if lib_config.verbose || build_config.verbose {
|
||||
println('Creating static archive: ${archive_path}')
|
||||
println('Archive command: ${cmd}')
|
||||
}
|
||||
|
||||
ar_res := os.execute(cmd)
|
||||
if ar_res.exit_code != 0 {
|
||||
println(colorize('Archive command: ${cmd}', ansi_cyan))
|
||||
println(colorize('Archive output:\n${ar_res.output}', ansi_red))
|
||||
return error('Static archive creation failed with exit code ${ar_res.exit_code}: ${ar_res.output}')
|
||||
}
|
||||
}
|
||||
|
||||
fn link_tool(object_files []string, executable string, build_config config.BuildConfig, toolchain config.Toolchain, tool_config config.ToolConfig) ! {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub mut:
|
|||
cflags []string // file-specific CFLAGS
|
||||
ldflags []string // file-specific LDFLAGS
|
||||
is_shared bool // whether this is a shared library
|
||||
static_link ?bool // override global static_link setting (none = use global)
|
||||
}
|
||||
|
||||
// TargetConfig is a sum type for build targets (shared lib or tool)
|
||||
|
|
@ -46,6 +47,7 @@ pub mut:
|
|||
debug bool
|
||||
optimize bool
|
||||
verbose bool
|
||||
static_link bool // link statically to produce self-contained binary
|
||||
}
|
||||
|
||||
// Dependency represents an external dependency to download/extract
|
||||
|
|
@ -83,6 +85,7 @@ pub mut:
|
|||
dependencies_dir string = 'dependencies' // external dependencies
|
||||
parallel_compilation bool = true // enable parallel builds
|
||||
dependencies []Dependency
|
||||
static_link bool // global static linking flag for tools
|
||||
|
||||
// Build directives from source files
|
||||
build_directives []BuildDirective
|
||||
|
|
@ -100,6 +103,7 @@ mut:
|
|||
optimize_str string
|
||||
verbose_str string
|
||||
parallel_str string
|
||||
static_link_str string
|
||||
include_dirs []string
|
||||
lib_search_paths []string
|
||||
libraries []string
|
||||
|
|
@ -134,6 +138,7 @@ mut:
|
|||
debug_str string
|
||||
optimize_str string
|
||||
verbose_str string
|
||||
static_link_str string
|
||||
}
|
||||
|
||||
struct RawDependencyConfig {
|
||||
|
|
@ -259,6 +264,7 @@ fn normalize_raw_config(raw RawBuildConfig, mut warnings []string) BuildConfig {
|
|||
cfg.optimize = resolve_bool(cfg.optimize, raw.global.optimize_str, 'global', 'optimize', mut warnings)
|
||||
cfg.verbose = resolve_bool(cfg.verbose, raw.global.verbose_str, 'global', 'verbose', mut warnings)
|
||||
cfg.parallel_compilation = resolve_bool(cfg.parallel_compilation, raw.global.parallel_str, 'global', 'parallel_compilation', mut warnings)
|
||||
cfg.static_link = resolve_bool(cfg.static_link, raw.global.static_link_str, 'global', 'static_link', mut warnings)
|
||||
|
||||
cfg.include_dirs = inherit_list(raw.global.include_dirs, cfg.include_dirs)
|
||||
cfg.lib_search_paths = inherit_list(raw.global.lib_search_paths, cfg.lib_search_paths)
|
||||
|
|
@ -294,6 +300,7 @@ fn normalize_raw_config(raw RawBuildConfig, mut warnings []string) BuildConfig {
|
|||
debug: resolve_bool(cfg.debug, raw_tool.debug_str, scope, 'debug', mut warnings)
|
||||
optimize: resolve_bool(cfg.optimize, raw_tool.optimize_str, scope, 'optimize', mut warnings)
|
||||
verbose: resolve_bool(cfg.verbose, raw_tool.verbose_str, scope, 'verbose', mut warnings)
|
||||
static_link: resolve_bool(cfg.static_link, raw_tool.static_link_str, scope, 'static_link', mut warnings)
|
||||
}
|
||||
tool.include_dirs = inherit_list(raw_tool.include_dirs, cfg.include_dirs)
|
||||
tool.cflags = inherit_list(raw_tool.cflags, cfg.cflags)
|
||||
|
|
@ -370,6 +377,8 @@ pub fn (mut build_config BuildConfig) parse_build_directives() ! {
|
|||
mut file_cflags := []string{}
|
||||
mut file_ldflags := []string{}
|
||||
mut is_shared := false
|
||||
mut static_override := false
|
||||
mut has_static_override := false
|
||||
|
||||
for line1 in lines {
|
||||
line := line1.trim_space()
|
||||
|
|
@ -420,6 +429,10 @@ pub fn (mut build_config BuildConfig) parse_build_directives() ! {
|
|||
'shared' {
|
||||
is_shared = directive_value == 'true'
|
||||
}
|
||||
'static' {
|
||||
static_override = directive_value == 'true'
|
||||
has_static_override = true
|
||||
}
|
||||
else {
|
||||
if build_config.verbose {
|
||||
println('Warning: Unknown build directive: ${directive_type} in ${src_file}')
|
||||
|
|
@ -437,6 +450,7 @@ pub fn (mut build_config BuildConfig) parse_build_directives() ! {
|
|||
cflags: file_cflags
|
||||
ldflags: file_ldflags
|
||||
is_shared: is_shared
|
||||
static_link: if has_static_override { ?bool(static_override) } else { none }
|
||||
}
|
||||
|
||||
if build_config.verbose {
|
||||
|
|
@ -464,6 +478,7 @@ pub fn parse_args() !BuildConfig {
|
|||
'-O', '--optimize' { build_config.optimize = true; build_config.debug = false }
|
||||
'-v', '--verbose' { build_config.verbose = true }
|
||||
'-p', '--parallel' { build_config.parallel_compilation = true }
|
||||
'-s', '--static' { build_config.static_link = true }
|
||||
'-o', '--output' {
|
||||
if i + 1 < os.args.len {
|
||||
build_config.project_name = os.args[i + 1]
|
||||
|
|
@ -533,6 +548,7 @@ pub fn parse_args() !BuildConfig {
|
|||
debug: build_config.debug
|
||||
optimize: build_config.optimize
|
||||
verbose: build_config.verbose
|
||||
static_link: build_config.static_link
|
||||
}
|
||||
build_config.tools << tool_config
|
||||
i += 2
|
||||
|
|
@ -551,6 +567,7 @@ pub fn parse_args() !BuildConfig {
|
|||
debug: build_config.debug
|
||||
optimize: build_config.optimize
|
||||
verbose: build_config.verbose
|
||||
static_link: build_config.static_link
|
||||
}
|
||||
build_config.tools << default_tool
|
||||
}
|
||||
|
|
@ -571,6 +588,7 @@ pub fn parse_args() !BuildConfig {
|
|||
debug: build_config.debug
|
||||
optimize: build_config.optimize
|
||||
verbose: build_config.verbose
|
||||
static_link: build_config.static_link
|
||||
}
|
||||
build_config.tools << default_tool
|
||||
}
|
||||
|
|
@ -657,6 +675,7 @@ pub fn parse_config_file(filename string) !BuildConfig {
|
|||
'cflags' { raw.global.cflags << parse_space_list(value) }
|
||||
'ldflags' { raw.global.ldflags << parse_space_list(value) }
|
||||
'dependencies_dir' { raw.global.dependencies_dir = value }
|
||||
'static_link' { raw.global.static_link_str = value }
|
||||
else { warnings << 'Unknown global config key: ${key}' }
|
||||
}
|
||||
}
|
||||
|
|
@ -697,6 +716,7 @@ pub fn parse_config_file(filename string) !BuildConfig {
|
|||
'optimize' { tool.optimize_str = value }
|
||||
'verbose' { tool.verbose_str = value }
|
||||
'output_dir' { tool.output_dir = value }
|
||||
'static_link' { tool.static_link_str = value }
|
||||
else { warnings << 'Unknown tools key: ${key}' }
|
||||
}
|
||||
}
|
||||
|
|
@ -935,6 +955,11 @@ fn common_tool_link_command(compiler string, object_files []string, executable s
|
|||
}
|
||||
mut cmd := '${binary}'
|
||||
|
||||
// Add static linking flags if enabled
|
||||
if tool_config.static_link {
|
||||
cmd += ' -static'
|
||||
}
|
||||
|
||||
cmd += ' -L${build_config.bin_dir}/lib'
|
||||
for lib_path in build_config.lib_search_paths {
|
||||
cmd += ' -L${lib_path}'
|
||||
|
|
@ -954,7 +979,19 @@ fn common_tool_link_command(compiler string, object_files []string, executable s
|
|||
}
|
||||
}
|
||||
|
||||
for library in tool_config.libraries {
|
||||
// For static linking, reverse library order so dependencies come after dependents
|
||||
// (static linker resolves symbols left-to-right, so A depending on B needs: -lA -lB)
|
||||
libs := if tool_config.static_link {
|
||||
mut reversed := []string{}
|
||||
for i := tool_config.libraries.len - 1; i >= 0; i-- {
|
||||
reversed << tool_config.libraries[i]
|
||||
}
|
||||
reversed
|
||||
} else {
|
||||
tool_config.libraries
|
||||
}
|
||||
|
||||
for library in libs {
|
||||
if library != '' {
|
||||
mut libfile := library
|
||||
if libfile.starts_with('lib/') {
|
||||
|
|
@ -964,9 +1001,14 @@ fn common_tool_link_command(compiler string, object_files []string, executable s
|
|||
if libfile.ends_with('.so') {
|
||||
libfile = libfile.replace('.so', '')
|
||||
}
|
||||
// Use static library (.a) when static linking, otherwise shared (.so)
|
||||
if tool_config.static_link {
|
||||
cmd += ' -l:${libfile}.a'
|
||||
} else {
|
||||
cmd += ' -l:${libfile}.so'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for flag in build_config.ldflags {
|
||||
cmd += ' ${flag}'
|
||||
|
|
@ -975,6 +1017,11 @@ fn common_tool_link_command(compiler string, object_files []string, executable s
|
|||
cmd += ' ${flag}'
|
||||
}
|
||||
|
||||
// Add static runtime flags for fully static binary
|
||||
if tool_config.static_link {
|
||||
cmd += ' -static-libgcc -static-libstdc++'
|
||||
}
|
||||
|
||||
cmd += ' -o ${executable}'
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Global Options:
|
|||
-O, --optimize Enable optimization (-O3, disables debug).
|
||||
-v, --verbose Verbose logging (graph + compiler commands).
|
||||
-p, --parallel Force parallel compilation worker pool.
|
||||
-s, --static Link tools statically (self-contained binaries).
|
||||
-o, --output <name> Override project/output name.
|
||||
-I <dir> Add include directory (repeatable).
|
||||
-L <dir> Add library search path (repeatable).
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ debug = true
|
|||
optimize = false
|
||||
verbose = false
|
||||
parallel_compilation = true
|
||||
static_link = false
|
||||
include_dirs = include
|
||||
lib_search_paths =
|
||||
cflags = -Wall -Wextra
|
||||
|
|
@ -22,3 +23,4 @@ dependencies_dir = dependencies
|
|||
|
||||
[tools]
|
||||
# legacy/manual entries go here when you don't want build directives
|
||||
# Per-tool static_link can override global: static_link = true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
module tests
|
||||
|
||||
import config
|
||||
import builder
|
||||
|
||||
fn test_static_link_defaults_to_false() {
|
||||
cfg := config.default_config
|
||||
|
||||
assert cfg.static_link == false
|
||||
}
|
||||
|
||||
fn test_tool_config_static_link_defaults_to_false() {
|
||||
tool := config.ToolConfig{
|
||||
name: 'test'
|
||||
}
|
||||
|
||||
assert tool.static_link == false
|
||||
}
|
||||
|
||||
fn test_static_link_command_includes_static_flag() {
|
||||
cfg := config.BuildConfig{
|
||||
compiler: 'g++'
|
||||
toolchain: 'gcc'
|
||||
bin_dir: 'bin'
|
||||
}
|
||||
tc := config.get_toolchain(cfg)
|
||||
|
||||
tool_cfg := config.ToolConfig{
|
||||
name: 'mytool'
|
||||
static_link: true
|
||||
}
|
||||
|
||||
cmd := tc.tool_link_command(['main.o'], 'bin/tools/mytool', &cfg, tool_cfg)
|
||||
|
||||
assert cmd.contains('-static')
|
||||
assert cmd.contains('-static-libgcc')
|
||||
assert cmd.contains('-static-libstdc++')
|
||||
}
|
||||
|
||||
fn test_static_link_command_uses_static_libraries() {
|
||||
cfg := config.BuildConfig{
|
||||
compiler: 'g++'
|
||||
toolchain: 'gcc'
|
||||
bin_dir: 'bin'
|
||||
}
|
||||
tc := config.get_toolchain(cfg)
|
||||
|
||||
tool_cfg := config.ToolConfig{
|
||||
name: 'mytool'
|
||||
libraries: ['core']
|
||||
static_link: true
|
||||
}
|
||||
|
||||
cmd := tc.tool_link_command(['main.o'], 'bin/tools/mytool', &cfg, tool_cfg)
|
||||
|
||||
assert cmd.contains('-l:core.a')
|
||||
assert !cmd.contains('-l:core.so')
|
||||
}
|
||||
|
||||
fn test_dynamic_link_command_uses_shared_libraries() {
|
||||
cfg := config.BuildConfig{
|
||||
compiler: 'g++'
|
||||
toolchain: 'gcc'
|
||||
bin_dir: 'bin'
|
||||
}
|
||||
tc := config.get_toolchain(cfg)
|
||||
|
||||
tool_cfg := config.ToolConfig{
|
||||
name: 'mytool'
|
||||
libraries: ['core']
|
||||
static_link: false
|
||||
}
|
||||
|
||||
cmd := tc.tool_link_command(['main.o'], 'bin/tools/mytool', &cfg, tool_cfg)
|
||||
|
||||
assert cmd.contains('-l:core.so')
|
||||
assert !cmd.contains('-l:core.a')
|
||||
assert !cmd.contains('-static')
|
||||
}
|
||||
|
||||
fn test_clang_static_link_command() {
|
||||
cfg := config.BuildConfig{
|
||||
compiler: 'clang++'
|
||||
toolchain: 'clang'
|
||||
bin_dir: 'bin'
|
||||
}
|
||||
tc := config.get_toolchain(cfg)
|
||||
|
||||
tool_cfg := config.ToolConfig{
|
||||
name: 'mytool'
|
||||
static_link: true
|
||||
}
|
||||
|
||||
cmd := tc.tool_link_command(['main.o'], 'bin/tools/mytool', &cfg, tool_cfg)
|
||||
|
||||
assert cmd.starts_with('clang++')
|
||||
assert cmd.contains('-static')
|
||||
}
|
||||
|
||||
fn test_any_tool_needs_static_link_returns_false_when_no_tools() {
|
||||
cfg := config.BuildConfig{
|
||||
tools: []
|
||||
}
|
||||
|
||||
assert builder.any_tool_needs_static_link(cfg) == false
|
||||
}
|
||||
|
||||
fn test_any_tool_needs_static_link_returns_false_when_all_dynamic() {
|
||||
cfg := config.BuildConfig{
|
||||
tools: [
|
||||
config.ToolConfig{ name: 'tool1', static_link: false },
|
||||
config.ToolConfig{ name: 'tool2', static_link: false }
|
||||
]
|
||||
}
|
||||
|
||||
assert builder.any_tool_needs_static_link(cfg) == false
|
||||
}
|
||||
|
||||
fn test_any_tool_needs_static_link_returns_true_when_one_static() {
|
||||
cfg := config.BuildConfig{
|
||||
tools: [
|
||||
config.ToolConfig{ name: 'tool1', static_link: false },
|
||||
config.ToolConfig{ name: 'tool2', static_link: true },
|
||||
config.ToolConfig{ name: 'tool3', static_link: false }
|
||||
]
|
||||
}
|
||||
|
||||
assert builder.any_tool_needs_static_link(cfg) == true
|
||||
}
|
||||
|
||||
fn test_any_tool_needs_static_link_returns_true_when_all_static() {
|
||||
cfg := config.BuildConfig{
|
||||
tools: [
|
||||
config.ToolConfig{ name: 'tool1', static_link: true },
|
||||
config.ToolConfig{ name: 'tool2', static_link: true }
|
||||
]
|
||||
}
|
||||
|
||||
assert builder.any_tool_needs_static_link(cfg) == true
|
||||
}
|
||||
Loading…
Reference in New Issue