| 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. |
| 4 | module cflag |
| 5 | |
| 6 | import os |
| 7 | import strings |
| 8 | |
| 9 | // parsed cflag |
| 10 | pub struct CFlag { |
| 11 | pub: |
| 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 |
| 16 | pub mut: |
| 17 | cached string // eg. ~/.vmodules/cache/ea/ea9878886727367672163.o (for .o files) |
| 18 | } |
| 19 | |
| 20 | pub 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 | |
| 24 | const fexisting_literal = r'$first_existing' |
| 25 | const wexisting_literal = r'$when_first_existing' |
| 26 | |
| 27 | fn 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 |
| 40 | pub 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 |
| 72 | pub 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 ... |
| 93 | pub fn (cflags []CFlag) c_options_before_target_msvc() []string { |
| 94 | return [] |
| 95 | } |
| 96 | |
| 97 | pub fn (cflags []CFlag) c_options_after_target_msvc() []string { |
| 98 | return [] |
| 99 | } |
| 100 | |
| 101 | pub 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 | |
| 109 | pub fn (cflags []CFlag) c_options_after_target() []string { |
| 110 | _, _, libs := cflags.defines_others_libs() |
| 111 | return libs |
| 112 | } |
| 113 | |
| 114 | pub 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 | |
| 125 | pub 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 | |
| 137 | pub 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 | |
| 184 | fn 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 | |
| 197 | fn 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 | |
| 203 | fn windows_import_lib_to_link_flag(value string) string { |
| 204 | lib := value.trim_space() |
| 205 | return '-l${lib[..lib.len - '.lib'.len]}' |
| 206 | } |
| 207 | |
| 208 | fn 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 | |
| 232 | fn 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 | |