From c1cc203c17bff4aeeb664e14d2c89251184e512e Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 8 Jan 2020 22:45:47 +0200 Subject: [PATCH] tools: bugfixes and new features for oldv and performance_compare --- .gitignore | 3 + tools/modules/scripting/scripting.v | 67 ++++-- tools/modules/vgit/vgit.v | 174 ++++++++++++++ tools/oldv.v | 209 ++++++----------- tools/performance_compare.v | 351 +++++++++++----------------- tools/vtest-fmt.v | 13 +- vlib/flag/flag.v | 32 ++- vlib/flag/flag_test.v | 21 ++ 8 files changed, 489 insertions(+), 381 deletions(-) create mode 100644 tools/modules/vgit/vgit.v diff --git a/.gitignore b/.gitignore index 0785fdbc97..c9ae96556d 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ vlib/os/bare/bare_example_linux info.log .vscode/** + +# vim/emacs editor backup files +*~ diff --git a/tools/modules/scripting/scripting.v b/tools/modules/scripting/scripting.v index 51e953b93b..18f744123c 100644 --- a/tools/modules/scripting/scripting.v +++ b/tools/modules/scripting/scripting.v @@ -2,17 +2,27 @@ module scripting import os -pub fn verbose_trace(label string, message string){ +pub fn set_verbose(on bool) { + // setting a global here would be the obvious solution, + // but V does not have globals normally. + if on { + os.setenv('VERBOSE', '1', true) + } + else { + os.unsetenv('VERBOSE') + } +} + +pub fn verbose_trace(label string, message string) { if os.getenv('VERBOSE').len > 0 { slabel := 'scripting.${label}' - println('# ${slabel:30s} : $message') + println('# ${slabel:-25s} : $message') } } pub fn verbose_trace_exec_result(x os.Result) { if os.getenv('VERBOSE').len > 0 { - println('# cmd.exit_code : ${x.exit_code.str()}') - println('# cmd.output :') + println('# cmd.exit_code : ${x.exit_code.str():-4s} cmd.output:') println('# ----------------------------------- #') mut lnum := 1 lines := x.output.split_into_lines() @@ -21,33 +31,45 @@ pub fn verbose_trace_exec_result(x os.Result) { lnum++ } println('# ----------------------------------- #') - } + } } pub fn chdir(path string) { verbose_trace(@FN, 'cd $path') - os.chdir( path ) + os.chdir(path) } pub fn run(cmd string) string { verbose_trace(@FN, cmd) - x := os.exec(cmd) or { return '' } - verbose_trace_exec_result( x ) - if x.exit_code == 0 { return x.output } + x := os.exec(cmd) or { + verbose_trace(@FN, '## failed.') + return '' + } + verbose_trace_exec_result(x) + if x.exit_code == 0 { + return x.output + } return '' } -pub fn command_exits_with_zero_status(cmd string) bool { +pub fn exit_0_status(cmd string) bool { verbose_trace(@FN, cmd) - x := os.exec(cmd) or { return false } - verbose_trace_exec_result( x ) - if x.exit_code == 0 { return true } + x := os.exec(cmd) or { + verbose_trace(@FN, '## failed.') + return false + } + verbose_trace_exec_result(x) + if x.exit_code == 0 { + return true + } return false } -pub fn tool_must_exist(toolcmd string) { +pub fn tool_must_exist (toolcmd string) { verbose_trace(@FN, toolcmd) - if command_exits_with_zero_status( 'type $toolcmd' ) { return } + if exit_0_status('type $toolcmd') { + return + } eprintln('Missing tool: $toolcmd') eprintln('Please try again after you install it.') exit(1) @@ -59,14 +81,9 @@ pub fn used_tools_must_exist(tools []string) { } } -pub fn check_v_commit_timestamp_before_self_rebuilding(v_timestamp int) { - if v_timestamp >= 1561805697 { return } - eprintln('##################################################################') - eprintln('# WARNING: v self rebuilding, before 5b7a1e8 (2019-06-29 12:21) #') - eprintln('# required the v executable to be built *inside* #') - eprintln('# the toplevel compiler/ folder. #') - eprintln('# #') - eprintln('# That is not supported by this tool. #') - eprintln('# You will have to build it manually there. #') - eprintln('##################################################################') +pub fn show_sizes_of_files(files []string) { + for f in files { + size := os.file_size(f) + println('${size:10d} $f') + } } diff --git a/tools/modules/vgit/vgit.v b/tools/modules/vgit/vgit.v new file mode 100644 index 0000000000..25e847a94b --- /dev/null +++ b/tools/modules/vgit/vgit.v @@ -0,0 +1,174 @@ +module vgit + +import os +import flag +import filepath +import scripting + +const ( + remote_v_repo_url = 'https://github.com/vlang/v' + remote_vc_repo_url = 'https://github.com/vlang/vc' +) + +pub fn check_v_commit_timestamp_before_self_rebuilding(v_timestamp int) { + if v_timestamp >= 1561805697 { + return + } + eprintln('##################################################################') + eprintln('# WARNING: v self rebuilding, before 5b7a1e8 (2019-06-29 12:21) #') + eprintln('# required the v executable to be built *inside* #') + eprintln('# the toplevel compiler/ folder. #') + eprintln('# #') + eprintln('# That is not supported by this tool. #') + eprintln('# You will have to build it manually there. #') + eprintln('##################################################################') +} + +pub fn validate_commit_exists(commit string) { + if commit.len == 0 { + return + } + cmd := "git cat-file -t \'$commit\' " + if !scripting.exit_0_status(cmd) { + eprintln('Commit: "$commit" does not exist in the current repository.') + exit(3) + } +} + +pub fn line_to_timestamp_and_commit(line string) (int,string) { + parts := line.split(' ') + return parts[0].int(),parts[1] +} + +pub fn normalized_workpath_for_commit(workdir string, commit string) string { + nc := 'v_at_' + commit.replace('^', '_').replace('-', '_').replace('/', '_') + return os.realpath(workdir + os.path_separator + nc) +} + +pub fn prepare_vc_source(vcdir string, cdir string, commit string) (string,string) { + scripting.chdir(cdir) + // Building a historic v with the latest vc is not always possible ... + // It is more likely, that the vc *at the time of the v commit*, + // or slightly before that time will be able to build the historic v: + vline := scripting.run('git rev-list -n1 --timestamp "$commit" ') + v_timestamp,v_commithash := vgit.line_to_timestamp_and_commit(vline) + vgit.check_v_commit_timestamp_before_self_rebuilding(v_timestamp) + scripting.chdir(vcdir) + scripting.run('git checkout master') + vcbefore := scripting.run('git rev-list HEAD -n1 --timestamp --before=$v_timestamp ') + _,vccommit_before := vgit.line_to_timestamp_and_commit(vcbefore) + scripting.run('git checkout "$vccommit_before" ') + scripting.run('wc *.c') + scripting.chdir(cdir) + return v_commithash,vccommit_before +} + +pub fn clone_or_pull( remote_git_url string, local_worktree_path string ) { + // NB: after clone_or_pull, the current repo branch is === HEAD === master + if os.is_dir( local_worktree_path ) && os.is_dir(filepath.join(local_worktree_path,'.git')) { + // Already existing ... Just pulling in this case is faster usually. + scripting.run('git -C "$local_worktree_path" checkout --quiet master') + scripting.run('git -C "$local_worktree_path" pull --quiet ') + } else { + // Clone a fresh + scripting.run('git clone --quiet "$remote_git_url" "$local_worktree_path" ') + } +} + +// + +pub struct VGitContext { +pub: + cc string = 'cc' // what compiler to use + workdir string = '/tmp' // the base working folder + commit_v string = 'master' // the commit-ish that needs to be prepared + path_v string // where is the local working copy v repo + path_vc string // where is the local working copy vc repo + v_repo_url string // the remote v repo URL + vc_repo_url string // the remote vc repo URL +pub mut: + // these will be filled by vgitcontext.compile_oldv_if_needed() + commit_v__hash string // the git commit of the v repo that should be prepared + commit_vc_hash string // the git commit of the vc repo, corresponding to commit_v__hash + vexename string // v or v.exe + vexepath string // the full absolute path to the prepared v/v.exe + vvlocation string // v.v or compiler/ , depending on v version +} + +pub fn (vgit_context mut VGitContext) compile_oldv_if_needed() { + vgit_context.vexename = if os.user_os() == 'windows' { 'v.exe' } else { 'v' } + vgit_context.vexepath = os.realpath( filepath.join(vgit_context.path_v, vgit_context.vexename) ) + mut command_for_building_v_from_c_source := '' + mut command_for_selfbuilding := '' + if 'windows' == os.user_os() { + command_for_building_v_from_c_source = '$vgit_context.cc -std=c99 -municode -w -o cv.exe "$vgit_context.path_vc/v_win.c" ' + command_for_selfbuilding = './cv.exe -o $vgit_context.vexename {SOURCE}' + } + else { + command_for_building_v_from_c_source = '$vgit_context.cc -std=gnu11 -w -o cv "$vgit_context.path_vc/v.c" -lm' + command_for_selfbuilding = './cv -o $vgit_context.vexename {SOURCE}' + } + scripting.chdir(vgit_context.workdir) + clone_or_pull( vgit_context.v_repo_url, vgit_context.path_v ) + clone_or_pull( vgit_context.vc_repo_url, vgit_context.path_vc ) + + scripting.chdir(vgit_context.path_v) + scripting.run('git checkout $vgit_context.commit_v') + v_commithash,vccommit_before := vgit.prepare_vc_source(vgit_context.path_vc, vgit_context.path_v, vgit_context.commit_v) + vgit_context.commit_v__hash = v_commithash + vgit_context.commit_vc_hash = vccommit_before + vgit_context.vvlocation = if os.exists('v.v') { 'v.v' } else { 'compiler' } + if os.is_dir(vgit_context.path_v) && os.exists(vgit_context.vexepath) { + // already compiled, so no need to compile v again + return + } + // Recompilation is needed. Just to be sure, clean up everything first. + scripting.run('git clean -xf') + scripting.run(command_for_building_v_from_c_source) + build_cmd := command_for_selfbuilding.replace('{SOURCE}', vgit_context.vvlocation) + scripting.run(build_cmd) + + // At this point, there exists a file vgit_context.vexepath + // which should be a valid working V executable. +} + +pub fn add_common_tool_options(context mut T, fp mut flag.FlagParser) []string { + tdir := os.tmpdir() + context.workdir = os.realpath(fp.string_('workdir', `w`, tdir, 'A writable base folder. Default: $tdir')) + context.v_repo_url = fp.string('vrepo', vgit.remote_v_repo_url, 'The url of the V repository. You can clone it locally too. See also --vcrepo below.') + context.vc_repo_url = fp.string('vcrepo', vgit.remote_vc_repo_url, 'The url of the vc repository. You can clone it +${flag.SPACE}beforehand, and then just give the local folder +${flag.SPACE}path here. That will eliminate the network ops +${flag.SPACE}done by this tool, which is useful, if you want +${flag.SPACE}to script it/run it in a restrictive vps/docker. +') + context.show_help = fp.bool_('help', `h`, false, 'Show this help screen.') + context.verbose = fp.bool_('verbose', `v`, false, 'Be more verbose.') + + if (context.show_help) { + println(fp.usage()) + exit(0) + } + + if context.verbose { + scripting.set_verbose(true) + } + + if os.is_dir(context.v_repo_url) { + context.v_repo_url = os.realpath( context.v_repo_url ) + } + + if os.is_dir(context.vc_repo_url) { + context.vc_repo_url = os.realpath( context.vc_repo_url ) + } + + commits := fp.finalize() or { + eprintln('Error: ' + err) + exit(1) + } + for commit in commits { + vgit.validate_commit_exists(commit) + } + + return commits +} diff --git a/tools/oldv.v b/tools/oldv.v index cde2631f13..78a87634c5 100644 --- a/tools/oldv.v +++ b/tools/oldv.v @@ -3,169 +3,101 @@ import ( flag filepath scripting -) + vgit +) const ( - tool_version = '0.0.2' - tool_description = 'Checkout an old V and compile it. Useful when you want to discover when something broke.' - remote_repo_url_v = 'https://github.com/vlang/v' - remote_repo_url_vc = 'https://github.com/vlang/vc' + tool_version = '0.0.3' + tool_description = ' Checkout an old V and compile it as it was on specific commit. + This tool is useful, when you want to discover when something broke. + It is also useful, when you just want to experiment with an older historic V. + + The VCOMMIT argument can be a git commitish like HEAD or master and so on. + When oldv is used with git bisect, you probably want to give HEAD. For example: + git bisect start + git bisect bad + git checkout known_good_commit + git bisect good + ## Now git will automatically checkout a middle commit between the bad and the good + tools/oldv HEAD --command="run commands in oldv folder, to verify if the commit is good or bad" + ## See what the result is, and either do: ... + git bisect good + ## ... or do: + git bisect bad + ## Now you just repeat the above steps, each time running oldv with the same command, then mark the result as good or bad, + ## until you find the commit, where the problem first occured. + ## When you finish, do not forget to do: + git bisect reset' ) struct Context { mut: - repo_url_v string // the url of the V repository. It can be a local folder path, if you want to eliminate network operations... - repo_url_vc string // the url of the vc repository. It can be a local folder path, if you want to eliminate network operations... - workdir string // the working folder (typically /tmp), where the tool will write - commit_v string = 'master' // the commit from which you want to produce a working v compiler (this may be a commit-ish too) - commit_vc string = 'master' // this will be derived from commit_v - commit_v_hash string // this will be filled from the commit-ish commit_v using rev-list. It IS a commit hash. - path_v string // the full path to the v folder inside workdir. - path_vc string // the full path to the vc folder inside workdir. - cmd_to_run string // the command that you want to run *in* the oldv repo - cc string = 'cc' // the C compiler to use for bootstrapping. - cleanup bool // should the tool run a cleanup first - verbose bool // should the tool be much more verbose + v_repo_url string // the url of the V repository. It can be a local folder path, if you want to eliminate network operations... + vc_repo_url string // the url of the vc repository. It can be a local folder path, if you want to eliminate network operations... + workdir string // the working folder (typically /tmp), where the tool will write + commit_v string='master' // the commit from which you want to produce a working v compiler (this may be a commit-ish too) + commit_vc string='master' // this will be derived from commit_v + commit_v_hash string // this will be filled from the commit-ish commit_v using rev-list. It IS a commit hash. + path_v string // the full path to the v folder inside workdir. + path_vc string // the full path to the vc folder inside workdir. + cmd_to_run string // the command that you want to run *in* the oldv repo + cc string='cc' // the C compiler to use for bootstrapping. + cleanup bool // should the tool run a cleanup first + verbose bool // should the tool be much more verbose + show_help bool // whether to show the usage screen } -fn (c mut Context) compile_oldv_if_needed() { - vexename := if os.user_os() == 'windows' { 'v.exe' } else { 'v' } - vexepath := filepath.join( c.path_v, vexename ) - - mut command_for_building_v_from_c_source := '' - mut commands_for_selfbuilding := []string - if 'windows' == os.user_os() { - command_for_building_v_from_c_source = '$c.cc -w -o cv.exe "$c.path_vc/v_win.c" ' - commands_for_selfbuilding << './cv.exe -o v2.exe {SOURCE}' - commands_for_selfbuilding << './v2.exe -o $vexename {SOURCE}' - }else{ - command_for_building_v_from_c_source = '$c.cc -w -o cv "$c.path_vc/v.c" -lm' - commands_for_selfbuilding << './cv -o $vexename {SOURCE}' +fn (c mut Context) compile_oldv_if_needed() { + mut vgit_context := vgit.VGitContext{ + cc: c.cc + workdir: c.workdir + commit_v: c.commit_v + path_v: c.path_v + path_vc: c.path_vc + v_repo_url: c.v_repo_url + vc_repo_url: c.vc_repo_url } - - scripting.chdir( c.workdir ) - scripting.run('git clone --quiet "$c.repo_url_v" "$c.path_v" ') - scripting.run('git clone --quiet "$c.repo_url_vc" "$c.path_vc" ') - - scripting.chdir( c.path_v ) - scripting.run('git checkout $c.commit_v') - c.prepare_vc_source( c.commit_v ) - - if os.is_dir( c.path_v ) && os.exists( vexepath ) { return } - - scripting.run('git clean -f') - source_location := if os.exists('v.v') { 'v.v' } else { 'compiler' } - scripting.run( command_for_building_v_from_c_source ) - for cmd in commands_for_selfbuilding { - build_cmd := cmd.replace('{SOURCE}', source_location) - scripting.run( build_cmd ) - } - - if !os.exists( vexepath ) && c.cmd_to_run.len > 0 { + vgit_context.compile_oldv_if_needed() + c.commit_v_hash = vgit_context.commit_v__hash + if !os.exists(vgit_context.vexepath) && c.cmd_to_run.len > 0 { // NB: 125 is a special code, that git bisect understands as 'skip this commit'. // it is used to inform git bisect that the current commit leads to a build failure. - exit( 125 ) - } - -} - -fn line_to_timestamp_and_commit(line string) (int, string) { - parts := line.split(' ') - return parts[0].int(), parts[1] -} - -fn (c mut Context) prepare_vc_source( commit string ) { - scripting.chdir( c.path_v ) - // Building a historic v with the latest vc is not always possible ... - // It is more likely, that the vc *at the time of the v commit*, - // or slightly before that time will be able to build the historic v: - vline := scripting.run('git rev-list -n1 --timestamp "$commit" ') - v_timestamp, v_commithash := line_to_timestamp_and_commit( vline ) - c.commit_v_hash = v_commithash - scripting.check_v_commit_timestamp_before_self_rebuilding(v_timestamp) - scripting.chdir( c.path_vc ) - scripting.run('git checkout master') - vcbefore := scripting.run('git rev-list HEAD -n1 --timestamp --before=$v_timestamp ') - _, vccommit_before := line_to_timestamp_and_commit( vcbefore ) - c.commit_vc = vccommit_before - scripting.run('git checkout "$vccommit_before" ') - scripting.chdir( c.path_v ) -} - -fn (c Context) normalized_workpath_for_commit( commit string ) string { - nc := 'v_at_' + commit.replace('^','_').replace('-','_').replace('/','_') - return os.realpath( c.workdir + os.path_separator + nc ) -} - -fn validate_commit_exists( commit string ){ - cmd := 'git cat-file -t ' + "'" + commit + "'" - if !scripting.command_exits_with_zero_status(cmd) { - eprintln("Commit: '" + commit + "' does not exist in the current repository.") - exit(3) + exit(125) } } -fn main(){ - scripting.used_tools_must_exist(['git','cc']) +fn main() { + scripting.used_tools_must_exist(['git', 'cc']) mut context := Context{} mut fp := flag.new_flag_parser(os.args) fp.application(filepath.filename(os.executable())) - fp.version( tool_version ) - fp.description( tool_description ) + fp.version(tool_version) + fp.description(tool_description) fp.arguments_description('VCOMMIT') fp.skip_executable() + fp.limit_free_args(1, 1) - show_help:=fp.bool_('help', `h`, false, 'Show this help screen.') - context.verbose = fp.bool_('verbose', `v`, false, 'Be more verbose.\n') - - context.cleanup = fp.bool('clean', true, 'Clean before running (slower).') - context.cmd_to_run = fp.string_('command', `c`, '', 'Command to run in the old V repo.') - - context.workdir = os.realpath( fp.string_('work-dir', `w`, os.tmpdir(), 'A writable folder, where the comparison will be done.\n') ) - - context.repo_url_v = fp.string('v-repo', remote_repo_url_v, 'The url of the V repository. You can clone it locally too.\n') - - context.repo_url_vc = fp.string('vc-repo', remote_repo_url_vc, '' + - 'The url of the vc repository. You can clone it \n'+ - flag.SPACE+'beforehand, and then just give the local folder \n'+ - flag.SPACE+'path here. That will eliminate the network ops \n'+ - flag.SPACE+'done by this tool, which is useful, if you want \n'+ - flag.SPACE+'to script it/run it in a restrictive vps/docker.') - - if( show_help ){ - println( fp.usage() ) - exit(0) - } - - if context.verbose { - os.setenv('VERBOSE','true',true) - } - - commits := fp.finalize() or { - eprintln('Error: ' + err) - exit(1) - } + context.cleanup = fp.bool('clean', true, 'Clean before running (slower).') + context.cmd_to_run = fp.string_('command', `c`, '', 'Command to run in the old V repo.\n') + commits := vgit.add_common_tool_options(mut context, mut fp) if commits.len > 0 { context.commit_v = commits[0] - validate_commit_exists( context.commit_v ) - }else{ + } else { context.commit_v = scripting.run('git rev-list -n1 HEAD') } println('################# context.commit_v: $context.commit_v #####################') - - context.path_v = context.normalized_workpath_for_commit( context.commit_v ) - context.path_vc = context.normalized_workpath_for_commit( 'vc' ) - - if !os.is_dir( context.workdir ) { + context.path_v = vgit.normalized_workpath_for_commit(context.workdir, context.commit_v) + context.path_vc = vgit.normalized_workpath_for_commit(context.workdir, 'vc') + if !os.is_dir(context.workdir) { msg := 'Work folder: ' + context.workdir + ' , does not exist.' eprintln(msg) exit(2) } - ecc := os.getenv('CC') - if ecc!='' { context.cc = ecc } - + if ecc != '' { + context.cc = ecc + } if context.cleanup { scripting.run('rm -rf $context.path_v') scripting.run('rm -rf $context.path_vc') @@ -173,17 +105,16 @@ fn main(){ context.compile_oldv_if_needed() - scripting.chdir( context.path_v ) + scripting.chdir(context.path_v) println('# v commit hash: $context.commit_v_hash') println('# checkout folder: $context.path_v') - if context.cmd_to_run.len > 0 { - cmdres := os.exec( context.cmd_to_run ) or { panic(err) } - println('# command: $context.cmd_to_run') - println('# command exit code: $cmdres.exit_code') - println('# command result :') + cmdres := os.exec(context.cmd_to_run) or { + panic(err) + } + println('# command: ${context.cmd_to_run:-34s} exit code: ${cmdres.exit_code:-4d} result:') println(cmdres.output) - exit( cmdres.exit_code ) + exit(cmdres.exit_code) } } diff --git a/tools/performance_compare.v b/tools/performance_compare.v index 107f83a158..f567302e08 100644 --- a/tools/performance_compare.v +++ b/tools/performance_compare.v @@ -2,266 +2,199 @@ import ( os flag filepath + scripting + vgit ) const ( - tool_version = '0.0.4' - tool_description = '' + - ' Compares V executable size and performance,\n' + - ' between 2 commits from V\'s local git history.\n' + - ' When only one commit is given, it is compared to master.' + tool_version = '0.0.5' + tool_description = ' Compares V executable size and performance, + between 2 commits from V\'s local git history. + When only one commit is given, it is compared to master. + ' ) struct Context { - cwd string // current working folder + cwd string // current working folder mut: - vc_repo_url string // the url of the vc repository. It can be a local folder path, which is usefull to eliminate network operations... - workdir string // the working folder (typically /tmp), where the tool will write - a string // the full path to the 'after' folder inside workdir - b string // the full path to the 'before' folder inside workdir - vc string // the full path to the vc folder inside workdir. It is used during bootstrapping v from the C source. + v_repo_url string // the url of the vc repository. It can be a local folder path, which is usefull to eliminate network operations... + vc_repo_url string // the url of the vc repository. It can be a local folder path, which is usefull to eliminate network operations... + workdir string // the working folder (typically /tmp), where the tool will write + a string // the full path to the 'after' folder inside workdir + b string // the full path to the 'before' folder inside workdir + vc string // the full path to the vc folder inside workdir. It is used during bootstrapping v from the C source. commit_before string // the git commit for the 'before' state commit_after string // the git commit for the 'after' state - warmups int // how many times to execute a command before gathering stats - verbose bool // whether to print even more stuff + warmups int // how many times to execute a command before gathering stats + verbose bool // whether to print even more stuff + show_help bool // whether to show the usage screen hyperfineopts string // use for additional CLI options that will be given to the hyperfine command + vflags string // other v options to pass to compared v commands } + fn new_context() Context { - return Context{ cwd: os.getwd(), commit_after: 'master', warmups: 4 } -} - -////// The stuff in this block may be reusable for other v cli tools? ///////////////// -fn run(cmd string) string { - x := os.exec(cmd) or { return '' } - if x.exit_code == 0 { return x.output } - return '' -} - -fn command_exits_with_zero_status(cmd string) bool { - x := os.exec(cmd) or { return false } - if x.exit_code == 0 { return true } - return false -} - -fn tool_must_exist(toolcmd string) { - if command_exits_with_zero_status( 'type $toolcmd' ) { return } - eprintln('Missing tool: $toolcmd') - eprintln('Please try again after you install it.') - exit(1) -} - -fn used_tools_must_exist(tools []string) { - for t in tools { - tool_must_exist(t) + return Context{ + cwd: os.getwd() + commit_after: 'master' + warmups: 4 } } -////////////////////////////////////////////////////////////////////////// fn (c Context) compare_versions() { // Input is validated at this point... // Cleanup artifacts from previous runs of this tool: - os.chdir( c.workdir ) - run('rm -rf "$c.a" "$c.b" "$c.vc" ') + scripting.chdir(c.workdir) + scripting.run('rm -rf "$c.a" "$c.b" "$c.vc" ') // clone the VC source *just once per comparison*, and reuse it: - run('git clone --quiet \'$c.vc_repo_url\' \'$c.vc\' ') - + scripting.run('git clone --quiet "$c.vc_repo_url" "$c.vc" ') println('Comparing V performance of commit $c.commit_before (before) vs commit $c.commit_after (after) ...') - c.prepare_v( c.b , c.commit_before ) - c.prepare_v( c.a , c.commit_after ) + c.prepare_v(c.b, c.commit_before) + c.prepare_v(c.a, c.commit_after) + scripting.chdir(c.workdir) - os.chdir( c.workdir ) - //The first is the baseline, against which all the others will be compared. - //It is the fastest, since hello_world.v has only a single println in it, - c.compare_v_performance([ - 'vprod @DEBUG@ -o source.c examples/hello_world.v', - 'vprod -o source.c examples/hello_world.v', - 'vprod @DEBUG@ -o source.c @COMPILER@', - 'vprod -o source.c @COMPILER@', - 'vprod -o hello examples/hello_world.v', - 'vprod -o binary @COMPILER@', - ///////////////////////////////////////////////////////// - 'v @DEBUG@ -o source.c examples/hello_world.v', - 'v -o source.c examples/hello_world.v', - 'v @DEBUG@ -o source.c @COMPILER@', - 'v -o source.c @COMPILER@', - 'v -o hello examples/hello_world.v', - 'v -o binary @COMPILER@', - ]) + if c.vflags.len > 0 { + os.setenv('VFLAGS', c.vflags, true) + } + + // The first is the baseline, against which all the others will be compared. + // It is the fastest, since hello_world.v has only a single println in it, + mut perf_files := []string + perf_files << c.compare_v_performance('source_hello', [ + 'vprod @DEBUG@ -o source.c examples/hello_world.v', + 'vprod -o source.c examples/hello_world.v', + 'v @DEBUG@ -o source.c examples/hello_world.v', + 'v -o source.c examples/hello_world.v', + ]) + + perf_files << c.compare_v_performance('source_v', [ + 'vprod @DEBUG@ -o source.c @COMPILER@', + 'vprod -o source.c @COMPILER@', + 'v @DEBUG@ -o source.c @COMPILER@', + 'v -o source.c @COMPILER@', + ]) -} + perf_files << c.compare_v_performance('binary_hello', [ + 'vprod -o hello examples/hello_world.v', + 'v -o hello examples/hello_world.v', + ]) + + perf_files << c.compare_v_performance('binary_v', [ + 'vprod -o binary @COMPILER@', + 'v -o binary @COMPILER@', + ]) -fn show_sizes_of_files(files []string) { - for f in files { - size := os.file_size(f) - println('${size:10d} $f') + println('All performance files:') + for f in perf_files { + println(' $f') } } -fn line_to_timestamp_and_commit(line string) (int, string) { - parts := line.split(' ') - return parts[0].int(), parts[1] -} - -fn (c &Context) prepare_vc_source( cdir string, commit string ) { - os.chdir( cdir ) - // Building a historic v with the latest vc is not always possible ... - // It is more likely, that the vc *at the time of the v commit*, - // or slightly before that time will be able to build the historic v: - vline := run('git rev-list -n1 --timestamp \'$commit\' ') - v_timestamp, _ := line_to_timestamp_and_commit( vline ) - os.chdir( c.vc ) - run('git checkout master') - vcbefore := run('git rev-list HEAD -n1 --timestamp --before=$v_timestamp ') - _, vccommit_before := line_to_timestamp_and_commit( vcbefore ) - run('git checkout \'$vccommit_before\' ') - os.chdir( cdir ) -} - -fn (c &Context) prepare_v( cdir string, commit string ) { +fn (c &Context) prepare_v(cdir string, commit string) { mut cc := os.getenv('CC') - if cc == '' { cc = 'cc' } - - mut command_for_building_v_from_c_source := '$cc -std=gnu11 -w -o cv $c.vc/v.c -lm' - if 'windows' == os.user_os() { - command_for_building_v_from_c_source = '$cc -std=gnu11 -w -o cv.exe $c.vc/v_win.c' + if cc == '' { + cc = 'cc' + } + mut vgit_context := vgit.VGitContext{ + cc: cc + workdir: c.workdir + commit_v: commit + path_v: cdir + path_vc: c.vc + v_repo_url: c.v_repo_url + vc_repo_url: c.vc_repo_url + } + vgit_context.compile_oldv_if_needed() + scripting.chdir(cdir) + println('Making a v compiler in $cdir') + scripting.run('./v -cc ${cc} -o v $vgit_context.vvlocation') + println('Making a vprod compiler in $cdir') + scripting.run('./v -cc ${cc} -prod -o vprod $vgit_context.vvlocation') + println('Stripping and compressing cv v and vprod binaries in $cdir') + scripting.run('cp cv cv_stripped') + scripting.run('cp v v_stripped') + scripting.run('cp vprod vprod_stripped') + scripting.run('strip *_stripped') + scripting.run('cp cv_stripped cv_stripped_upxed') + scripting.run('cp v_stripped v_stripped_upxed') + scripting.run('cp vprod_stripped vprod_stripped_upxed') + scripting.run('upx -qqq --lzma cv_stripped_upxed') + scripting.run('upx -qqq --lzma v_stripped_upxed') + scripting.run('upx -qqq --lzma vprod_stripped_upxed') + scripting.show_sizes_of_files(['$cdir/cv', '$cdir/cv_stripped', '$cdir/cv_stripped_upxed']) + scripting.show_sizes_of_files(['$cdir/v', '$cdir/v_stripped', '$cdir/v_stripped_upxed']) + scripting.show_sizes_of_files(['$cdir/vprod', '$cdir/vprod_stripped', '$cdir/vprod_stripped_upxed']) + vversion := scripting.run('$cdir/v --version') + vcommit := scripting.run('git rev-parse --short --verify HEAD') + println('V version is: ${vversion} , local source commit: ${vcommit}') + if vgit_context.vvlocation == 'v.v' { + println('Source lines of the compiler: ' + scripting.run('wc v.v vlib/compiler/*.v | tail -n -1')) + }else{ + println('Source lines of the compiler: ' + scripting.run('wc compiler/*.v | tail -n -1')) } - - println('') - // prepare c.vc first - os.chdir( c.vc ) - run('git checkout master') - - println('Cloning current v source to $cdir ...') - os.system('git clone --quiet \'$c.cwd\' \'$cdir\' ') - os.chdir( cdir ) - os.system('git checkout --quiet \'$commit\' ') - - run('git clean -f') - c.prepare_vc_source( cdir, commit ) - source_location := if os.exists('v.v') { 'v.v' } else { 'compiler/' } - - println('Making v and vprod compilers in $cdir') - run(command_for_building_v_from_c_source) - run('./cv -o v $source_location') - run('./cv -prod -o vprod $source_location') - - run('cp cv cv_stripped') - run('cp v v_stripped') - run('cp vprod vprod_stripped') - run('strip *_stripped') - - run('cp cv_stripped cv_stripped_upxed') - run('cp v_stripped v_stripped_upxed') - run('cp vprod_stripped vprod_stripped_upxed') - run('upx -qqq --lzma cv_stripped_upxed') - run('upx -qqq --lzma v_stripped_upxed') - run('upx -qqq --lzma vprod_stripped_upxed') - - show_sizes_of_files(["$cdir/cv", "$cdir/cv_stripped", "$cdir/cv_stripped_upxed"]) - show_sizes_of_files(["$cdir/v", "$cdir/v_stripped", "$cdir/v_stripped_upxed"]) - show_sizes_of_files(["$cdir/vprod", "$cdir/vprod_stripped", "$cdir/vprod_stripped_upxed"]) - println("V version is: " + run("$cdir/v --version") + " , local source commit: " + run("git rev-parse --short --verify HEAD") ) - println('Source lines of the compiler: ' + run('wc v.v compiler/*.v vlib/compiler/*.v | tail -n -1') ) } -fn (c Context) compare_v_performance( commands []string ) { +fn (c Context) compare_v_performance(label string, commands []string) string { println('---------------------------------------------------------------------------------') - println('Compare v performance when doing the following commands:') - - source_location_a := if os.exists('$c.a/v.v') { 'v.v' } else { 'compiler/' } - source_location_b := if os.exists('$c.b/v.v') { 'v.v' } else { 'compiler/' } - timestamp_a, _ := line_to_timestamp_and_commit(run('cd $c.a/ ; git rev-list -n1 --timestamp HEAD')) - timestamp_b, _ := line_to_timestamp_and_commit(run('cd $c.b/ ; git rev-list -n1 --timestamp HEAD')) - debug_option_a := if timestamp_a > 1570877641 { '-g' } else { '-debug' } - debug_option_b := if timestamp_b > 1570877641 { '-g' } else { '-debug' } - + println('Compare v performance when doing the following commands ($label):') + source_location_a := if os.exists('$c.a/v.v') { 'v.v ' } else { 'compiler/ ' } + source_location_b := if os.exists('$c.b/v.v') { 'v.v ' } else { 'compiler/ ' } + timestamp_a,_ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.a/ ; git rev-list -n1 --timestamp HEAD')) + timestamp_b,_ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.b/ ; git rev-list -n1 --timestamp HEAD')) + debug_option_a := if timestamp_a > 1570877641 { '-g ' } else { '-debug ' } + debug_option_b := if timestamp_b > 1570877641 { '-g ' } else { '-debug ' } mut hyperfine_commands_arguments := []string - for cmd in commands { println(cmd) } for cmd in commands { - hyperfine_commands_arguments << ' \'cd ${c.b:30s} ; ./$cmd \' '.replace_each(['@COMPILER@', source_location_b, '@DEBUG@', debug_option_b]) + println(cmd) } for cmd in commands { - hyperfine_commands_arguments << ' \'cd ${c.a:30s} ; ./$cmd \' '.replace_each(['@COMPILER@', source_location_a, '@DEBUG@', debug_option_a]) + hyperfine_commands_arguments << " \'cd ${c.b:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_b, '@DEBUG@', debug_option_b]) } - /////////////////////////////////////////////////////////////////////////////// - cmd_stats_file := os.realpath([ c.workdir, 'v_performance_stats.json'].join(os.path_separator)) - comparison_cmd := 'hyperfine $c.hyperfineopts '+ - '--export-json ${cmd_stats_file} '+ - '--time-unit millisecond '+ - '--style full --warmup $c.warmups ' + - hyperfine_commands_arguments.join(' ') - /////////////////////////////////////////////////////////////////////////////// - if c.verbose { println( comparison_cmd ) } - os.system( comparison_cmd ) + for cmd in commands { + hyperfine_commands_arguments << " \'cd ${c.a:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_a, '@DEBUG@', debug_option_a]) + } + // ///////////////////////////////////////////////////////////////////////////// + cmd_stats_file := os.realpath([c.workdir, 'v_performance_stats_${label}.json'].join(os.path_separator)) + comparison_cmd := 'hyperfine $c.hyperfineopts ' + '--export-json ${cmd_stats_file} ' + '--time-unit millisecond ' + '--style full --warmup $c.warmups ' + hyperfine_commands_arguments.join(' ') + // ///////////////////////////////////////////////////////////////////////////// + if c.verbose { + println(comparison_cmd) + } + os.system(comparison_cmd) println('The detailed performance comparison report was saved to: $cmd_stats_file .') println('') + return cmd_stats_file } -fn (c Context) normalized_workpath_for_commit( commit string ) string { - nc := 'v_at_' + commit.replace('^','_').replace('-','_').replace('/','_') - return os.realpath( c.workdir + os.path_separator + nc ) -} - -fn validate_commit_exists( commit string ){ - cmd := 'git cat-file -t ' + "'" + commit + "'" - if !command_exits_with_zero_status(cmd) { - eprintln("Commit: '" + commit + "' does not exist in the current repository.") - exit(3) - } -} - -fn main(){ - used_tools_must_exist(['cp','rm','strip','make','git','upx','cc','wc','tail','hyperfine']) +fn main() { + scripting.used_tools_must_exist(['cp', 'rm', 'strip', 'make', 'git', 'upx', 'cc', 'wc', 'tail', 'hyperfine']) mut context := new_context() mut fp := flag.new_flag_parser(os.args) fp.application(filepath.filename(os.executable())) - fp.version( tool_version ) - fp.description( tool_description ) + fp.version(tool_version) + fp.description(tool_description) fp.arguments_description('COMMIT_BEFORE [COMMIT_AFTER]') fp.skip_executable() - fp.limit_free_args(1,2) - show_help:=fp.bool('help', false, 'Show this help screen\n') + fp.limit_free_args(1, 2) - context.vc_repo_url = fp.string('vcrepo', 'https://github.com/vlang/vc', - '' + - 'The url of the vc repository. You can clone it \n'+ - flag.SPACE+'beforehand, and then just give the local folder \n'+ - flag.SPACE+'path here. That will eliminate the network ops \n'+ - flag.SPACE+'done by this tool, which is useful, if you want \n'+ - flag.SPACE+'to script it/run it in a restrictive vps/docker.\n') - - context.verbose = fp.bool('verbose', false, 'Be more verbose\n') + context.vflags = fp.string('vflags', '', 'Additional options to pass to the v commands, for example "-cc tcc"') context.hyperfineopts = fp.string('hyperfine_options', '', - '' + - 'Additional options passed to hyperfine.\n'+ - flag.SPACE+'For example on linux, you may want to pass:\n'+ - flag.SPACE+' --hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'" \n') - - context.workdir = os.realpath( fp.string('workdir', '/tmp', 'A writable folder, where the comparison will be done.') ) - if( show_help ){ - println( fp.usage() ) - exit(0) - } - commits := fp.finalize() or { - eprintln('Error: ' + err) - exit(1) - } - + 'Additional options passed to hyperfine. +${flag.SPACE}For example on linux, you may want to pass: +${flag.SPACE}--hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'" +') + commits := vgit.add_common_tool_options(mut context, mut fp) context.commit_before = commits[0] - if commits.len > 1 { context.commit_after = commits[1] } - - validate_commit_exists( context.commit_before ) - validate_commit_exists( context.commit_after ) - - context.b = context.normalized_workpath_for_commit( context.commit_before ) - context.a = context.normalized_workpath_for_commit( context.commit_after ) - context.vc = context.normalized_workpath_for_commit( 'vc' ) - - if !os.is_dir( context.workdir ) { + if commits.len > 1 { + context.commit_after = commits[1] + } + context.b = vgit.normalized_workpath_for_commit(context.workdir, context.commit_before) + context.a = vgit.normalized_workpath_for_commit(context.workdir, context.commit_after) + context.vc = vgit.normalized_workpath_for_commit(context.workdir, 'vc') + if !os.is_dir(context.workdir) { msg := 'Work folder: ' + context.workdir + ' , does not exist.' eprintln(msg) exit(2) } - + context.compare_versions() } diff --git a/tools/vtest-fmt.v b/tools/vtest-fmt.v index 939f85ddac..086d8579be 100644 --- a/tools/vtest-fmt.v +++ b/tools/vtest-fmt.v @@ -8,6 +8,13 @@ import ( const ( known_failing_exceptions = ['./examples/vweb/vweb_example.v', './tools/gen_vc.v', + './tools/modules/vgit/vgit.v', // generics + './tools/preludes/live_main.v', + './tools/preludes/live_shared.v', + './tools/preludes/tests_assertions.v', + './tools/preludes/tests_with_stats.v', + './tools/performance_compare.v', // generics + './tools/oldv.v', // generics './tutorials/code/blog/article.v', './tutorials/code/blog/blog.v', './vlib/arrays/arrays.v', @@ -15,10 +22,6 @@ const ( './vlib/builtin/js/hashmap.v', './vlib/compiler/tests/fn_variadic_test.v', './vlib/compiler/tests/generic_test.v', - './tools/preludes/live_main.v', - './tools/preludes/live_shared.v', - './tools/preludes/tests_assertions.v', - './tools/preludes/tests_with_stats.v', './vlib/crypto/aes/aes.v', './vlib/crypto/aes/aes_cbc.v', './vlib/crypto/aes/block_generic.v', @@ -28,7 +31,7 @@ const ( './vlib/eventbus/eventbus_test.v', './vlib/os/bare/bare_example_linux.v', './vlib/szip/szip.v', - './vlib/ui/examples/users_gui/users.v', + './vlib/uiold/examples/users_gui/users.v', './vlib/vweb/assets/assets.v', './vlib/vweb/vweb.v', ] diff --git a/vlib/flag/flag.v b/vlib/flag/flag.v index 1f95082e92..1db351371e 100644 --- a/vlib/flag/flag.v +++ b/vlib/flag/flag.v @@ -54,6 +54,23 @@ pub struct Flag { // and also the default value, when the flag is not given } +pub fn (f Flag) str() string { + return '' + +' flag:\n' + +' name: $f.name\n' + +' abbr: $f.abbr\n' + +' usag: $f.usage\n' + +' desc: $f.val_desc' +} +pub fn (af []Flag) str() string { + mut res := []string + res << '\n []Flag = [' + for f in af { + res << f.str() + } + res << ' ]' + return res.join('\n') +} // pub struct FlagParser { pub mut: @@ -135,7 +152,10 @@ fn (fs mut FlagParser) parse_value(longhand string, shorthand byte) []string { //End of input. We're done here. break } - if arg == full || (arg[0] == `-` && arg[1] == shorthand && arg.len == 2) { + if arg[0] != `-` { + continue + } + if (arg.len == 2 && arg[0] == `-` && arg[1] == shorthand ) || arg == full { if i+1 > fs.args.len { panic("Missing argument for '$longhand'") } @@ -177,7 +197,13 @@ fn (fs mut FlagParser) parse_bool_value(longhand string, shorthand byte) ?string //End of input. We're done. break } - if arg == full || (arg[0] == `-` && arg[1] == shorthand && arg.len == 2) { + if arg.len == 0 { + continue + } + if arg[0] != `-` { + continue + } + if ( arg.len == 2 && arg[0] == `-` && arg[1] == shorthand ) || arg == full { if fs.args.len > i+1 && (fs.args[i+1] in ['true', 'false']) { val := fs.args[i+1] fs.args.delete(i+1) @@ -194,7 +220,7 @@ fn (fs mut FlagParser) parse_bool_value(longhand string, shorthand byte) ?string fs.args.delete(i) return val } - if arg[0] == `-` && arg.index_byte(shorthand) != -1 { + if arg[0] == `-` && arg[1] != `-` && arg.index_byte(shorthand) != -1 { // -abc is equivalent to -a -b -c return 'true' } diff --git a/vlib/flag/flag_test.v b/vlib/flag/flag_test.v index 689c19d6c8..6c20198934 100644 --- a/vlib/flag/flag_test.v +++ b/vlib/flag/flag_test.v @@ -320,3 +320,24 @@ fn test_multiple_arguments() { assert b[0] == 'a' && b[1] == 'c' && b[2] == 'b' assert c[0] == 1.23 && c[1] == 2.34 && c[2] == 3.45 } + +fn test_long_options_that_start_with_the_same_letter_as_another_short_option() { + mut fp := flag.new_flag_parser([ + '--vabc', '/abc', + ]) + verbose := fp.bool_('verbose', `v`, false, 'Be more verbose.') + vabc := fp.string_('vabc', `x`, 'default', 'Another option that *may* conflict with v, but *should not*') + assert verbose == false + assert vabc == '/abc' +} + +fn test_long_options_that_start_with_the_same_letter_as_another_short_option_both_set() { + mut fp := flag.new_flag_parser([ + '-v', + '--vabc', '/abc', + ]) + verbose := fp.bool_('verbose', `v`, false, 'Be more verbose.') + vabc := fp.string_('vabc', `x`, 'default', 'Another option that *may* conflict with v, but *should not*') + assert verbose == true + assert vabc == '/abc' +}