cmd/tools: add check_os_api_parity.v - keep module APIs even
parent
05177b9dcb
commit
5ef9569098
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -163,7 +163,6 @@ fn (foptions &FormatOptions) format_file(file string) {
|
||||||
fn print_compiler_options(compiler_params &pref.Preferences) {
|
fn print_compiler_options(compiler_params &pref.Preferences) {
|
||||||
eprintln(' os: ' + compiler_params.os.str())
|
eprintln(' os: ' + compiler_params.os.str())
|
||||||
eprintln(' ccompiler: $compiler_params.ccompiler')
|
eprintln(' ccompiler: $compiler_params.ccompiler')
|
||||||
eprintln(' mod: $compiler_params.mod ')
|
|
||||||
eprintln(' path: $compiler_params.path ')
|
eprintln(' path: $compiler_params.path ')
|
||||||
eprintln(' out_name: $compiler_params.out_name ')
|
eprintln(' out_name: $compiler_params.out_name ')
|
||||||
eprintln(' vroot: $compiler_params.vroot ')
|
eprintln(' vroot: $compiler_params.vroot ')
|
||||||
|
|
|
@ -210,6 +210,7 @@ pub mut:
|
||||||
pub struct FnDecl {
|
pub struct FnDecl {
|
||||||
pub:
|
pub:
|
||||||
name string
|
name string
|
||||||
|
mod string
|
||||||
stmts []Stmt
|
stmts []Stmt
|
||||||
args []table.Arg
|
args []table.Arg
|
||||||
is_deprecated bool
|
is_deprecated bool
|
||||||
|
|
|
@ -6,6 +6,21 @@ module ast
|
||||||
import v.table
|
import v.table
|
||||||
import strings
|
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.
|
// These methods are used only by vfmt, vdoc, and for debugging.
|
||||||
|
|
||||||
pub fn (node &FnDecl) str(t &table.Table) string {
|
pub fn (node &FnDecl) str(t &table.Table) string {
|
||||||
|
|
|
@ -4382,13 +4382,6 @@ fn (mut g Gen) panic_debug_info(pos token.Position) (int, string, string, string
|
||||||
paline := pos.line_nr + 1
|
paline := pos.line_nr + 1
|
||||||
pafile := g.fn_decl.file.replace('\\', '/')
|
pafile := g.fn_decl.file.replace('\\', '/')
|
||||||
pafn := g.fn_decl.name.after('.')
|
pafn := g.fn_decl.name.after('.')
|
||||||
mut pamod := g.fn_decl.name.all_before_last('.')
|
pamod := g.fn_decl.modname()
|
||||||
if pamod == pafn {
|
|
||||||
pamod = if g.fn_decl.is_builtin {
|
|
||||||
'builtin'
|
|
||||||
} else {
|
|
||||||
'main'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return paline, pafile, pamod, pafn
|
return paline, pafile, pamod, pafn
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,6 +277,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
p.attr_ctdefine = ''
|
p.attr_ctdefine = ''
|
||||||
return ast.FnDecl{
|
return ast.FnDecl{
|
||||||
name: name
|
name: name
|
||||||
|
mod: p.mod
|
||||||
stmts: stmts
|
stmts: stmts
|
||||||
return_type: return_type
|
return_type: return_type
|
||||||
args: args
|
args: args
|
||||||
|
@ -339,6 +340,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
|
||||||
return ast.AnonFn{
|
return ast.AnonFn{
|
||||||
decl: ast.FnDecl{
|
decl: ast.FnDecl{
|
||||||
name: name
|
name: name
|
||||||
|
mod: p.mod
|
||||||
stmts: stmts
|
stmts: stmts
|
||||||
return_type: return_type
|
return_type: return_type
|
||||||
args: args
|
args: args
|
||||||
|
|
|
@ -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) {
|
if p.pref.is_script && !p.pref.is_test && p.mod == 'main' && !have_fn_main(stmts) {
|
||||||
stmts << ast.FnDecl{
|
stmts << ast.FnDecl{
|
||||||
name: 'main'
|
name: 'main'
|
||||||
|
mod: p.mod
|
||||||
file: p.file_name
|
file: p.file_name
|
||||||
return_type: table.void_type
|
return_type: table.void_type
|
||||||
}
|
}
|
||||||
|
@ -458,6 +459,7 @@ pub fn (mut p Parser) top_stmt() ast.Stmt {
|
||||||
}
|
}
|
||||||
return ast.FnDecl{
|
return ast.FnDecl{
|
||||||
name: 'main'
|
name: 'main'
|
||||||
|
mod: p.mod
|
||||||
stmts: stmts
|
stmts: stmts
|
||||||
file: p.file_name
|
file: p.file_name
|
||||||
return_type: table.void_type
|
return_type: table.void_type
|
||||||
|
|
|
@ -319,6 +319,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
|
||||||
args << args2
|
args << args2
|
||||||
mut method := ast.FnDecl{
|
mut method := ast.FnDecl{
|
||||||
name: name
|
name: name
|
||||||
|
mod: p.mod
|
||||||
args: args
|
args: args
|
||||||
file: p.file_name
|
file: p.file_name
|
||||||
return_type: table.void_type
|
return_type: table.void_type
|
||||||
|
|
|
@ -103,7 +103,6 @@ pub mut:
|
||||||
compile_defines []string // just ['vfmt']
|
compile_defines []string // just ['vfmt']
|
||||||
compile_defines_all []string // contains both: ['vfmt','another']
|
compile_defines_all []string // contains both: ['vfmt','another']
|
||||||
|
|
||||||
mod string
|
|
||||||
run_args []string // `v run x.v 1 2 3` => `1 2 3`
|
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
|
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.
|
print_v_files bool // when true, just print the list of all parsed .v files then stop.
|
||||||
|
|
Loading…
Reference in New Issue