v2 / vlib / v / cflag / cflags.v
243 lines · 222 sloc · 6.17 KB · bb90b74e2c14f64595e388496a1781ddf925a74c
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module cflag
5
6import os
7import strings
8
9// parsed cflag
10pub struct CFlag {
11pub:
12 mod string // the module in which the flag was given
13 os string // eg. windows | darwin | linux
14 name string // eg. -I
15 value string // eg. /path/to/include
16pub mut:
17 cached string // eg. ~/.vmodules/cache/ea/ea9878886727367672163.o (for .o files)
18}
19
20pub fn (c &CFlag) str() string {
21 return 'CFlag{ name: "${c.name}" value: "${c.value}" mod: "${c.mod}" os: "${c.os}" cached: "${c.cached}" }'
22}
23
24const fexisting_literal = r'$first_existing'
25const wexisting_literal = r'$when_first_existing'
26
27fn find_first_existing_path(remainder string, literal string) (bool, string, int, []string) {
28 sparams := remainder[literal.len + 1..].all_before(')')
29 delta_i := sparams.len + literal.len + 1
30 svalues := sparams.replace(',', '\n').split_into_lines().map(it.trim('\t \'"'))
31 for spath in svalues {
32 if os.exists(spath) {
33 return true, spath, delta_i, []string{}
34 }
35 }
36 return false, '', delta_i, svalues
37}
38
39// expand the flag value
40pub fn (cf &CFlag) eval() ?string {
41 mut value_builder := strings.new_builder(10 * cf.value.len)
42 cflag_eval_outer_loop: for i := 0; i < cf.value.len; i++ {
43 x := cf.value[i]
44 if x == `$` {
45 remainder := cf.value[i..]
46 if remainder.starts_with(fexisting_literal) {
47 found, spath, delta_i, svalues := find_first_existing_path(remainder,
48 fexisting_literal)
49 if found {
50 value_builder.write_string(spath)
51 i += delta_i
52 continue
53 }
54 panic('>> error: none of the paths ${svalues} exist')
55 }
56 if remainder.starts_with(wexisting_literal) {
57 found, spath, delta_i, _ := find_first_existing_path(remainder, wexisting_literal)
58 if found {
59 value_builder.write_string(spath)
60 i += delta_i
61 continue
62 }
63 return none
64 }
65 }
66 value_builder.write_string(x.ascii_str())
67 }
68 return value_builder.str()
69}
70
71// format flag
72pub fn (cf &CFlag) format() ?string {
73 mut value := ''
74 if cf.cached != '' {
75 value = cf.cached
76 } else {
77 value = cf.eval()?
78 }
79 if cf.name in ['-l', '-Wa', '-Wl', '-Wp'] && value != '' {
80 return '${cf.name}${value}'.trim_space()
81 }
82 // convert to absolute path
83 if cf.name == '-I' || cf.name == '-L' || value.ends_with('.o') {
84 value = '"' + os.real_path(value) + '"'
85 if cf.name in ['-I', '-L'] {
86 return '${cf.name}${value}'.trim_space()
87 }
88 }
89 return '${cf.name} ${value}'.trim_space()
90}
91
92// TODO: implement msvc specific c_options_before_target and c_options_after_target ...
93pub fn (cflags []CFlag) c_options_before_target_msvc() []string {
94 return []
95}
96
97pub fn (cflags []CFlag) c_options_after_target_msvc() []string {
98 return []
99}
100
101pub fn (cflags []CFlag) c_options_before_target() []string {
102 defines, others, _ := cflags.defines_others_libs()
103 mut args := []string{cap: defines.len + others.len}
104 args << defines
105 args << others
106 return uniq_non_empty(args)
107}
108
109pub fn (cflags []CFlag) c_options_after_target() []string {
110 _, _, libs := cflags.defines_others_libs()
111 return libs
112}
113
114pub fn (cflags []CFlag) c_options_without_object_files() []string {
115 mut args := []string{}
116 for flag in cflags {
117 if flag.value.ends_with('.o') || flag.value.ends_with('.obj') {
118 continue
119 }
120 args << flag.format() or { continue }
121 }
122 return uniq_non_empty(args)
123}
124
125pub fn (cflags []CFlag) c_options_only_object_files() []string {
126 mut args := []string{}
127 for flag in cflags {
128 // TODO figure out a better way to copy cross compiling flags to the linker
129 if flag.value.ends_with('.o') || flag.value.ends_with('.obj')
130 || (flag.name == '-l' && flag.value == 'pq') {
131 args << flag.format() or { continue }
132 }
133 }
134 return uniq_non_empty(args)
135}
136
137pub fn (cflags []CFlag) defines_others_libs() ([]string, []string, []string) {
138 copts_without_obj_files := cflags.c_options_without_object_files()
139 mut defines := []string{}
140 mut others := []string{}
141 mut libs := []string{}
142 for copt in copts_without_obj_files {
143 if copt.ends_with('@START_LIBS') {
144 libs.insert(0, copt.all_before('@START_LIBS'))
145 continue
146 }
147 if copt.starts_with('-l') {
148 libs << copt
149 continue
150 }
151 if copt.ends_with('.a') || copt.ends_with('.so') || copt.ends_with('.dylib')
152 || copt.ends_with('.dll') || copt.ends_with('.lib') {
153 windows_import_libs := split_bare_windows_import_libs(copt)
154 if windows_import_libs.len > 0 {
155 libs << windows_import_libs.map(windows_import_lib_to_link_flag(it))
156 continue
157 }
158 if is_bare_windows_import_lib(copt) {
159 libs << windows_import_lib_to_link_flag(copt)
160 continue
161 }
162 libs << '"${copt}"'
163 continue
164 }
165
166 if copt.ends_with('@START_DEFINES') {
167 defines.insert(0, copt.all_before('@START_DEFINES'))
168 continue
169 }
170 if copt.starts_with('-D') {
171 defines << copt
172 continue
173 }
174
175 if copt.ends_with('@START_OTHERS') {
176 others.insert(0, copt.all_before('@START_OTHERS'))
177 continue
178 }
179 others << copt
180 }
181 return uniq_non_empty(defines), uniq_non_empty(others), uniq_non_empty(libs)
182}
183
184fn split_bare_windows_import_libs(value string) []string {
185 parts := split_quoted_flags(value)
186 if parts.len < 2 {
187 return []string{}
188 }
189 for part in parts {
190 if !is_bare_windows_import_lib(part) {
191 return []string{}
192 }
193 }
194 return parts
195}
196
197fn is_bare_windows_import_lib(value string) bool {
198 lib := value.trim_space()
199 return lib.len > '.lib'.len && lib.to_lower().ends_with('.lib') && !lib.contains('/')
200 && !lib.contains('\\') && !lib.contains(':') && !lib.contains(' ') && !lib.contains('\t')
201}
202
203fn windows_import_lib_to_link_flag(value string) string {
204 lib := value.trim_space()
205 return '-l${lib[..lib.len - '.lib'.len]}'
206}
207
208fn split_quoted_flags(value string) []string {
209 mut parts := []string{}
210 mut buf := []u8{}
211 mut in_quote := false
212 for ch in value {
213 if ch == `"` {
214 in_quote = !in_quote
215 continue
216 }
217 if !in_quote && ch in [` `, `\t`] {
218 if buf.len > 0 {
219 parts << buf.bytestr()
220 buf = []u8{}
221 }
222 continue
223 }
224 buf << ch
225 }
226 if buf.len > 0 {
227 parts << buf.bytestr()
228 }
229 return parts
230}
231
232fn uniq_non_empty(args []string) []string {
233 mut uniq_args := []string{}
234 for a in args {
235 if a == '' {
236 continue
237 }
238 if a !in uniq_args {
239 uniq_args << a
240 }
241 }
242 return uniq_args
243}
244