tools: bugfixes and new features for oldv and performance_compare

pull/3375/head
Delyan Angelov 2020-01-08 22:45:47 +02:00 committed by Alexander Medvednikov
parent 0d93eeb3fe
commit c1cc203c17
8 changed files with 489 additions and 381 deletions

3
.gitignore vendored
View File

@ -52,3 +52,6 @@ vlib/os/bare/bare_example_linux
info.log info.log
.vscode/** .vscode/**
# vim/emacs editor backup files
*~

View File

@ -2,17 +2,27 @@ module scripting
import os 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 { if os.getenv('VERBOSE').len > 0 {
slabel := 'scripting.${label}' slabel := 'scripting.${label}'
println('# ${slabel:30s} : $message') println('# ${slabel:-25s} : $message')
} }
} }
pub fn verbose_trace_exec_result(x os.Result) { pub fn verbose_trace_exec_result(x os.Result) {
if os.getenv('VERBOSE').len > 0 { if os.getenv('VERBOSE').len > 0 {
println('# cmd.exit_code : ${x.exit_code.str()}') println('# cmd.exit_code : ${x.exit_code.str():-4s} cmd.output:')
println('# cmd.output :')
println('# ----------------------------------- #') println('# ----------------------------------- #')
mut lnum := 1 mut lnum := 1
lines := x.output.split_into_lines() lines := x.output.split_into_lines()
@ -21,33 +31,45 @@ pub fn verbose_trace_exec_result(x os.Result) {
lnum++ lnum++
} }
println('# ----------------------------------- #') println('# ----------------------------------- #')
} }
} }
pub fn chdir(path string) { pub fn chdir(path string) {
verbose_trace(@FN, 'cd $path') verbose_trace(@FN, 'cd $path')
os.chdir( path ) os.chdir(path)
} }
pub fn run(cmd string) string { pub fn run(cmd string) string {
verbose_trace(@FN, cmd) verbose_trace(@FN, cmd)
x := os.exec(cmd) or { return '' } x := os.exec(cmd) or {
verbose_trace_exec_result( x ) verbose_trace(@FN, '## failed.')
if x.exit_code == 0 { return x.output } return ''
}
verbose_trace_exec_result(x)
if x.exit_code == 0 {
return x.output
}
return '' return ''
} }
pub fn command_exits_with_zero_status(cmd string) bool { pub fn exit_0_status(cmd string) bool {
verbose_trace(@FN, cmd) verbose_trace(@FN, cmd)
x := os.exec(cmd) or { return false } x := os.exec(cmd) or {
verbose_trace_exec_result( x ) verbose_trace(@FN, '## failed.')
if x.exit_code == 0 { return true } return false
}
verbose_trace_exec_result(x)
if x.exit_code == 0 {
return true
}
return false return false
} }
pub fn tool_must_exist(toolcmd string) { pub fn tool_must_exist (toolcmd string) {
verbose_trace(@FN, toolcmd) 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('Missing tool: $toolcmd')
eprintln('Please try again after you install it.') eprintln('Please try again after you install it.')
exit(1) 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) { pub fn show_sizes_of_files(files []string) {
if v_timestamp >= 1561805697 { return } for f in files {
eprintln('##################################################################') size := os.file_size(f)
eprintln('# WARNING: v self rebuilding, before 5b7a1e8 (2019-06-29 12:21) #') println('${size:10d} $f')
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('##################################################################')
} }

View File

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

View File

@ -3,169 +3,101 @@ import (
flag flag
filepath filepath
scripting scripting
) vgit
)
const ( const (
tool_version = '0.0.2' tool_version = '0.0.3'
tool_description = 'Checkout an old V and compile it. Useful when you want to discover when something broke.' tool_description = ' Checkout an old V and compile it as it was on specific commit.
remote_repo_url_v = 'https://github.com/vlang/v' This tool is useful, when you want to discover when something broke.
remote_repo_url_vc = 'https://github.com/vlang/vc' 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 { struct Context {
mut: 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... v_repo_url 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... 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 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_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_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. 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_v string // the full path to the v folder inside workdir.
path_vc string // the full path to the vc 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 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. cc string='cc' // the C compiler to use for bootstrapping.
cleanup bool // should the tool run a cleanup first cleanup bool // should the tool run a cleanup first
verbose bool // should the tool be much more verbose 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() { fn (c mut Context) compile_oldv_if_needed() {
vexename := if os.user_os() == 'windows' { 'v.exe' } else { 'v' } mut vgit_context := vgit.VGitContext{
vexepath := filepath.join( c.path_v, vexename ) cc: c.cc
workdir: c.workdir
mut command_for_building_v_from_c_source := '' commit_v: c.commit_v
mut commands_for_selfbuilding := []string path_v: c.path_v
if 'windows' == os.user_os() { path_vc: c.path_vc
command_for_building_v_from_c_source = '$c.cc -w -o cv.exe "$c.path_vc/v_win.c" ' v_repo_url: c.v_repo_url
commands_for_selfbuilding << './cv.exe -o v2.exe {SOURCE}' vc_repo_url: c.vc_repo_url
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}'
} }
vgit_context.compile_oldv_if_needed()
scripting.chdir( c.workdir ) c.commit_v_hash = vgit_context.commit_v__hash
scripting.run('git clone --quiet "$c.repo_url_v" "$c.path_v" ') if !os.exists(vgit_context.vexepath) && c.cmd_to_run.len > 0 {
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 {
// NB: 125 is a special code, that git bisect understands as 'skip this commit'. // 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. // it is used to inform git bisect that the current commit leads to a build failure.
exit( 125 ) 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)
} }
} }
fn main(){ fn main() {
scripting.used_tools_must_exist(['git','cc']) scripting.used_tools_must_exist(['git', 'cc'])
mut context := Context{} mut context := Context{}
mut fp := flag.new_flag_parser(os.args) mut fp := flag.new_flag_parser(os.args)
fp.application(filepath.filename(os.executable())) fp.application(filepath.filename(os.executable()))
fp.version( tool_version ) fp.version(tool_version)
fp.description( tool_description ) fp.description(tool_description)
fp.arguments_description('VCOMMIT') fp.arguments_description('VCOMMIT')
fp.skip_executable() fp.skip_executable()
fp.limit_free_args(1, 1)
show_help:=fp.bool_('help', `h`, false, 'Show this help screen.') context.cleanup = fp.bool('clean', true, 'Clean before running (slower).')
context.verbose = fp.bool_('verbose', `v`, false, 'Be more verbose.\n') context.cmd_to_run = fp.string_('command', `c`, '', 'Command to run in the old V repo.\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)
}
commits := vgit.add_common_tool_options(mut context, mut fp)
if commits.len > 0 { if commits.len > 0 {
context.commit_v = commits[0] context.commit_v = commits[0]
validate_commit_exists( context.commit_v ) } else {
}else{
context.commit_v = scripting.run('git rev-list -n1 HEAD') context.commit_v = scripting.run('git rev-list -n1 HEAD')
} }
println('################# context.commit_v: $context.commit_v #####################') println('################# context.commit_v: $context.commit_v #####################')
context.path_v = vgit.normalized_workpath_for_commit(context.workdir, context.commit_v)
context.path_v = context.normalized_workpath_for_commit( context.commit_v ) context.path_vc = vgit.normalized_workpath_for_commit(context.workdir, 'vc')
context.path_vc = context.normalized_workpath_for_commit( 'vc' ) if !os.is_dir(context.workdir) {
if !os.is_dir( context.workdir ) {
msg := 'Work folder: ' + context.workdir + ' , does not exist.' msg := 'Work folder: ' + context.workdir + ' , does not exist.'
eprintln(msg) eprintln(msg)
exit(2) exit(2)
} }
ecc := os.getenv('CC') ecc := os.getenv('CC')
if ecc!='' { context.cc = ecc } if ecc != '' {
context.cc = ecc
}
if context.cleanup { if context.cleanup {
scripting.run('rm -rf $context.path_v') scripting.run('rm -rf $context.path_v')
scripting.run('rm -rf $context.path_vc') scripting.run('rm -rf $context.path_vc')
@ -173,17 +105,16 @@ fn main(){
context.compile_oldv_if_needed() context.compile_oldv_if_needed()
scripting.chdir( context.path_v ) scripting.chdir(context.path_v)
println('# v commit hash: $context.commit_v_hash') println('# v commit hash: $context.commit_v_hash')
println('# checkout folder: $context.path_v') println('# checkout folder: $context.path_v')
if context.cmd_to_run.len > 0 { if context.cmd_to_run.len > 0 {
cmdres := os.exec( context.cmd_to_run ) or { panic(err) } cmdres := os.exec(context.cmd_to_run) or {
println('# command: $context.cmd_to_run') panic(err)
println('# command exit code: $cmdres.exit_code') }
println('# command result :') println('# command: ${context.cmd_to_run:-34s} exit code: ${cmdres.exit_code:-4d} result:')
println(cmdres.output) println(cmdres.output)
exit( cmdres.exit_code ) exit(cmdres.exit_code)
} }
} }

View File

@ -2,266 +2,199 @@ import (
os os
flag flag
filepath filepath
scripting
vgit
) )
const ( const (
tool_version = '0.0.4' tool_version = '0.0.5'
tool_description = '' + tool_description = ' Compares V executable size and performance,
' Compares V executable size and performance,\n' + between 2 commits from V\'s local git history.
' between 2 commits from V\'s local git history.\n' + When only one commit is given, it is compared to master.
' When only one commit is given, it is compared to master.' '
) )
struct Context { struct Context {
cwd string // current working folder cwd string // current working folder
mut: 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... v_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 vc_repo_url string // the url of the vc repository. It can be a local folder path, which is usefull to eliminate network operations...
a string // the full path to the 'after' folder inside workdir workdir string // the working folder (typically /tmp), where the tool will write
b string // the full path to the 'before' folder inside workdir a string // the full path to the 'after' folder inside workdir
vc string // the full path to the vc folder inside workdir. It is used during bootstrapping v from the C source. 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_before string // the git commit for the 'before' state
commit_after string // the git commit for the 'after' state commit_after string // the git commit for the 'after' state
warmups int // how many times to execute a command before gathering stats warmups int // how many times to execute a command before gathering stats
verbose bool // whether to print even more stuff 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 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 { fn new_context() Context {
return Context{ cwd: os.getwd(), commit_after: 'master', warmups: 4 } return Context{
} cwd: os.getwd()
commit_after: 'master'
////// The stuff in this block may be reusable for other v cli tools? ///////////////// warmups: 4
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)
} }
} }
//////////////////////////////////////////////////////////////////////////
fn (c Context) compare_versions() { fn (c Context) compare_versions() {
// Input is validated at this point... // Input is validated at this point...
// Cleanup artifacts from previous runs of this tool: // Cleanup artifacts from previous runs of this tool:
os.chdir( c.workdir ) scripting.chdir(c.workdir)
run('rm -rf "$c.a" "$c.b" "$c.vc" ') scripting.run('rm -rf "$c.a" "$c.b" "$c.vc" ')
// clone the VC source *just once per comparison*, and reuse it: // 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) ...') 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.b, c.commit_before)
c.prepare_v( c.a , c.commit_after ) c.prepare_v(c.a, c.commit_after)
scripting.chdir(c.workdir)
os.chdir( c.workdir ) if c.vflags.len > 0 {
//The first is the baseline, against which all the others will be compared. os.setenv('VFLAGS', c.vflags, true)
//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', // The first is the baseline, against which all the others will be compared.
'vprod -o source.c examples/hello_world.v', // It is the fastest, since hello_world.v has only a single println in it,
'vprod @DEBUG@ -o source.c @COMPILER@', mut perf_files := []string
'vprod -o source.c @COMPILER@', perf_files << c.compare_v_performance('source_hello', [
'vprod -o hello examples/hello_world.v', 'vprod @DEBUG@ -o source.c examples/hello_world.v',
'vprod -o binary @COMPILER@', 'vprod -o source.c examples/hello_world.v',
///////////////////////////////////////////////////////// 'v @DEBUG@ -o source.c examples/hello_world.v',
'v @DEBUG@ -o source.c examples/hello_world.v', 'v -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@', perf_files << c.compare_v_performance('source_v', [
'v -o hello examples/hello_world.v', 'vprod @DEBUG@ -o source.c @COMPILER@',
'v -o binary @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) { println('All performance files:')
for f in files { for f in perf_files {
size := os.file_size(f) println(' $f')
println('${size:10d} $f')
} }
} }
fn line_to_timestamp_and_commit(line string) (int, string) { fn (c &Context) prepare_v(cdir string, commit 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 ) {
mut cc := os.getenv('CC') mut cc := os.getenv('CC')
if cc == '' { cc = '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() { mut vgit_context := vgit.VGitContext{
command_for_building_v_from_c_source = '$cc -std=gnu11 -w -o cv.exe $c.vc/v_win.c' 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('---------------------------------------------------------------------------------')
println('Compare v performance when doing the following commands:') 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_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/ ' }
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_a, _ := line_to_timestamp_and_commit(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'))
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_a := if timestamp_a > 1570877641 { '-g' } else { '-debug' } debug_option_b := if timestamp_b > 1570877641 { '-g ' } else { '-debug ' }
debug_option_b := if timestamp_b > 1570877641 { '-g' } else { '-debug' }
mut hyperfine_commands_arguments := []string mut hyperfine_commands_arguments := []string
for cmd in commands { println(cmd) }
for cmd in commands { 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 { 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])
} }
/////////////////////////////////////////////////////////////////////////////// for cmd in commands {
cmd_stats_file := os.realpath([ c.workdir, 'v_performance_stats.json'].join(os.path_separator)) hyperfine_commands_arguments << " \'cd ${c.a:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_a, '@DEBUG@', debug_option_a])
comparison_cmd := 'hyperfine $c.hyperfineopts '+ }
'--export-json ${cmd_stats_file} '+ // /////////////////////////////////////////////////////////////////////////////
'--time-unit millisecond '+ cmd_stats_file := os.realpath([c.workdir, 'v_performance_stats_${label}.json'].join(os.path_separator))
'--style full --warmup $c.warmups ' + comparison_cmd := 'hyperfine $c.hyperfineopts ' + '--export-json ${cmd_stats_file} ' + '--time-unit millisecond ' + '--style full --warmup $c.warmups ' + hyperfine_commands_arguments.join(' ')
hyperfine_commands_arguments.join(' ') // /////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// if c.verbose {
if c.verbose { println( comparison_cmd ) } println(comparison_cmd)
os.system( comparison_cmd ) }
os.system(comparison_cmd)
println('The detailed performance comparison report was saved to: $cmd_stats_file .') println('The detailed performance comparison report was saved to: $cmd_stats_file .')
println('') println('')
return cmd_stats_file
} }
fn (c Context) normalized_workpath_for_commit( commit string ) string { fn main() {
nc := 'v_at_' + commit.replace('^','_').replace('-','_').replace('/','_') scripting.used_tools_must_exist(['cp', 'rm', 'strip', 'make', 'git', 'upx', 'cc', 'wc', 'tail', 'hyperfine'])
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'])
mut context := new_context() mut context := new_context()
mut fp := flag.new_flag_parser(os.args) mut fp := flag.new_flag_parser(os.args)
fp.application(filepath.filename(os.executable())) fp.application(filepath.filename(os.executable()))
fp.version( tool_version ) fp.version(tool_version)
fp.description( tool_description ) fp.description(tool_description)
fp.arguments_description('COMMIT_BEFORE [COMMIT_AFTER]') fp.arguments_description('COMMIT_BEFORE [COMMIT_AFTER]')
fp.skip_executable() fp.skip_executable()
fp.limit_free_args(1,2) fp.limit_free_args(1, 2)
show_help:=fp.bool('help', false, 'Show this help screen\n')
context.vc_repo_url = fp.string('vcrepo', 'https://github.com/vlang/vc', context.vflags = fp.string('vflags', '', 'Additional options to pass to the v commands, for example "-cc tcc"')
'' +
'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.hyperfineopts = fp.string('hyperfine_options', '', context.hyperfineopts = fp.string('hyperfine_options', '',
'' + 'Additional options passed to hyperfine.
'Additional options passed to hyperfine.\n'+ ${flag.SPACE}For example on linux, you may want to pass:
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\'"
flag.SPACE+' --hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'" \n') ')
commits := vgit.add_common_tool_options(mut context, mut fp)
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)
}
context.commit_before = commits[0] context.commit_before = commits[0]
if commits.len > 1 { context.commit_after = commits[1] } if commits.len > 1 {
context.commit_after = commits[1]
validate_commit_exists( context.commit_before ) }
validate_commit_exists( context.commit_after ) 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.b = context.normalized_workpath_for_commit( context.commit_before ) context.vc = vgit.normalized_workpath_for_commit(context.workdir, 'vc')
context.a = context.normalized_workpath_for_commit( context.commit_after ) if !os.is_dir(context.workdir) {
context.vc = context.normalized_workpath_for_commit( 'vc' )
if !os.is_dir( context.workdir ) {
msg := 'Work folder: ' + context.workdir + ' , does not exist.' msg := 'Work folder: ' + context.workdir + ' , does not exist.'
eprintln(msg) eprintln(msg)
exit(2) exit(2)
} }
context.compare_versions() context.compare_versions()
} }

View File

@ -8,6 +8,13 @@ import (
const ( const (
known_failing_exceptions = ['./examples/vweb/vweb_example.v', known_failing_exceptions = ['./examples/vweb/vweb_example.v',
'./tools/gen_vc.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/article.v',
'./tutorials/code/blog/blog.v', './tutorials/code/blog/blog.v',
'./vlib/arrays/arrays.v', './vlib/arrays/arrays.v',
@ -15,10 +22,6 @@ const (
'./vlib/builtin/js/hashmap.v', './vlib/builtin/js/hashmap.v',
'./vlib/compiler/tests/fn_variadic_test.v', './vlib/compiler/tests/fn_variadic_test.v',
'./vlib/compiler/tests/generic_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.v',
'./vlib/crypto/aes/aes_cbc.v', './vlib/crypto/aes/aes_cbc.v',
'./vlib/crypto/aes/block_generic.v', './vlib/crypto/aes/block_generic.v',
@ -28,7 +31,7 @@ const (
'./vlib/eventbus/eventbus_test.v', './vlib/eventbus/eventbus_test.v',
'./vlib/os/bare/bare_example_linux.v', './vlib/os/bare/bare_example_linux.v',
'./vlib/szip/szip.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/assets/assets.v',
'./vlib/vweb/vweb.v', './vlib/vweb/vweb.v',
] ]

View File

@ -54,6 +54,23 @@ pub struct Flag {
// and also the default value, when the flag is not given // 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 struct FlagParser {
pub mut: pub mut:
@ -135,7 +152,10 @@ fn (fs mut FlagParser) parse_value(longhand string, shorthand byte) []string {
//End of input. We're done here. //End of input. We're done here.
break 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 { if i+1 > fs.args.len {
panic("Missing argument for '$longhand'") 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. //End of input. We're done.
break 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']) { if fs.args.len > i+1 && (fs.args[i+1] in ['true', 'false']) {
val := fs.args[i+1] val := fs.args[i+1]
fs.args.delete(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) fs.args.delete(i)
return val 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 // -abc is equivalent to -a -b -c
return 'true' return 'true'
} }

View File

@ -320,3 +320,24 @@ fn test_multiple_arguments() {
assert b[0] == 'a' && b[1] == 'c' && b[2] == 'b' assert b[0] == 'a' && b[1] == 'c' && b[2] == 'b'
assert c[0] == 1.23 && c[1] == 2.34 && c[2] == 3.45 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'
}