cmd/tools: add check_os_api_parity.v - keep module APIs even

pull/5351/head
Delyan Angelov 2020-06-11 21:02:27 +03:00
parent 05177b9dcb
commit 5ef9569098
9 changed files with 148 additions and 10 deletions

View File

@ -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
}
}

View File

@ -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 ')

View File

@ -210,6 +210,7 @@ pub mut:
pub struct FnDecl {
pub:
name string
mod string
stmts []Stmt
args []table.Arg
is_deprecated bool

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.