From bcfc16a34d40d3cf03fb2331c789cac8aca88353 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 26 Feb 2026 06:53:34 +0300 Subject: [PATCH] all: add OS-specific headers support for #include directives (#26654) (fixes #26562) --- vlib/v/ast/ast.v | 3 +- vlib/v/checker/checker.v | 5 +++ vlib/v/gen/c/cgen.v | 13 ++++++++ .../os_specific_hash_stmts.c.must_have | 12 +++++++ .../gen/c/testdata/os_specific_hash_stmts.vv | 10 ++++++ vlib/v/gen/native/comptime.v | 5 +++ vlib/v/parser/comptime.v | 33 ++++++++++++++----- vlib/v2/ast/ast.v | 5 +-- vlib/v2/ast_dump/ast_dump.v | 6 ++++ vlib/v2/gen/cleanc/stmt.v | 3 +- vlib/v2/gen/v/gen.v | 4 +++ vlib/v2/parser/parser.v | 15 +++++++-- 12 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 vlib/v/gen/c/testdata/os_specific_hash_stmts.c.must_have create mode 100644 vlib/v/gen/c/testdata/os_specific_hash_stmts.vv diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index fc6221ef6..a7f1217ab 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1504,7 +1504,8 @@ pub mut: msg string // : 'please install openssl' ct_conds []Expr // *all* comptime conditions, that must be true, for the hash to be processed // ct_conds is filled by the checker, based on the current nesting of `$if cond1 {}` blocks - attrs []Attr + ct_low_level_cond string // optional low-level comptime condition e.g. 'linux', 'darwin' for `#include linux ` + attrs []Attr } // variable assign statement diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 4ff9db583..d03f10584 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2950,6 +2950,11 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { if c.ct_cond_stack.len > 0 { node.ct_conds = c.ct_cond_stack.clone() } + if node.ct_low_level_cond.len > 0 + && node.ct_low_level_cond !in ast.valid_comptime_not_user_defined { + c.error('invalid OS/platform condition `${node.ct_low_level_cond}` in #${node.kind}', + node.pos) + } if c.pref.backend == .golang || c.is_js_backend { // consider the best way to handle the .go.vv files if !c.file.path.ends_with('.js.v') && !c.file.path.ends_with('.go.v') diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index ae1a8af2d..4594ce638 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -6214,6 +6214,16 @@ fn (mut g Gen) gen_hash_stmts(mut sb strings.Builder, node &ast.HashStmtNode, se if !need_gen_stmt { return } + // OS-specific condition e.g. `#include linux ` wraps the output with #if / #endif + has_low_level_cond := node.ct_low_level_cond.len > 0 + if has_low_level_cond { + ifdef := ast.comptime_if_to_ifdef(node.ct_low_level_cond, g.pref) or { + g.error('#${node.kind} has invalid condition `${node.ct_low_level_cond}`', + node.pos) + return + } + sb.writeln('\n#if defined(${ifdef})') + } line_nr := node.pos.line_nr + 1 match node.kind { 'include', 'preinclude', 'postinclude' { @@ -6233,6 +6243,9 @@ fn (mut g Gen) gen_hash_stmts(mut sb strings.Builder, node &ast.HashStmtNode, se } else {} } + if has_low_level_cond { + sb.writeln('#endif') + } } // TODO: support $match } diff --git a/vlib/v/gen/c/testdata/os_specific_hash_stmts.c.must_have b/vlib/v/gen/c/testdata/os_specific_hash_stmts.c.must_have new file mode 100644 index 000000000..d14331ef0 --- /dev/null +++ b/vlib/v/gen/c/testdata/os_specific_hash_stmts.c.must_have @@ -0,0 +1,12 @@ +#if defined(__linux__) +#include +#endif +#if defined(__DARWIN__) +#include +#endif +#if defined(_WIN32) +#include "win_header.h" +#endif +#if defined(__FreeBSD__) +#include +#endif diff --git a/vlib/v/gen/c/testdata/os_specific_hash_stmts.vv b/vlib/v/gen/c/testdata/os_specific_hash_stmts.vv new file mode 100644 index 000000000..bab478d30 --- /dev/null +++ b/vlib/v/gen/c/testdata/os_specific_hash_stmts.vv @@ -0,0 +1,10 @@ +// vtest vflags: -os cross +module main + +#include linux +#include darwin +#include windows "win_header.h" +#include freebsd + +fn main() { +} diff --git a/vlib/v/gen/native/comptime.v b/vlib/v/gen/native/comptime.v index 54b09cf3d..2e99b2599 100644 --- a/vlib/v/gen/native/comptime.v +++ b/vlib/v/gen/native/comptime.v @@ -15,6 +15,11 @@ fn (mut g Gen) comptime_conditional(node ast.IfExpr) ?ast.IfBranch { } fn (mut g Gen) should_emit_hash_stmt(node ast.HashStmt) bool { + if node.ct_low_level_cond.len > 0 { + if !g.comptime_ident(node.ct_low_level_cond, false) { + return false + } + } return node.ct_conds.all(g.comptime_is_truthy(it)) } diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index dc1be7dd9..bd63fe871 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -93,6 +93,7 @@ fn (mut p Parser) hash() ast.HashStmt { p.next() mut main_str := '' mut msg := '' + mut ct_low_level_cond := '' content := val.all_after('${kind} ').all_before('//') if content.contains(' #') { main_str = content.all_before(' #').trim_space() @@ -102,6 +103,19 @@ fn (mut p Parser) hash() ast.HashStmt { msg = '' } + // Detect OS-specific conditions like `#include linux ` or `#include darwin "util.h"`. + // The condition is the first word before the actual include path. + if kind in ['include', 'preinclude', 'postinclude', 'insert'] { + first_space := main_str.index_u8(` `) + if first_space > 0 { + maybe_cond := main_str[..first_space] + if maybe_cond in ast.valid_comptime_not_user_defined { + ct_low_level_cond = maybe_cond + main_str = main_str[first_space + 1..].trim_space() + } + } + } + mut is_use_once := false for fna in attrs { match fna.name { @@ -111,15 +125,16 @@ fn (mut p Parser) hash() ast.HashStmt { } return ast.HashStmt{ - mod: p.mod - source_file: p.file_path - val: val - kind: kind - main: main_str - msg: msg - pos: pos - attrs: attrs - is_use_once: is_use_once + mod: p.mod + source_file: p.file_path + val: val + kind: kind + main: main_str + msg: msg + pos: pos + attrs: attrs + is_use_once: is_use_once + ct_low_level_cond: ct_low_level_cond } } diff --git a/vlib/v2/ast/ast.v b/vlib/v2/ast/ast.v index 6b0c60d02..4952c633c 100644 --- a/vlib/v2/ast/ast.v +++ b/vlib/v2/ast/ast.v @@ -810,8 +810,9 @@ pub: // #flag / #include pub struct Directive { pub: - name string - value string + name string + value string + ct_cond string // optional comptime condition e.g. 'linux', 'darwin' for `#include linux ` } pub struct EnumDecl { diff --git a/vlib/v2/ast_dump/ast_dump.v b/vlib/v2/ast_dump/ast_dump.v index 9cb5621b2..84717e566 100644 --- a/vlib/v2/ast_dump/ast_dump.v +++ b/vlib/v2/ast_dump/ast_dump.v @@ -404,6 +404,12 @@ fn (mut jb JsonBuilder) write_directive(stmt ast.Directive) { jb.write_indent() jb.sb.write_string('"value": ') jb.write_string(stmt.value) + if stmt.ct_cond.len > 0 { + jb.sb.write_string(',\n') + jb.write_indent() + jb.sb.write_string('"ct_cond": ') + jb.write_string(stmt.ct_cond) + } jb.sb.write_string('\n') jb.indent-- diff --git a/vlib/v2/gen/cleanc/stmt.v b/vlib/v2/gen/cleanc/stmt.v index 8fae7d1f4..4a1041054 100644 --- a/vlib/v2/gen/cleanc/stmt.v +++ b/vlib/v2/gen/cleanc/stmt.v @@ -287,7 +287,8 @@ fn (mut g Gen) gen_stmt(node ast.Stmt) { } ast.Directive { g.write_indent() - g.sb.writeln('/* [TODO] Directive: #${node.name} ${node.value} */') + ct_cond_str := if node.ct_cond.len > 0 { ' ct_cond=${node.ct_cond}' } else { '' } + g.sb.writeln('/* [TODO] Directive: #${node.name} ${node.value}${ct_cond_str} */') } ast.ForInStmt { panic('bug in v2 compiler: ForInStmt should have been lowered in v2.transformer') diff --git a/vlib/v2/gen/v/gen.v b/vlib/v2/gen/v/gen.v index e4b56fb4e..cd5eb2ec8 100644 --- a/vlib/v2/gen/v/gen.v +++ b/vlib/v2/gen/v/gen.v @@ -125,6 +125,10 @@ fn (mut g Gen) stmt(stmt ast.Stmt) { g.write('#') g.write(stmt.name) g.write(' ') + if stmt.ct_cond.len > 0 { + g.write(stmt.ct_cond) + g.write(' ') + } g.writeln(stmt.value) } ast.EmptyStmt {} diff --git a/vlib/v2/parser/parser.v b/vlib/v2/parser/parser.v index cf350fe9a..45b14ecec 100644 --- a/vlib/v2/parser/parser.v +++ b/vlib/v2/parser/parser.v @@ -1862,6 +1862,16 @@ fn (mut p Parser) directive() ast.Directive { // TODO: handle properly // NOTE: these line checks will be removed once this is parsed properly // and only needed since auto semi is not inserted after `>` + // Detect OS-specific conditions like `#include linux ` or `#include darwin "util.h"`. + // When the directive is an include variant and the next token is a name followed by '<' or '"', + // the name is an OS/platform condition. + mut ct_cond := '' + if name in ['include', 'preinclude', 'postinclude', 'insert'] && p.tok == .name { + next := p.peek() + if next == .lt || next == .string { + ct_cond = p.lit() + } + } mut value := p.lit() for p.line == line { if p.tok == .name { @@ -1873,8 +1883,9 @@ fn (mut p Parser) directive() ast.Directive { } // p.next() return ast.Directive{ - name: name - value: value + name: name + value: value + ct_cond: ct_cond } } -- 2.39.5