| 1 | import os |
| 2 | import rand |
| 3 | |
| 4 | const vexe = @VEXE |
| 5 | |
| 6 | fn test_shared_library_links_with_system_cc() { |
| 7 | if os.user_os() != 'linux' { |
| 8 | return |
| 9 | } |
| 10 | workdir := os.join_path(os.vtmp_dir(), 'v_shared_link_${rand.ulid()}') |
| 11 | os.mkdir_all(workdir) or { panic(err) } |
| 12 | defer { |
| 13 | os.rmdir_all(workdir) or {} |
| 14 | } |
| 15 | lib_src := os.join_path(workdir, 'libfoo.v') |
| 16 | lib_out := os.join_path(workdir, 'libfoo') |
| 17 | lib_so := '${lib_out}.so' |
| 18 | host_src := os.join_path(workdir, 'host.c') |
| 19 | host_bin := os.join_path(workdir, 'host') |
| 20 | os.write_file(lib_src, [ |
| 21 | 'module libfoo', |
| 22 | '', |
| 23 | "@[export: 'libfoo_square']", |
| 24 | 'pub fn square(x int) int {', |
| 25 | '\treturn x * x', |
| 26 | '}', |
| 27 | ].join('\n')) or { panic(err) } |
| 28 | os.write_file(host_src, [ |
| 29 | '#include <stdio.h>', |
| 30 | '', |
| 31 | 'int libfoo_square(int);', |
| 32 | '', |
| 33 | 'int main(void) {', |
| 34 | '\tprintf("%d\\n", libfoo_square(2));', |
| 35 | '\treturn 0;', |
| 36 | '}', |
| 37 | ].join('\n')) or { panic(err) } |
| 38 | run_cmd('${os.quoted_path(vexe)} -shared -o ${os.quoted_path(lib_out)} ${os.quoted_path(lib_src)}') or { |
| 39 | panic(err) |
| 40 | } |
| 41 | assert os.exists(lib_so) |
| 42 | run_cmd('cc ${os.quoted_path(host_src)} -L${os.quoted_path(workdir)} -lfoo -o ${os.quoted_path(host_bin)}') or { |
| 43 | panic(err) |
| 44 | } |
| 45 | res := run_cmd('LD_LIBRARY_PATH=${os.quoted_path(workdir)} ${os.quoted_path(host_bin)}') or { |
| 46 | panic(err) |
| 47 | } |
| 48 | assert res.output.trim_space() == '4' |
| 49 | } |
| 50 | |
| 51 | fn test_shared_library_only_exports_tagged_symbols() { |
| 52 | // Regression test for vlang/v#27167: `v -shared` used to export every |
| 53 | // V runtime/stdlib symbol, not just functions tagged with `@[export: '…']`. |
| 54 | uos := os.user_os() |
| 55 | if uos !in ['linux', 'macos'] { |
| 56 | return |
| 57 | } |
| 58 | // tcc does not honor `-fvisibility=hidden`, so the symbol-set check would be |
| 59 | // noisy there; use a system compiler for Linux and clang on macOS. |
| 60 | cc_flag := if uos == 'macos' { '-cc clang' } else { '-cc cc' } |
| 61 | workdir := os.join_path(os.vtmp_dir(), 'v_shared_visibility_${rand.ulid()}') |
| 62 | os.mkdir_all(workdir) or { panic(err) } |
| 63 | defer { |
| 64 | os.rmdir_all(workdir) or {} |
| 65 | } |
| 66 | lib_src := os.join_path(workdir, 'lib.v') |
| 67 | lib_out := os.join_path(workdir, 'liblib') |
| 68 | lib_so := if uos == 'macos' { '${lib_out}.dylib' } else { '${lib_out}.so' } |
| 69 | os.write_file(lib_src, [ |
| 70 | 'module main', |
| 71 | '', |
| 72 | "@[export: 'my_add']", |
| 73 | 'fn my_add(a int, b int) int {', |
| 74 | '\treturn a + b', |
| 75 | '}', |
| 76 | ].join('\n')) or { panic(err) } |
| 77 | run_cmd('${os.quoted_path(vexe)} ${cc_flag} -shared -o ${os.quoted_path(lib_out)} ${os.quoted_path(lib_src)}') or { |
| 78 | panic(err) |
| 79 | } |
| 80 | assert os.exists(lib_so) |
| 81 | nm_flag := if uos == 'macos' { '-gU' } else { '-D --defined-only' } |
| 82 | res := run_cmd('nm ${nm_flag} ${os.quoted_path(lib_so)}') or { panic(err) } |
| 83 | symbol_token := if uos == 'macos' { ' _my_add' } else { ' my_add' } |
| 84 | assert res.output.contains(symbol_token), 'expected exported `my_add` in:\n${res.output}' |
| 85 | // None of the V runtime/stdlib helpers should leak into the exported ABI. |
| 86 | leaky_prefixes := ['Array_string_', 'main__', 'builtin__', 'GC_', '_vinit', '_vcleanup', |
| 87 | '_const_'] |
| 88 | for line in res.output.split('\n') { |
| 89 | for prefix in leaky_prefixes { |
| 90 | needles := if uos == 'macos' { [' _${prefix}', ' T _${prefix}', ' D _${prefix}'] } else { [ |
| 91 | ' ${prefix}', |
| 92 | ' T ${prefix}', |
| 93 | ' D ${prefix}', |
| 94 | ] } |
| 95 | for needle in needles { |
| 96 | assert !line.contains(needle), 'unexpected exported symbol on line: ${line}' |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | fn run_cmd(cmd string) !os.Result { |
| 103 | res := os.execute(cmd) |
| 104 | if res.exit_code != 0 { |
| 105 | return error('command failed:\n${cmd}\n${res.output}') |
| 106 | } |
| 107 | return res |
| 108 | } |
| 109 | |