tools: bugfixes and new features for oldv and performance_compare
parent
0d93eeb3fe
commit
c1cc203c17
|
@ -52,3 +52,6 @@ vlib/os/bare/bare_example_linux
|
|||
|
||||
info.log
|
||||
.vscode/**
|
||||
|
||||
# vim/emacs editor backup files
|
||||
*~
|
||||
|
|
|
@ -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()
|
||||
|
@ -26,28 +36,40 @@ pub fn verbose_trace_exec_result(x os.Result) {
|
|||
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
189
tools/oldv.v
189
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...
|
||||
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 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.
|
||||
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}'
|
||||
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()
|
||||
|
||||
show_help:=fp.bool_('help', `h`, false, 'Show this help screen.')
|
||||
context.verbose = fp.bool_('verbose', `v`, false, 'Be more verbose.\n')
|
||||
fp.limit_free_args(1, 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.')
|
||||
|
||||
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.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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,19 +2,22 @@ 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
|
||||
mut:
|
||||
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
|
||||
|
@ -24,240 +27,170 @@ mut:
|
|||
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
|
||||
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([
|
||||
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',
|
||||
'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',
|
||||
])
|
||||
|
||||
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)
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue