diff --git a/cmd/tools/check_os_api_parity.v b/cmd/tools/check_os_api_parity.v new file mode 100644 index 0000000000..f9e1a591c7 --- /dev/null +++ b/cmd/tools/check_os_api_parity.v @@ -0,0 +1,126 @@ +module main + +import os +import v.util +import v.pref +import v.builder +import v.ast +import term + +const ( + base_os = 'linux' + os_names = ['linux', 'macos', 'windows'] + skip_modules = [ + 'builtin.bare', 'builtin.js', + 'strconv', 'strconv.ftoa', 'hash.wyhash', 'strings', + 'crypto.rand', + 'os.bare', 'os2', + 'picohttpparser', 'picoev', + 'szip', + 'v.eval' + ] +) + +struct App { + diff_cmd string + is_verbose bool + modules []string +mut: + api_differences map[string]int +} + +fn main() { + vexe := pref.vexe_path() + vroot := os.dir(vexe) + util.set_vroot_folder(vroot) + os.chdir(vroot) + cmd := util.find_working_diff_command() or { + '' + } + mut app := App{ + diff_cmd: cmd + is_verbose: os.getenv('VERBOSE').len > 0 + modules: if os.args.len > 1 { os.args[1..] } else { all_vlib_modules() } + } + for mname in app.modules { + if !app.is_verbose { + eprintln('Checking module: ${mname} ...') + } + api_base := app.gen_api_for_module_in_os(mname, base_os) + for oname in os_names { + if oname == base_os { + continue + } + api_os := app.gen_api_for_module_in_os(mname, oname) + app.compare_api(api_base, api_os, mname, base_os, oname) + } + } + howmany := app.api_differences.size + eprintln('NB: please, do run `git clean -xf` after this tool, or at least `find thirdparty/ |grep .o$|xargs rm`') + eprintln('otherwise, `./v test-fixed` may show false positives, due to .o files compiled with a cross compiler.') + if howmany > 0 { + eprintln(term.header('Found $howmany modules with different APIs', '=')) + for m in app.api_differences.keys() { + eprintln('Module: $m') + } + exit(1) + } +} + +fn all_vlib_modules() []string { + mut vlib_v_files := os.walk_ext('vlib', '.v') + mut vmodulesmap := map[string]int{} + for f in vlib_v_files { + if f.contains('/tests/') || f.ends_with('_test.v') { + continue + } + vmodulename := os.dir(f).replace('/', '.').replace('vlib.', '') + if vmodulename in skip_modules { + continue + } + vmodulesmap[vmodulename] = vmodulesmap[vmodulename] + 1 + } + mut modules := vmodulesmap.keys() + modules.sort() + return modules +} + +fn (app App) gen_api_for_module_in_os(mod_name, os_name string) string { + if app.is_verbose { + eprintln('Checking module: ${mod_name:-30} for OS: ${os_name:-10} ...') + } + mpath := os.join_path('vlib', mod_name.replace('.', '/')) + tmpname := '/tmp/${mod_name}_${os_name}.c' + prefs, _ := pref.parse_args(['-os', os_name, '-o', tmpname, '-shared', mpath]) + mut b := builder.new_builder(prefs) + b.compile_c() + mut res := []string{} + for f in b.parsed_files { + for s in f.stmts { + if s is ast.FnDecl { + fnd := s as ast.FnDecl + if fnd.is_pub { + fn_signature := fnd.str(b.table) + fn_mod := fnd.modname() + if fn_mod == mod_name { + fline := '${fn_mod}: $fn_signature' + res << fline + } + } + } + } + } + res.sort() + return res.join('\n') +} + +fn (mut app App) compare_api(api_base, api_os, mod_name, os_base, os_target string) { + res := util.color_compare_strings(app.diff_cmd, api_base, api_os) + if res.len > 0 { + summary := 'Different APIs found for module: `${mod_name}`, between OS base: `${os_base}` and OS: `${os_target}`' + eprintln(term.header(summary, '-')) + eprintln(res) + eprintln(term.h_divider('-')) + app.api_differences[mod_name] = 1 + } +} diff --git a/cmd/tools/vfmt.v b/cmd/tools/vfmt.v index e7514027ea..9dce73c726 100644 --- a/cmd/tools/vfmt.v +++ b/cmd/tools/vfmt.v @@ -163,7 +163,6 @@ fn (foptions &FormatOptions) format_file(file string) { fn print_compiler_options(compiler_params &pref.Preferences) { eprintln(' os: ' + compiler_params.os.str()) eprintln(' ccompiler: $compiler_params.ccompiler') - eprintln(' mod: $compiler_params.mod ') eprintln(' path: $compiler_params.path ') eprintln(' out_name: $compiler_params.out_name ') eprintln(' vroot: $compiler_params.vroot ') diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 84414f970d..8ae1bc4966 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -210,6 +210,7 @@ pub mut: pub struct FnDecl { pub: name string + mod string stmts []Stmt args []table.Arg is_deprecated bool diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 8aa4760672..85b5c14d93 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -6,6 +6,21 @@ module ast import v.table import strings +pub fn (node &FnDecl) modname() string { + if node.mod != '' { + return node.mod + } + mut pamod := node.name.all_before_last('.') + if pamod == node.name.after('.') { + pamod = if node.is_builtin { + 'builtin' + } else { + 'main' + } + } + return pamod +} + // These methods are used only by vfmt, vdoc, and for debugging. pub fn (node &FnDecl) str(t &table.Table) string { diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 5843677c3d..0514ba7acf 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -4382,13 +4382,6 @@ fn (mut g Gen) panic_debug_info(pos token.Position) (int, string, string, string paline := pos.line_nr + 1 pafile := g.fn_decl.file.replace('\\', '/') pafn := g.fn_decl.name.after('.') - mut pamod := g.fn_decl.name.all_before_last('.') - if pamod == pafn { - pamod = if g.fn_decl.is_builtin { - 'builtin' - } else { - 'main' - } - } + pamod := g.fn_decl.modname() return paline, pafile, pamod, pafn } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 5423a042f8..e4d3d7c97c 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -277,6 +277,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { p.attr_ctdefine = '' return ast.FnDecl{ name: name + mod: p.mod stmts: stmts return_type: return_type args: args @@ -339,6 +340,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn { return ast.AnonFn{ decl: ast.FnDecl{ name: name + mod: p.mod stmts: stmts return_type: return_type args: args diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 059839a084..9a00845982 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -144,6 +144,7 @@ fn (mut p Parser) parse() ast.File { if p.pref.is_script && !p.pref.is_test && p.mod == 'main' && !have_fn_main(stmts) { stmts << ast.FnDecl{ name: 'main' + mod: p.mod file: p.file_name return_type: table.void_type } @@ -458,6 +459,7 @@ pub fn (mut p Parser) top_stmt() ast.Stmt { } return ast.FnDecl{ name: 'main' + mod: p.mod stmts: stmts file: p.file_name return_type: table.void_type diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 0b9510a47d..9b73099426 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -319,6 +319,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { args << args2 mut method := ast.FnDecl{ name: name + mod: p.mod args: args file: p.file_name return_type: table.void_type diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 2e1140c4db..6ec958fb7d 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -103,7 +103,6 @@ pub mut: compile_defines []string // just ['vfmt'] compile_defines_all []string // contains both: ['vfmt','another'] - mod string run_args []string // `v run x.v 1 2 3` => `1 2 3` printfn_list []string // a list of generated function names, whose source should be shown, for debugging print_v_files bool // when true, just print the list of all parsed .v files then stop.