1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

move v.v to cmd/v

This commit is contained in:
lutherwenxu
2020-02-09 17:08:04 +08:00
committed by GitHub
parent 3f5e4c55b2
commit 9332a83ce6
64 changed files with 1123 additions and 799 deletions

9
cmd/tools/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
gen_vc
performance_compare
vcreate
vnames
vpm
vrepl
vtest
vtest-compiler
vup

112
cmd/tools/bench/hashmap.v Normal file
View File

@@ -0,0 +1,112 @@
import rand
import time
import builtin.hashmap
fn hashmap_set_bench(arr []string, repeat int) {
start_time := time.ticks()
for _ in 0..repeat {
mut b := hashmap.new_hashmap()
for x in arr {
b.set(x, 1)
}
}
end_time := time.ticks() - start_time
println("* hashmap_set: ${end_time} ms")
}
fn map_set_bench(arr []string, repeat int) {
start_time := time.ticks()
for _ in 0..repeat {
mut b := map[string]int
for x in arr {
b[x] = 1
}
}
end_time := time.ticks() - start_time
println("* map_set: ${end_time} ms")
}
fn hashmap_get_bench(arr []string, repeat int) {
mut b := hashmap.new_hashmap()
for x in arr {
b.set(x, 1)
}
start_time := time.ticks()
for _ in 0..repeat {
for x in arr {
b.get(x)
}
}
end_time := time.ticks() - start_time
println("* hashmap_get: ${end_time} ms")
}
fn map_get_bench(arr []string, repeat int) {
mut b := map[string]int
for x in arr {
b[x] = 1
}
start_time := time.ticks()
for _ in 0..repeat {
for x in arr {
b[x]
}
}
end_time := time.ticks() - start_time
println("* map_get: ${end_time} ms")
}
fn benchmark_many_keys() {
key_len := 30
repeat := 1
for i := 2048; i <= 10000000; i = i * 2 {
mut arr := []string
for _ in 0..i {
mut buf := []byte
for j in 0..key_len {
buf << byte(rand.next(int(`z`) - int(`a`)) + `a`)
}
s := string(buf)
arr << s
}
println("$arr.len keys of length $key_len")
// Uncomment the benchmark you would like to benchmark
// Run one or two at a time while memory leaks is a thing
hashmap_get_bench(arr, repeat)
map_get_bench(arr, repeat)
// hashmap_set_bench(arr, repeat)
// map_set_bench(arr, repeat)
println('')
}
}
fn benchmark_few_keys() {
key_len := 30
repeat := 10000
println("Benchmarks are repeated $repeat times")
for i := 16; i <= 2048; i = i * 2 {
mut arr := []string
for _ in 0..i {
mut buf := []byte
for j in 0..key_len {
buf << byte(rand.next(int(`z`) - int(`a`)) + `a`)
}
s := string(buf)
arr << s
}
println("$arr.len keys of length $key_len")
// Uncomment the benchmark you would like to benchmark
// Run one or two at a time while memory leaks is a thing
hashmap_get_bench(arr, repeat)
map_get_bench(arr, repeat)
// hashmap_set_bench(arr, repeat)
// map_set_bench(arr, repeat)
println('')
}
}
fn main() {
// Uncomment below to benchmark on many keys
// benchmark_many_keys()
benchmark_few_keys()
}

55
cmd/tools/bench/wyhash.v Normal file
View File

@@ -0,0 +1,55 @@
module main
import (
hash.fnv1a
hash.wyhash
rand
time
)
fn main() {
sample_size := 10000000
min_str_len := 20
max_str_len := 40
println('Generating $sample_size strings between $min_str_len - $max_str_len chars long...')
mut bytepile := []byte
for _ in 0 .. sample_size * max_str_len {
bytepile << byte(40 + rand.next(125 - 40))
}
mut str_lens := []int
for _ in 0 .. sample_size {
str_lens << min_str_len + rand.next(max_str_len - min_str_len)
}
println('Hashing each of the generated strings...')
t0 := time.ticks()
mut start_pos := 0
for len in str_lens {
end_pos := start_pos + len
str := string(bytepile[start_pos..end_pos],len)
_ = wyhash.wyhash_c(&str.str, u64(str.len), 1)
start_pos = end_pos
}
t1 := time.ticks()
d1 := t1 - t0
println(' * wyhash4 C: ${d1}ms')
start_pos = 0
for len in str_lens {
end_pos := start_pos + len
str := string(bytepile[start_pos..end_pos],len)
_ = wyhash.sum64_string(str, 1)
start_pos = end_pos
}
t2 := time.ticks()
d2 := t2 - t1
println(' * wyhash4: ${d2}ms')
start_pos = 0
for len in str_lens {
end_pos := start_pos + len
str := string(bytepile[start_pos..end_pos],len)
_ = fnv1a.sum64_string(str)
start_pos = end_pos
}
t3 := time.ticks()
d3 := t3 - t2
println(' * fnv1a64: ${d3}ms')
}

3
cmd/tools/fast/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
fast
v2
index.html

89
cmd/tools/fast/fast.v Normal file
View File

@@ -0,0 +1,89 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
import (
os
time
filepath
)
fn main() {
exe := os.executable()
dir := filepath.dir(exe)
vdir := filepath.dir(filepath.dir(filepath.dir(dir)))
if !os.exists('$vdir/v') && !os.is_dir('$vdir/vlib') {
println('fast.html generator needs to be located in `v/cmd/tools/fast`')
}
println('fast.html generator\n')
// Fetch the last commit's hash
println('Fetching updates...')
ret := os.system('git pull --rebase')
if ret != 0 {
println('failed to git pull')
return
}
mut commit_hash := exec('git rev-parse HEAD')
commit_hash = commit_hash[..7]
if !os.exists('table.html') {
os.create('table.html') or { panic(err) }
}
mut table := os.read_file('table.html') or { panic(err) }
// Do nothing if it's already been processed.
if table.contains(commit_hash) {
println('Commit $commit_hash has already been processed')
return
}
// Build an optimized V
println('Building vprod...')
exec('v -o $vdir/vprod -prod $vdir/cmd/v')
println('Measuring...')
diff1 := measure('$vdir/vprod -cc clang -o v.c $vdir/cmd/v')
diff2 := measure('$vdir/vprod -cc clang -o v2 $vdir/cmd/v')
diff3 := measure('$vdir/vprod -x64 $vdir/cmd/tools/1mil.v')
diff4 := measure('$vdir/vprod -cc clang $vdir/examples/hello_world.v')
//println('Building V took ${diff}ms')
commit_date := exec('git log -n1 --pretty="format:%at"')
message := exec('git log -n1 --pretty="format:%s"')
date := time.unix(commit_date.int())
mut out := os.create('table.html') or { panic(err) }
// Place the new row on top
table =
'<tr>
<td>${date.format()}</td>
<td><a target=_blank href="https://github.com/vlang/v/commit/$commit_hash">$commit_hash</a></td>
<td>$message</td>
<td>${diff1}ms</td>
<td>${diff2}ms</td>
<td>${diff3}ms</td>
<td>${diff4}ms</td>
</tr>\n' +
table.trim_space()
out.writeln(table)
out.close()
// Regenerate index.html
header := os.read_file('header.html') or { panic(err) }
footer := os.read_file('footer.html') or { panic(err) }
mut res := os.create('index.html') or { panic(err) }
res.writeln(header)
res.writeln(table)
res.writeln(footer)
res.close()
}
fn exec(s string) string {
e := os.exec(s) or { panic(err) }
return e.output
}
// returns milliseconds
fn measure(cmd string) int {
println('Warming up...')
for i in 0..3 {
exec(cmd)
}
println('Building...')
ticks := time.ticks()
exec(cmd)
return int(time.ticks() - ticks)
}

View File

@@ -0,0 +1,65 @@
(function () {
var table = document.querySelector("table");
var isTbody = table.children[0].nodeName == "TBODY";
var trs = isTbody
? table.children[0].querySelectorAll("tr")
: table.querySelectorAll("tr");
trs.forEach(function (tr, idx) {
if (idx != 0 && idx + 1 < trs.length) {
var vc = 3, vv = 4, vf = 5, vh = 6;
var textContent = {
vc: tr.children[vc].textContent,
vv: tr.children[vv].textContent,
vf: tr.children[vf].textContent,
vh: tr.children[vh].textContent
};
var currentData = {
vc: int(textContent.vc.slice(0, -2)),
vv: int(textContent.vv.slice(0, -2)),
vf: int(textContent.vf.slice(0, -2)),
vh: int(textContent.vh.slice(0, -2))
};
var prevData = {
vc: int(trs[idx + 1].children[vc].textContent.slice(0, -2)),
vv: int(trs[idx + 1].children[vv].textContent.slice(0, -2)),
vf: int(trs[idx + 1].children[vf].textContent.slice(0, -2)),
vh: int(trs[idx + 1].children[vh].textContent.slice(0, -2))
};
var result = {
vc: currentData.vc - prevData.vc,
vv: currentData.vv - prevData.vv,
vf: currentData.vf - prevData.vf,
vh: currentData.vh - prevData.vh
};
if (Math.abs(result.vc) > 50)
tr.children[vc].appendChild(createElement(result.vc));
if (Math.abs(result.vv) > 50)
tr.children[vv].appendChild(createElement(result.vv));
if (Math.abs(result.vf) > 50)
tr.children[vf].appendChild(createElement(result.vf));
if (Math.abs(result.vh) > 50)
tr.children[vh].appendChild(createElement(result.vh));
}
});
function int(src) {
return src - 0;
}
function getClassName(x) {
if (x == 0)
return "equal";
return x < 0 ? "plus" : "minus";
}
function createElement(result) {
var el = document.createElement("span");
var parsedResult = parseResult(result);
el.classList.add("diff");
el.classList.add(getClassName(result));
el.textContent = parsedResult;
return el;
}
function parseResult(x) {
if (x == 0)
return "0";
return x > 0 ? "+" + x : x;
}
})();

View File

@@ -0,0 +1,4 @@
</table>
<script src="main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Is V still fast?</title>
<style>
*, body {
font-family: Menlo, Monospace, 'Courier New';
}
table, td {
border-collapse: collapse;
border: 1px solid #dfdfdf;
}
td {
padding: 5px;
position: relative;
}
.diff {
border-radius: 2.5px;
color: #ffffff;
padding: 0 5px 0 5px;
position: absolute;
right: 5px;
}
.minus {
background-color: rgb(195, 74, 104);
}
.plus {
background-color: #8BC34A;
}
.equal {
background-color: rgb(113, 68, 172);
}
</style>
</head>
<body>
<h2>Is V still fast?</h2>
Monitoring compilation speed for each commit. <br><br>
Source code: <a target=blank href='https://github.com/vlang/v/blob/master/cmd/tools/fast/fast.v'>fast.v</a> <br><br>
<table>
<tr>
<td></td>
<td></td>
<td style='width:400px'></td>
<td style='width:120px'>v -o v.c</td>
<td style='width:120px'>v -o v</td>
<td style='width:130px'>v -x64 1mil.v</td>
<td style='width:120px'>v hello.v</td>
</tr>

19
cmd/tools/gen1m.v Normal file
View File

@@ -0,0 +1,19 @@
fn main() {
println('fn println(a int) {}')
println('fn print(a string) {}')
for i in 0..100000 {
println('
fn foo${i}() {
x := $i
mut a := x
a += 2
println(a)
a = 0
a = 1
}
')
}
//println('fn main() {foo1()} ')
println('fn main() { print("1m DONE") } ')
}

389
cmd/tools/gen_vc.v Normal file
View File

@@ -0,0 +1,389 @@
// This tool regenerates V's bootstrap .c files
// every time the V master branch is updated.
// if run with the --serve flag it will run in webhook
// server mode awaiting a request to http://host:port/genhook
// available command line flags:
// --work-dir gen_vc's working directory
// --purge force purge the local repositories
// --serve run in webhook server mode
// --port port for http server to listen on
// --log-to either 'file' or 'terminal'
// --log-file path to log file used when --log-to is 'file'
// --dry-run dont push anything to remote repo
module main
import (
os
log
flag
time
vweb
net.urllib
)
// git credentials
const(
git_username = os.getenv('GITUSER')
git_password = os.getenv('GITPASS')
)
// repository
const(
// git repo
git_repo_v = 'github.com/vlang/v'
git_repo_vc = 'github.com/vlang/vc'
// local repo directories
git_repo_dir_v = 'v'
git_repo_dir_vc = 'vc'
)
// gen_vc
const(
// name
app_name = 'gen_vc'
// version
app_version = '0.1.1'
// description
app_description = 'This tool regenerates V\'s bootstrap .c files every time the V master branch is updated.'
// assume something went wrong if file size less than this
too_short_file_limit = 5000
// create a .c file for these os's
vc_build_oses = [
'nix', // all nix based os
'windows'
]
)
// default options (overridden by flags)
const(
// gen_vc working directory
work_dir = '/tmp/gen_vc'
// dont push anything to remote repo
dry_run = false
// server port
server_port = 7171
// log file
log_file = '$work_dir/log.txt'
// log_to is either 'file' or 'terminal'
log_to = 'terminal'
)
// errors
const(
err_msg_build = 'error building'
err_msg_make = 'make failed'
err_msg_gen_c = 'failed to generate .c file'
err_msg_cmd_x = 'error running cmd'
)
struct GenVC {
// logger
logger &log.Log
// flag options
options FlagOptions
mut:
// true if error was experienced running generate
gen_error bool
}
// webhook server
pub struct WebhookServer {
pub mut:
vweb vweb.Context
gen_vc &GenVC
}
// storage for flag options
struct FlagOptions {
work_dir string
purge bool
serve bool
port int
log_to string
log_file string
dry_run bool
}
fn main() {
mut fp := flag.new_flag_parser(os.args.clone())
fp.application(app_name)
fp.version(app_version)
fp.description(app_description)
fp.skip_executable()
show_help:=fp.bool('help', false, 'Show this help screen\n')
flag_options := parse_flags(mut fp)
if( show_help ){ println( fp.usage() ) exit(0) }
fp.finalize() or {
eprintln(err)
println(fp.usage())
return
}
// webhook server mode
if flag_options.serve {
vweb.run<WebhookServer>(flag_options.port)
}
// cmd mode
else {
mut gen_vc := new_gen_vc(flag_options)
gen_vc.init()
gen_vc.generate()
}
}
// new GenVC
fn new_gen_vc(flag_options FlagOptions) &GenVC {
mut logger := &log.Log{}
logger.set_level(log.DEBUG)
if flag_options.log_to == 'file' {
logger.set_full_logpath( flag_options.log_file )
}
return &GenVC{
options: flag_options
logger: logger
}
}
// WebhookServer init
pub fn (ws mut WebhookServer) init() {
mut fp := flag.new_flag_parser(os.args.clone())
flag_options := parse_flags(mut fp)
ws.gen_vc = new_gen_vc(flag_options)
ws.gen_vc.init()
//ws.gen_vc = new_gen_vc(flag_options)
}
// gen webhook
pub fn (ws mut WebhookServer) genhook() {
ws.gen_vc.generate()
// error in generate
if ws.gen_vc.gen_error {
ws.vweb.json('{status: "failed"}')
return
}
ws.vweb.json('{status: "ok"}')
}
pub fn (ws &WebhookServer) reset() {
}
// parse flags to FlagOptions struct
fn parse_flags(fp mut flag.FlagParser) FlagOptions {
return FlagOptions{
serve : fp.bool('serve', false, 'run in webhook server mode')
work_dir : fp.string('work-dir', work_dir, 'gen_vc working directory')
purge : fp.bool('purge', false, 'force purge the local repositories')
port : fp.int('port', server_port, 'port for web server to listen on')
log_to : fp.string('log-to', log_to, 'log to is \'file\' or \'terminal\'')
log_file : fp.string('log-file', log_file, 'log file to use when log-to is \'file\'')
dry_run : fp.bool('dry-run', dry_run, 'when specified dont push anything to remote repo')
}
}
// init
fn (gen_vc mut GenVC) init() {
// purge repos if flag is passed
if gen_vc.options.purge {
gen_vc.purge_repos()
}
}
// regenerate
fn (gen_vc mut GenVC) generate() {
// set errors to false
gen_vc.gen_error = false
// check if gen_vc dir exists
if !os.is_dir(gen_vc.options.work_dir) {
// try create
os.mkdir(gen_vc.options.work_dir) or { panic(err) }
// still dosen't exist... we have a problem
if !os.is_dir(gen_vc.options.work_dir) {
gen_vc.logger.error('error creating directory: $gen_vc.options.work_dir')
gen_vc.gen_error = true
return
}
}
// cd to gen_vc dir
os.chdir(gen_vc.options.work_dir)
// if we are not running with the --serve flag (webhook server)
// rather than deleting and re-downloading the repo each time
// first check to see if the local v repo is behind master
// if it isn't behind theres no point continuing further
if !gen_vc.options.serve && os.is_dir(git_repo_dir_v) {
gen_vc.cmd_exec('git -C $git_repo_dir_v checkout master')
// fetch the remote repo just in case there are newer commits there
gen_vc.cmd_exec('git -C $git_repo_dir_v fetch')
git_status := gen_vc.cmd_exec('git -C $git_repo_dir_v status')
if !git_status.contains('behind') {
gen_vc.logger.warn('v repository is already up to date.')
return
}
}
// delete repos
gen_vc.purge_repos()
// clone repos
gen_vc.cmd_exec('git clone --depth 1 https://$git_repo_v $git_repo_dir_v')
gen_vc.cmd_exec('git clone --depth 1 https://$git_repo_vc $git_repo_dir_vc')
// get output of git log -1 (last commit)
git_log_v := gen_vc.cmd_exec('git -C $git_repo_dir_v log -1 --format="commit %H%nDate: %ci%nDate Unix: %ct%nSubject: %s"')
git_log_vc := gen_vc.cmd_exec('git -C $git_repo_dir_vc log -1 --format="Commit %H%nDate: %ci%nDate Unix: %ct%nSubject: %s"')
// date of last commit in each repo
ts_v := git_log_v.find_between('Date:', '\n').trim_space()
ts_vc := git_log_vc.find_between('Date:', '\n').trim_space()
// parse time as string to time.Time
last_commit_time_v := time.parse(ts_v) or {
panic(err)
}
last_commit_time_vc := time.parse(ts_vc) or {
panic(err)
}
// git dates are in users local timezone and v time.parse does not parse
// timezones at the moment, so for now get unix timestamp from output also
t_unix_v := git_log_v.find_between('Date Unix:', '\n').trim_space().int()
t_unix_vc := git_log_vc.find_between('Date Unix:', '\n').trim_space().int()
// last commit hash in v repo
last_commit_hash_v := git_log_v.find_between('commit', '\n').trim_space()
last_commit_hash_v_short := last_commit_hash_v[..7]
// subject
last_commit_subject := git_log_v.find_between('Subject:', '\n').trim_space()
// log some info
gen_vc.logger.debug('last commit time ($git_repo_v): ' + last_commit_time_v.format_ss())
gen_vc.logger.debug('last commit time ($git_repo_vc): ' + last_commit_time_vc.format_ss())
gen_vc.logger.debug('last commit hash ($git_repo_v): $last_commit_hash_v')
gen_vc.logger.debug('last commit subject ($git_repo_v): $last_commit_subject')
// if vc repo already has a newer commit than the v repo, assume it's up to date
if t_unix_vc >= t_unix_v {
gen_vc.logger.warn('vc repository is already up to date.')
return
}
// try build v for current os (linux in this case)
gen_vc.cmd_exec('make -C $git_repo_dir_v')
v_exec := '$git_repo_dir_v/v'
// check if make was successful
gen_vc.assert_file_exists_and_is_not_too_short(v_exec, err_msg_make)
// build v.c for each os
for os_name in vc_build_oses {
vc_suffix := if os_name == 'nix' { '' } else { '_${os_name[..3]}' }
v_flags := if os_name == 'nix' { '-output-cross-platform-c' } else { '-os $os_name' }
c_file := 'v${vc_suffix}.c'
// try generate .c file
gen_vc.cmd_exec('$v_exec $v_flags -o $c_file $git_repo_dir_v/cmd/v')
// check if the c file seems ok
gen_vc.assert_file_exists_and_is_not_too_short(c_file, err_msg_gen_c)
// embed the latest v commit hash into the c file
gen_vc.cmd_exec('sed -i \'1s/^/#define V_COMMIT_HASH "$last_commit_hash_v_short"\\n/\' $c_file')
// run clang-format to make the c file more readable
gen_vc.cmd_exec('clang-format -i $c_file')
// move to vc repo
gen_vc.cmd_exec('mv $c_file $git_repo_dir_vc/$c_file')
// add new .c file to local vc repo
gen_vc.cmd_exec('git -C $git_repo_dir_vc add $c_file')
}
// check if the vc repo actually changed
git_status := gen_vc.cmd_exec('git -C $git_repo_dir_vc status')
if git_status.contains('nothing to commit') {
gen_vc.logger.error('no changes to vc repo: something went wrong.')
gen_vc.gen_error = true
}
// commit changes to local vc repo
gen_vc.cmd_exec_safe('git -C $git_repo_dir_vc commit -m "[v:master] $last_commit_hash_v_short - $last_commit_subject"')
// push changes to remote vc repo
gen_vc.cmd_exec_safe('git -C $git_repo_dir_vc push https://${urllib.query_escape(git_username)}:${urllib.query_escape(git_password)}@$git_repo_vc master')
}
// only execute when dry_run option is false, otherwise just log
fn (gen_vc mut GenVC) cmd_exec_safe(cmd string) string {
return gen_vc.command_execute(cmd, gen_vc.options.dry_run)
}
// always execute command
fn (gen_vc mut GenVC) cmd_exec(cmd string) string {
return gen_vc.command_execute(cmd, false)
}
// execute command
fn (gen_vc mut GenVC) command_execute(cmd string, dry bool) string {
// if dry is true then dont execute, just log
if dry {
return gen_vc.command_execute_dry(cmd)
}
gen_vc.logger.info('cmd: $cmd')
r := os.exec(cmd) or {
gen_vc.logger.error('$err_msg_cmd_x: "$cmd" could not start.')
gen_vc.logger.error( err )
// something went wrong, better start fresh next time
gen_vc.purge_repos()
gen_vc.gen_error = true
return ''
}
if r.exit_code != 0 {
gen_vc.logger.error('$err_msg_cmd_x: "$cmd" failed.')
gen_vc.logger.error(r.output)
// something went wrong, better start fresh next time
gen_vc.purge_repos()
gen_vc.gen_error = true
return ''
}
return r.output
}
// just log cmd, dont execute
fn (gen_vc mut GenVC) command_execute_dry(cmd string) string {
gen_vc.logger.info('cmd (dry): "$cmd"')
return ''
}
// delete repo directories
fn (gen_vc mut GenVC) purge_repos() {
// delete old repos (better to be fully explicit here, since these are destructive operations)
mut repo_dir := '$gen_vc.options.work_dir/$git_repo_dir_v'
if os.is_dir(repo_dir) {
gen_vc.logger.info('purging local repo: "$repo_dir"')
gen_vc.cmd_exec('rm -rf $repo_dir')
}
repo_dir = '$gen_vc.options.work_dir/$git_repo_dir_vc'
if os.is_dir(repo_dir) {
gen_vc.logger.info('purging local repo: "$repo_dir"')
gen_vc.cmd_exec('rm -rf $repo_dir')
}
}
// check if file size is too short
fn (gen_vc mut GenVC) assert_file_exists_and_is_not_too_short(f string, emsg string){
if !os.exists(f) {
gen_vc.logger.error('$err_msg_build: $emsg .')
gen_vc.gen_error = true
return
}
fsize := os.file_size(f)
if fsize < too_short_file_limit {
gen_vc.logger.error('$err_msg_build: $f exists, but is too short: only $fsize bytes.')
gen_vc.gen_error = true
return
}
}

View File

@@ -0,0 +1,100 @@
module scripting
import os
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:-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():-4s} cmd.output:')
println('# ----------------------------------- #')
mut lnum := 1
lines := x.output.split_into_lines()
for line in lines {
println('# ${lnum:3d}: $line')
lnum++
}
println('# ----------------------------------- #')
}
}
pub fn chdir(path string) {
verbose_trace(@FN, 'cd $path')
os.chdir(path)
}
pub fn rmrf(path string) {
verbose_trace(@FN, 'rm -rf $path')
if os.exists(path) {
if os.is_dir(path) {
os.rmdir_recursive(path)
}else{
os.rm(path)
}
}
}
pub fn run(cmd string) string {
verbose_trace(@FN, cmd)
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 exit_0_status(cmd string) bool {
verbose_trace(@FN, cmd)
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) {
verbose_trace(@FN, toolcmd)
if exit_0_status('type $toolcmd') {
return
}
eprintln('Missing tool: $toolcmd')
eprintln('Please try again after you install it.')
exit(1)
}
pub fn used_tools_must_exist(tools []string) {
for t in tools {
tool_must_exist(t)
}
}
pub fn show_sizes_of_files(files []string) {
for f in files {
size := os.file_size(f)
println('${size:10d} $f')
}
}

View File

@@ -0,0 +1,272 @@
module testing
import (
os
term
benchmark
filepath
runtime
sync
)
pub struct TestSession {
pub mut:
files []string
vexe string
vargs string
failed bool
benchmark benchmark.Benchmark
ntask int // writing to this should be locked by mu.
ntask_mtx &sync.Mutex
waitgroup &sync.WaitGroup
show_ok_tests bool
}
pub fn new_test_session(vargs string) TestSession {
return TestSession{
vexe: vexe_path()
vargs: vargs
ntask: 0
ntask_mtx: sync.new_mutex()
waitgroup: sync.new_waitgroup()
show_ok_tests: !vargs.contains('-silent')
}
}
pub fn vexe_path() string {
// NB: tools extracted from v require that the VEXE
// environment variable contains the path to the v executable location.
// They are usually launched by cmd/v/simple_tool.v,
// launch_tool/1 , which provides it.
return os.getenv('VEXE')
}
pub fn (ts mut TestSession) init() {
ts.benchmark = benchmark.new_benchmark()
}
pub fn (ts mut TestSession) test() {
ts.init()
mut remaining_files := []string
for dot_relative_file in ts.files {
relative_file := dot_relative_file.replace('./', '')
file := os.realpath(relative_file)
$if windows {
if file.contains('sqlite') || file.contains('httpbin') {
continue
}
}
$if !macos {
if file.contains('customer') {
continue
}
}
$if msvc {
if file.contains('asm') {
continue
}
}
$if tinyc {
if file.contains('asm') {
continue
}
}
remaining_files << dot_relative_file
}
ts.files = remaining_files
ts.benchmark.set_total_expected_steps(remaining_files.len)
mut njobs := runtime.nr_jobs()
$if msvc {
// NB: MSVC can not be launched in parallel, without giving it
// the option /FS because it uses a shared PDB file, which should
// be locked, but that makes writing slower...
// See: https://docs.microsoft.com/en-us/cpp/build/reference/fs-force-synchronous-pdb-writes?view=vs-2019
// Instead, just run tests on 1 core for now.
njobs = 1
}
ts.waitgroup.add( njobs )
for i:=0; i < njobs; i++ {
go process_in_thread(ts)
}
ts.waitgroup.wait()
ts.benchmark.stop()
eprintln(term.h_divider('-'))
}
fn process_in_thread(ts mut TestSession){
ts.process_files()
ts.waitgroup.done()
}
fn (ts mut TestSession) process_files() {
tmpd := os.tmpdir()
show_stats := '-stats' in ts.vargs.split(' ')
mut tls_bench := benchmark.new_benchmark() // tls_bench is used to format the step messages/timings
tls_bench.set_total_expected_steps( ts.benchmark.nexpected_steps )
for {
ts.ntask_mtx.lock()
ts.ntask++
idx := ts.ntask-1
ts.ntask_mtx.unlock()
if idx >= ts.files.len { break }
tls_bench.cstep = idx
dot_relative_file := ts.files[ idx ]
relative_file := dot_relative_file.replace('./', '')
file := os.realpath(relative_file)
// Ensure that the generated binaries will be stored in the temporary folder.
// Remove them after a test passes/fails.
fname := filepath.filename(file)
generated_binary_fname := if os.user_os() == 'windows' { fname.replace('.v', '.exe') } else { fname.replace('.v', '') }
generated_binary_fpath := filepath.join(tmpd,generated_binary_fname)
if os.exists(generated_binary_fpath) {
os.rm(generated_binary_fpath)
}
mut cmd_options := [ts.vargs]
if !ts.vargs.contains('fmt') {
cmd_options << ' -o "$generated_binary_fpath"'
}
cmd := '"${ts.vexe}" ' + cmd_options.join(' ') + ' "${file}"'
// eprintln('>>> v cmd: $cmd')
ts.benchmark.step()
tls_bench.step()
if show_stats {
eprintln(term.h_divider('-'))
status := os.system(cmd)
if status == 0 {
ts.benchmark.ok()
tls_bench.ok()
}
else {
ts.failed = true
ts.benchmark.fail()
tls_bench.fail()
continue
}
}
else {
r := os.exec(cmd) or {
ts.failed = true
ts.benchmark.fail()
tls_bench.fail()
eprintln(tls_bench.step_message_fail(relative_file))
continue
}
if r.exit_code != 0 {
ts.failed = true
ts.benchmark.fail()
tls_bench.fail()
eprintln(tls_bench.step_message_fail('${relative_file}\n`$file`\n (\n$r.output\n)'))
}
else {
ts.benchmark.ok()
tls_bench.ok()
if ts.show_ok_tests {
eprintln(tls_bench.step_message_ok(relative_file))
}
}
}
if os.exists(generated_binary_fpath) {
os.rm(generated_binary_fpath)
}
}
}
pub fn vlib_should_be_present(parent_dir string) {
vlib_dir := filepath.join(parent_dir,'vlib')
if !os.is_dir(vlib_dir) {
eprintln('$vlib_dir is missing, it must be next to the V executable')
exit(1)
}
}
pub fn v_build_failing(zargs string, folder string) bool {
main_label := 'Building $folder ...'
finish_label := 'building $folder'
vexe := vexe_path()
parent_dir := filepath.dir(vexe)
vlib_should_be_present(parent_dir)
vargs := zargs.replace(vexe, '')
eheader(main_label)
eprintln('v compiler args: "$vargs"')
mut session := new_test_session(vargs)
files := os.walk_ext(filepath.join(parent_dir,folder), '.v')
mut mains := files.filter(!it.contains('modules') && !it.contains('preludes'))
$if windows {
// skip pico example on windows
// there was a bug using filter here
mut mains_filtered := []string
for file in mains {
if !file.ends_with('examples\\pico\\pico.v') {
mains_filtered << file
}
}
mains = mains_filtered
}
session.files << mains
session.test()
eprintln(session.benchmark.total_message(finish_label))
return session.failed
}
pub fn build_v_cmd_failed(cmd string) bool {
res := os.exec(cmd) or {
return true
}
if res.exit_code != 0 {
eprintln('')
eprintln(res.output)
return true
}
return false
}
pub fn building_any_v_binaries_failed() bool {
eheader('Building V binaries...')
eprintln('VFLAGS is: "' + os.getenv('VFLAGS') + '"')
vexe := testing.vexe_path()
parent_dir := filepath.dir(vexe)
testing.vlib_should_be_present(parent_dir)
os.chdir(parent_dir)
mut failed := false
v_build_commands := ['$vexe -o v_g -g cmd/v',
'$vexe -o v_prod_g -prod -g cmd/v',
'$vexe -o v_cg -cg cmd/v',
'$vexe -o v_prod_cg -prod -cg cmd/v',
'$vexe -o v_prod -prod cmd/v',
]
mut bmark := benchmark.new_benchmark()
for cmd in v_build_commands {
bmark.step()
if build_v_cmd_failed(cmd) {
bmark.fail()
failed = true
eprintln(bmark.step_message_fail('command: ${cmd} . See details above ^^^^^^^'))
eprintln('')
continue
}
bmark.ok()
eprintln(bmark.step_message_ok('command: ${cmd}'))
}
bmark.stop()
eprintln(term.h_divider('-'))
eprintln(bmark.total_message('building v binaries'))
return failed
}
pub fn eheader(msg string) {
eprintln(term.header(msg,'-'))
}
pub fn header(msg string) {
println(term.header(msg,'-'))
}

View File

@@ -0,0 +1,178 @@
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/ or cmd/v, 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
if os.exists('cmd/v') {
vgit_context.vvlocation = 'cmd/v'
} else {
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
}

120
cmd/tools/oldv.v Normal file
View File

@@ -0,0 +1,120 @@
import (
os
flag
filepath
scripting
vgit
)
const (
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
cmd/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:
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() {
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
}
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 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.arguments_description('VCOMMIT')
fp.skip_executable()
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.\n')
commits := vgit.add_common_tool_options(mut context, mut fp)
if commits.len > 0 {
context.commit_v = commits[0]
} else {
context.commit_v = scripting.run('git rev-list -n1 HEAD')
}
println('################# context.commit_v: $context.commit_v #####################')
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 context.cleanup {
scripting.rmrf(context.path_v)
scripting.rmrf(context.path_vc)
}
context.compile_oldv_if_needed()
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:-34s} exit code: ${cmdres.exit_code:-4d} result:')
println(cmdres.output)
exit(cmdres.exit_code)
}
}

View File

@@ -0,0 +1,212 @@
import (
os
flag
filepath
scripting
vgit
)
const (
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
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
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
}
}
fn (c Context) compare_versions() {
// Input is validated at this point...
// Cleanup artifacts from previous runs of this tool:
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:
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)
scripting.chdir(c.workdir)
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@',
])
println('All performance files:')
for f in perf_files {
println(' $f')
}
}
fn (c &Context) prepare_v(cdir string, commit string) {
mut cc := os.getenv('CC')
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 == 'cmd/v' {
println('Source lines of the compiler: ' + scripting.run('wc cmd/v/*.v vlib/compiler/*.v | tail -n -1'))
} else 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'))
}
}
fn (c Context) compare_v_performance(label string, commands []string) string {
println('---------------------------------------------------------------------------------')
println('Compare v performance when doing the following commands ($label):')
mut source_location_a := ''
mut source_location_b := ''
if os.exists('$c.a/cmd/v') {
source_location_a = 'cmd/v'
} else {
source_location_a = if os.exists('$c.a/v.v') { 'v.v ' } else { 'compiler/ ' }
}
if os.exists('$c.b/cmd/v') {
source_location_b = 'cmd/v'
} else {
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:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_b, '@DEBUG@', debug_option_b])
}
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 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.arguments_description('COMMIT_BEFORE [COMMIT_AFTER]')
fp.skip_executable()
fp.limit_free_args(1, 2)
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.
${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]
}
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()
}

View File

@@ -0,0 +1,28 @@
# V preludes:
The cmd/tools/preludes/ contains small v code snippets, that V uses when
compiling certain v programs. V adds the files below automatically itself.
Each file is used in different situations (see below).
NB: preludes are *NOT* intended to be used by user programs/modules.
The folder cmd/tools/preludes/ is *NOT* a v module.
## Details:
### cmd/tools/preludes/live_main.v
Used when compiling live programs. This file is used by the main executable
live program, that starts the file change monitoring thread. Each live program
needs module `os` and module `time`, in order for the background file change
monitoring thread to work properly.
### cmd/tools/preludes/live_shared.v
Used when compiling live programs, for the shared library portion of the live
programs, that is reloaded each time the code is changed.
### cmd/tools/preludes/tests_assertions.v
Used when compiling `_test.v` programs.
It specifies how failed assertions will look.
### cmd/tools/preludes/tests_with_stats.v
Used when compiling `_test.v` programs with -stats option.
It specifies how the result will appear ('assert' vs 'asserts' and so on).

View File

@@ -0,0 +1,9 @@
module main
import os
import time
const (
os_used = os.MAX_PATH
time_used = time.now()
)

View File

@@ -0,0 +1,9 @@
module main
import os
import time
const (
os_used = os.MAX_PATH
time_used = time.now()
)

View File

@@ -0,0 +1,36 @@
module main
import os
import term
// //////////////////////////////////////////////////////////////////
// / This file will get compiled as part of the main program,
// / for a _test.v file.
// / The methods defined here are called back by the test program's
// / assert statements, on each success/fail. The goal is to make
// / customizing the look & feel of the assertions results easier,
// / since it is done in normal V code, instead of in embedded C ...
// //////////////////////////////////////////////////////////////////
fn cb_assertion_failed(filename string, line int, sourceline string, funcname string) {
color_on := term.can_show_color_on_stderr()
use_relative_paths := match os.getenv('VERROR_PATHS') {
'absolute'{
false
}
else {
true}
}
final_filename := if use_relative_paths { filename } else { os.realpath(filename) }
final_funcname := funcname.replace('main__', '').replace('__', '.')
mut fail_message := 'FAILED assertion'
if color_on {
fail_message = term.bold(term.red(fail_message))
}
eprintln('$final_filename:$line: $fail_message')
eprintln('Function: $final_funcname')
eprintln('Source : $sourceline')
}
fn cb_assertion_ok(filename string, line int, sourceline string, funcname string) {
// do nothing for now on an OK assertion
// println('OK ${line:5d}|$sourceline ')
}

View File

@@ -0,0 +1,104 @@
module main
// /////////////////////////////////////////////////////////////////////
// / This file will get compiled as a part of the same module,
// / in which a given _test.v file is, when v is given -stats argument
// / The methods defined here are called back by the test program's
// / main function, generated by compiler/main.v so that customizing the
// / look & feel of the results is easy, since it is done in normal V
// / code, instead of in embedded C ...
// /////////////////////////////////////////////////////////////////////
import (
filepath
benchmark
)
const (
INNER_INDENT = ' '
)
struct BenchedTests {
mut:
oks int
fails int
test_suit_file string
step_func_name string
bench benchmark.Benchmark
}
// ///////////////////////////////////////////////////////////////////
// Called at the start of the test program produced by `v -stats file_test.v`
fn start_testing(total_number_of_tests int, vfilename string) BenchedTests {
mut b := BenchedTests{
bench: benchmark.new_benchmark()
}
b.bench.set_total_expected_steps(total_number_of_tests)
b.test_suit_file = vfilename
println('running tests in: $b.test_suit_file')
return b
}
// Called before each test_ function, defined in file_test.v
fn (b mut BenchedTests) testing_step_start(stepfunc string) {
b.step_func_name = stepfunc.replace('main__', '').replace('__', '.')
b.oks = C.g_test_oks
b.fails = C.g_test_fails
b.bench.step()
}
// Called after each test_ function, defined in file_test.v
fn (b mut BenchedTests) testing_step_end() {
ok_diff := C.g_test_oks - b.oks
fail_diff := C.g_test_fails - b.fails
// ////////////////////////////////////////////////////////////////
if ok_diff == 0 && fail_diff == 0 {
b.bench.neither_fail_nor_ok()
println(INNER_INDENT + b.bench.step_message_ok('NO asserts | ') + b.fn_name())
return
}
// ////////////////////////////////////////////////////////////////
if ok_diff > 0 {
b.bench.ok_many(ok_diff)
}
if fail_diff > 0 {
b.bench.fail_many(fail_diff)
}
// ////////////////////////////////////////////////////////////////
if ok_diff > 0 && fail_diff == 0 {
println(INNER_INDENT + b.bench.step_message_ok(nasserts(ok_diff)) + b.fn_name())
return
}
if fail_diff > 0 {
println(INNER_INDENT + b.bench.step_message_fail(nasserts(fail_diff)) + b.fn_name())
return
}
}
fn (b &BenchedTests) fn_name() string {
return b.step_func_name + '()'
}
// Called at the end of the test program produced by `v -stats file_test.v`
fn (b mut BenchedTests) end_testing() {
b.bench.stop()
println(INNER_INDENT + b.bench.total_message('running V tests in "' + filepath.filename(b.test_suit_file) + '"'))
}
// ///////////////////////////////////////////////////////////////////
fn nasserts(n int) string {
if n == 0 {
return '${n:2d} asserts | '
}
if n == 1 {
return '${n:2d} assert | '
}
if n < 10 {
return '${n:2d} asserts | '
}
if n < 100 {
return '${n:3d} asserts | '
}
if n < 1000 {
return '${n:4d} asserts | '
}
return '${n:5d} asserts | '
}

95
cmd/tools/vbin2v.v Normal file
View File

@@ -0,0 +1,95 @@
module main
import os
import flag
import filepath
const (
tool_version = '0.0.3'
tool_description = 'Converts a list of arbitrary files into a single v module file.'
)
struct Context {
mut:
files []string
prefix string
show_help bool
module_name string
}
fn (context Context) header() {
println('module ${context.module_name}')
println('')
allfiles := context.files.join(' ')
mut options := []string
if context.prefix.len > 0 {
options << '-p ${context.prefix}'
}
if context.module_name.len > 0 {
options << '-m ${context.module_name}'
}
soptions := options.join(' ')
println('// File generated by:')
println('// v bin2v ${allfiles} ${soptions}')
println('// Please, do not edit this file.')
println('// Your changes may be overwritten.')
println('')
println('const (')
}
fn (context Context) footer() {
println(')')
}
fn (context Context) file2v(file string) {
fname := filepath.filename(file)
fname_no_dots := fname.replace('.', '_')
byte_name := '${context.prefix}${fname_no_dots}'
fbytes := os.read_bytes(file) or {
eprintln('Error: $err')
return
}
fbyte := fbytes[0]
println(' ${byte_name}_len = ${fbytes.len}')
print(' ${byte_name} = [ byte(${fbyte}), \n ')
for i := 1; i < fbytes.len; i++ {
b := int(fbytes[i]).str()
print('${b:4s}, ')
if 0 == i % 16 {
print('\n ')
}
}
println('\n]!!')
println('')
}
fn main() {
mut context := Context{}
mut fp := flag.new_flag_parser(os.args[1..])
fp.application('v bin2v')
fp.version(tool_version)
fp.description(tool_description)
fp.arguments_description('FILE [FILE]...')
context.show_help = fp.bool_('help', `h`, false, 'Show this help screen.')
context.module_name = fp.string_('module', `m`, 'binary', 'Name of the generated module.\n')
context.prefix = fp.string_('prefix', `p`, '', 'A prefix put before each resource name.\n')
if (context.show_help) {
println(fp.usage())
exit(0)
}
files := fp.finalize() or {
eprintln('Error: ' + err)
exit(1)
}
real_files := files.filter(it != 'bin2v')
if real_files.len == 0 {
println(fp.usage())
exit(0)
}
context.files = real_files
context.header()
for file in real_files {
context.file2v(file)
}
context.footer()
}

View File

@@ -0,0 +1,22 @@
module main
import (
os
testing
filepath
)
fn main() {
args := os.args
args_string := args[1..].join(' ')
params := args_string.all_before('build-examples')
if testing.v_build_failing(params, 'examples'){
exit(1)
}
if testing.v_build_failing(params + '-live', filepath.join( 'examples', 'hot_reload')){
exit(1)
}
}

14
cmd/tools/vbuild-tools.v Normal file
View File

@@ -0,0 +1,14 @@
module main
import (
os
testing
)
fn main() {
args := os.args
args_string := args[1..].join(' ')
if testing.v_build_failing(args_string.all_before('build-tools'), 'cmd/tools') {
exit(1)
}
}

View File

@@ -0,0 +1,9 @@
module main
import testing
fn main() {
if testing.building_any_v_binaries_failed() {
exit(1)
}
}

55
cmd/tools/vcreate.v Normal file
View File

@@ -0,0 +1,55 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import (
os
)
struct Create {
mut:
name string
description string
}
fn cerror(e string){
eprintln('\nerror: $e')
}
fn (c Create)write_vmod() {
mut vmod := os.create('${c.name}/v.mod') or { cerror(err) exit(1) }
mut vmod_content := []string
vmod_content << '#V Project#\n'
vmod_content << 'Module {'
vmod_content << ' name: \'${c.name}\','
vmod_content << ' description: \'${c.description}\','
vmod_content << ' dependencies: []'
vmod_content << '}'
vmod.write(vmod_content.join('\n'))
}
fn (c Create)write_main() {
mut main := os.create('${c.name}/${c.name}.v') or { cerror(err) exit(2) }
mut main_content := []string
main_content << 'module main\n'
main_content << 'fn main() {'
main_content << ' println(\'Hello World !\')'
main_content << '}'
main.write(main_content.join('\n'))
}
fn main() {
mut c := Create{}
print('Choose your project name: ')
c.name = os.get_line()
print('Choose your project description: ')
c.description = os.get_line()
println('Initialising ...')
if (os.is_dir(c.name)) { cerror('folder already exists') exit(3) }
os.mkdir(c.name) or { panic(err) }
c.write_vmod()
c.write_main()
println('Complete !')
}

414
cmd/tools/vfmt.v Normal file
View File

@@ -0,0 +1,414 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import (
os
os.cmdline
filepath
compiler
v.pref
)
struct FormatOptions {
is_l bool
is_c bool
is_w bool
is_diff bool
is_verbose bool
is_all bool
is_worker bool
is_debug bool
is_noerror bool
}
const (
platform_and_file_extensions = [['windows', '_win.v', '_windows.v'],
['linux', '_lin.v', '_linux.v', '_nix.v'],
['macos', '_mac.v', '_darwin.v'],
['freebsd', '_bsd.v', '_freebsd.v'],
['solaris', '_solaris.v'],
['haiku', '_haiku.v'],
]
FORMATTED_FILE_TOKEN = '\@\@\@' + 'FORMATTED_FILE: '
)
fn main() {
toolexe := os.executable()
compiler.set_vroot_folder(filepath.dir(filepath.dir(filepath.dir(toolexe))))
args := join_flags_and_argument()
foptions := FormatOptions{
is_c: '-c' in args
is_l: '-l' in args
is_w: '-w' in args
is_diff: '-diff' in args
is_verbose: '-verbose' in args || '--verbose' in args
is_all: '-all' in args || '--all' in args
is_worker: '-worker' in args
is_debug: '-debug' in args
is_noerror: '-noerror' in args
}
if foptions.is_verbose {
eprintln('vfmt foptions: $foptions')
}
if foptions.is_worker {
// -worker should be added by a parent vfmt process.
// We launch a sub process for each file because
// the v compiler can do an early exit if it detects
// a syntax error, but we want to process ALL passed
// files if possible.
foptions.format_file(cmdline.option(args, '-worker', ''))
exit(0)
}
// we are NOT a worker at this stage, i.e. we are a parent vfmt process
possible_files := cmdline.only_non_options(cmdline.after(args, ['fmt']))
if foptions.is_verbose {
eprintln('vfmt toolexe: $toolexe')
eprintln('vfmt args: ' + os.args.str())
eprintln('vfmt env_vflags_and_os_args: ' + args.str())
eprintln('vfmt possible_files: ' + possible_files.str())
}
mut files := []string
for file in possible_files {
if !os.exists(file) {
compiler.verror('"$file" does not exist.')
}
if !file.ends_with('.v') {
compiler.verror('v fmt can only be used on .v files.\nOffending file: "$file" .')
}
files << file
}
if files.len == 0 {
usage()
exit(0)
}
mut cli_args_no_files := []string
for a in os.args {
if !a in files {
cli_args_no_files << a
}
}
mut errors := 0
for file in files {
fpath := os.realpath(file)
mut worker_command_array := cli_args_no_files.clone()
worker_command_array << ['-worker', fpath]
worker_cmd := worker_command_array.join(' ')
if foptions.is_verbose {
eprintln('vfmt worker_cmd: $worker_cmd')
}
worker_result := os.exec(worker_cmd) or {
errors++
continue
}
if worker_result.exit_code != 0 {
eprintln(worker_result.output)
if worker_result.exit_code == 1 {
eprintln('vfmt error while formatting file: $file .')
}
errors++
continue
}
if worker_result.output.len > 0 {
if worker_result.output.contains(FORMATTED_FILE_TOKEN) {
wresult := worker_result.output.split(FORMATTED_FILE_TOKEN)
formatted_warn_errs := wresult[0]
formatted_file_path := wresult[1]
foptions.post_process_file(fpath, formatted_file_path)
if formatted_warn_errs.len > 0 {
eprintln(formatted_warn_errs)
}
continue
}
}
errors++
}
if errors > 0 {
eprintln('Encountered a total of: ${errors} errors.')
if foptions.is_noerror {
exit(0)
}
exit(1)
}
}
fn (foptions &FormatOptions) format_file(file string) {
tmpfolder := os.tmpdir()
mut compiler_params := &pref.Preferences{}
target_os := file_to_target_os(file)
if target_os != '' {
compiler_params.os = pref.os_from_string(target_os)
}
mut cfile := file
mut mod_folder_parent := tmpfolder
is_test_file := file.ends_with('_test.v')
mod_name,is_module_file := file_to_mod_name_and_is_module_file(file)
use_tmp_main_program := is_module_file && !is_test_file
mod_folder := filepath.basedir(file)
if use_tmp_main_program {
// TODO: remove the need for this
// This makes a small program that imports the module,
// so that the module files will get processed by the
// vfmt implementation.
mod_folder_parent = filepath.basedir(mod_folder)
mut main_program_content := if mod_name == 'builtin' || mod_name == 'main' { 'fn main(){}\n' } else { 'import ${mod_name}\n' + 'fn main(){}\n' }
main_program_file := filepath.join(tmpfolder,'vfmt_tmp_${mod_name}_program.v')
if os.exists(main_program_file) {
os.rm(main_program_file)
}
os.write_file(main_program_file, main_program_content)
cfile = main_program_file
compiler_params.user_mod_path = mod_folder_parent
}
if !is_test_file && mod_name == 'main' {
// NB: here, file is guaranted to be a main. We do not know however
// whether it is a standalone v program, or is it a part of a bigger
// project, like vorum or vid.
cfile = get_compile_name_of_potential_v_project(cfile)
}
compiler_params.path = cfile
compiler_params.mod = mod_name
compiler_params.is_test = is_test_file
compiler_params.is_script = file.ends_with('.v') || file.ends_with('.vsh')
if foptions.is_verbose {
eprintln('vfmt format_file: file: $file')
eprintln('vfmt format_file: cfile: $cfile')
eprintln('vfmt format_file: is_test_file: $is_test_file')
eprintln('vfmt format_file: is_module_file: $is_module_file')
eprintln('vfmt format_file: mod_name: $mod_name')
eprintln('vfmt format_file: mod_folder: $mod_folder')
eprintln('vfmt format_file: mod_folder_parent: $mod_folder_parent')
eprintln('vfmt format_file: use_tmp_main_program: $use_tmp_main_program')
eprintln('vfmt format_file: compiler_params: ')
print_compiler_options( compiler_params )
eprintln('-------------------------------------------')
}
compiler_params.fill_with_defaults()
if foptions.is_verbose {
eprintln('vfmt format_file: compiler_params: AFTER fill_with_defaults() ')
print_compiler_options( compiler_params )
}
formatted_file_path := foptions.compile_file(file, compiler_params)
if use_tmp_main_program {
if !foptions.is_debug {
os.rm(cfile)
}
}
eprintln('${FORMATTED_FILE_TOKEN}${formatted_file_path}')
}
fn print_compiler_options( compiler_params &pref.Preferences ) {
eprintln(' os: ' + compiler_params.os.str() )
eprintln(' ccompiler: $compiler_params.ccompiler' )
eprintln(' mod: $compiler_params.mod ')
eprintln(' path: $compiler_params.path ')
eprintln(' out_name: $compiler_params.out_name ')
eprintln(' vroot: $compiler_params.vroot ')
eprintln(' vpath: $compiler_params.vpath ')
eprintln(' vlib_path: $compiler_params.vlib_path ')
eprintln(' out_name: $compiler_params.out_name ')
eprintln(' umpath: $compiler_params.user_mod_path ')
eprintln(' cflags: $compiler_params.cflags ')
eprintln(' is_test: $compiler_params.is_test ')
eprintln(' is_script: $compiler_params.is_script ')
}
fn (foptions &FormatOptions) post_process_file(file string, formatted_file_path string) {
if formatted_file_path.len == 0 {
return
}
if foptions.is_diff {
diff_cmd := find_working_diff_command() or {
eprintln('No working "diff" CLI command found.')
return
}
os.system('$diff_cmd --minimal --text --unified=2 --show-function-line="fn " "$file" "$formatted_file_path" ')
return
}
fc := os.read_file(file) or {
eprintln('File $file could not be read')
return
}
formatted_fc := os.read_file(formatted_file_path) or {
eprintln('File $formatted_file_path could not be read')
return
}
is_formatted_different := fc != formatted_fc
if foptions.is_c {
if is_formatted_different {
eprintln('File is not formatted: $file')
exit(2)
}
return
}
if foptions.is_l {
if is_formatted_different {
eprintln('File needs formatting: $file')
}
return
}
if foptions.is_w {
if is_formatted_different {
os.mv_by_cp(formatted_file_path, file) or {
panic(err)
}
eprintln('Reformatted file: $file')
}
else {
eprintln('Already formatted file: $file')
}
return
}
print(formatted_fc)
}
fn usage() {
print('Usage: cmd/tools/vfmt [flags] fmt path_to_source.v [path_to_other_source.v]
Formats the given V source files, and prints their formatted source to stdout.
Options:
-c check if file is already formatted.
If it is not, print filepath, and exit with code 2.
-diff display only diffs between the formatted source and the original source.
-l list files whose formatting differs from vfmt.
-w write result to (source) file(s) instead of to stdout.
')
}
fn find_working_diff_command() ?string {
for diffcmd in ['colordiff', 'diff', 'colordiff.exe', 'diff.exe'] {
p := os.exec('$diffcmd --version') or {
continue
}
if p.exit_code == 0 {
return diffcmd
}
}
return error('no working diff command found')
}
fn (foptions &FormatOptions) compile_file(file string, compiler_params &pref.Preferences) string {
if foptions.is_verbose {
eprintln('> new_v_compiler_with_args file: $file')
eprintln('> new_v_compiler_with_args compiler_params:')
print_compiler_options( compiler_params )
}
mut v := compiler.new_v(compiler_params)
v.v_fmt_file = file
if foptions.is_all {
v.v_fmt_all = true
}
v.compile()
return v.v_fmt_file_result
}
pub fn (f FormatOptions) str() string {
return 'FormatOptions{ ' + ' is_l: $f.is_l' + ' is_w: $f.is_w' + ' is_diff: $f.is_diff' + ' is_verbose: $f.is_verbose' + ' is_all: $f.is_all' + ' is_worker: $f.is_worker' + ' is_debug: $f.is_debug' + ' }'
}
fn file_to_target_os(file string) string {
for extensions in platform_and_file_extensions {
for ext in extensions {
if file.ends_with(ext) {
return extensions[0]
}
}
}
return ''
}
fn file_to_mod_name_and_is_module_file(file string) (string,bool) {
mut mod_name := 'main'
mut is_module_file := false
flines := read_source_lines(file) or {
return mod_name,is_module_file
}
for fline in flines {
line := fline.trim_space()
if line.starts_with('module ') {
if !line.starts_with('module main') {
is_module_file = true
mod_name = line.replace('module ', ' ').trim_space()
}
break
}
}
return mod_name,is_module_file
}
fn read_source_lines(file string) ?[]string {
raw_fcontent := os.read_file(file) or {
return error('can not read $file')
}
fcontent := raw_fcontent.replace('\r\n', '\n')
return fcontent.split('\n')
}
fn get_compile_name_of_potential_v_project(file string) string {
// This function get_compile_name_of_potential_v_project returns:
// a) the file's folder, if file is part of a v project
// b) the file itself, if the file is a standalone v program
pfolder := os.realpath(filepath.dir(file))
// a .v project has many 'module main' files in one folder
// if there is only one .v file, then it must be a standalone
all_files_in_pfolder := os.ls(pfolder) or {
panic(err)
}
mut vfiles := []string
for f in all_files_in_pfolder {
vf := filepath.join(pfolder,f)
if f.starts_with('.') || !f.ends_with('.v') || os.is_dir(vf) {
continue
}
vfiles << vf
}
if vfiles.len == 1 {
return file
}
// /////////////////////////////////////////////////////////////
// At this point, we know there are many .v files in the folder
// We will have to read them all, and if there are more than one
// containing `fn main` then the folder contains multiple standalone
// v programs. If only one contains `fn main` then the folder is
// a project folder, that should be compiled with `v pfolder`.
mut main_fns := 0
for f in vfiles {
slines := read_source_lines(f) or {
panic(err)
}
for line in slines {
if line.contains('fn main()') {
main_fns++
if main_fns > 1 {
return file
}
}
}
}
return pfolder
}
//TODO Move join_flags_and_argument() and non_empty() into `cmd/internal` when v.mod work correctly
//to prevent code duplication with `cmd/v` (cmd/v/flag.v)
fn join_flags_and_argument() []string {
vosargs := os.getenv('VOSARGS')
if vosargs != '' {
return non_empty(vosargs.split(' '))
}
mut args := []string
vflags := os.getenv('VFLAGS')
if vflags != '' {
args << os.args[0]
args << vflags.split(' ')
if os.args.len > 1 {
args << os.args[1..]
}
return non_empty(args)
}
return non_empty(os.args)
}
fn non_empty(arg []string) []string {
return arg.filter(it != '')
}

86
cmd/tools/vnames.v Normal file
View File

@@ -0,0 +1,86 @@
module main
import (
os
flag
strings
filepath
compiler
v.pref
)
const (
tool_version = '0.0.1'
tool_description = ' Extracts the function names declared in a v file.'
)
fn f_to_string(fmod string, f compiler.Fn) ?string {
svisibility := if f.is_public {
'public'
}else{
'private'
}
if fmod != f.v_fn_module() { return none }
if fmod == 'builtin' {
return '$svisibility\t' + f.v_fn_name()
}
return '$svisibility\t' + f.v_fn_module() + '.' + f.v_fn_name()
}
fn analyze_v_file(file string) {
println('')
hash := strings.repeat(`#`, (76 - file.len) / 2)
println('$hash $file $hash')
// main work:
mut pref := &pref.Preferences{
path: file
}
pref.fill_with_defaults()
mut v := compiler.new_v(pref)
v.add_v_files_to_compile()
for f in v.files { v.parse(f, .decl) }
fi := v.get_file_parser_index( file ) or { panic(err) }
fmod := v.parsers[fi].mod
// output:
mut fns :=[]string
for _, f in v.table.fns {
fname := f_to_string(fmod, f) or { continue }
fns << fname
}
fns.sort()
for f in fns { println(f) }
}
fn main(){
toolexe := os.executable()
compiler.set_vroot_folder(filepath.dir(filepath.dir(filepath.dir(toolexe))))
mut fp := flag.new_flag_parser(os.args)
fp.application(filepath.filename(toolexe))
fp.version( tool_version )
fp.description( tool_description )
fp.arguments_description('FILE.v/FOLDER [FILE.v/FOLDER]...')
fp.limit_free_args_to_at_least(1)
fp.skip_executable()
show_help:=fp.bool_('help', `h`, false, 'Show this help screen\n')
if( show_help ){
println( fp.usage() )
exit(0)
}
mut files := []string
locations := fp.finalize() or { eprintln('Error: ' + err) exit(1) }
for xloc in locations {
loc := os.realpath(xloc)
xfiles := if os.is_dir(loc){ os.walk_ext(loc,'.v') } else { [loc] }
filtered_files := xfiles.filter(!it.ends_with('_js.v'))
files << filtered_files
}
for file in files {
analyze_v_file(file)
}
}

496
cmd/tools/vpm.v Normal file
View File

@@ -0,0 +1,496 @@
module main
import (
net.http
os
os.cmdline
json
filepath
)
const (
default_vpm_server_urls = ['https://vpm.best', 'https://vpm.vlang.io']
valid_vpm_commands = ['help', 'search', 'install', 'update', 'remove']
excluded_dirs = ['cache', 'vlib']
supported_vcs_systems = ['git', 'hg']
supported_vcs_folders = ['.git', '.hg']
supported_vcs_update_cmds = {
'git': 'git pull --depth=1'
'hg': 'hg pull --update'
}
supported_vcs_install_cmds = {
'git': 'git clone --depth=1'
'hg': 'hg clone'
}
)
struct Mod {
id int
name string
url string
nr_downloads int
vcs string
}
struct Vmod {
mut:
name string
version string
deps []string
}
fn main() {
init_settings()
// This tool is intended to be launched by the v frontend,
// which provides the path to V inside os.getenv('VEXE')
args := os.args // args are: vpm [options] SUBCOMMAND module names
params := cmdline.only_non_options(args[1..])
verbose_println('cli params: $params')
if params.len < 1 {
vpm_help([])
exit(5)
}
vpm_command := params[0]
module_names := params[1..]
ensure_vmodules_dir_exist()
// println('module names: ') println(module_names)
match vpm_command {
'help' {
vpm_help(module_names)
}
'search' {
vpm_search(module_names)
}
'install' {
vpm_install(module_names)
}
'update' {
vpm_update(module_names)
}
'remove' {
vpm_remove(module_names)
}
else {
println('Error: you tried to run "v $vpm_command"')
println('... but the v package management tool vpm only knows about these commands:')
for validcmd in valid_vpm_commands {
println(' v $validcmd')
}
exit(3)
}
}
}
fn vpm_search(keywords []string) {
if settings.is_help {
println('Usage:')
println(' v search keyword1 [keyword2] [...]')
println(' ^^^^^^^^^^^^^^^^^ will search https://vpm.vlang.io/ for matching modules,')
println(' and will show details about them')
show_vpm_options()
exit(0)
}
if keywords.len == 0 {
println(' v search requires *at least one* keyword')
exit(2)
}
modules := get_all_modules()
joined := keywords.join(', ')
mut index := 0
for mod in modules {
// TODO for some reason .filter results in substr error, so do it manually
for k in keywords {
if !mod.contains(k) {
continue
}
if index == 0 {
println('Search results for "$joined":\n')
}
index++
mut parts := mod.split('.')
// in case the author isn't present
if parts.len == 1 {
parts << parts[0]
parts[0] = ''
}
println('${index}. ${parts[1]} by ${parts[0]} [$mod]')
break
}
}
println('\nUse "v install author.module_name" to install the module')
if index == 0 {
println('No module(s) found for "$joined"')
}
}
fn vpm_install(module_names []string) {
if settings.is_help {
println('Usage:')
println(' v install module [module] [module] [...]')
println(' ^^^^^^^^^^^^^ will install the modules you specified')
show_vpm_options()
exit(0)
}
if module_names.len == 0 {
println(' v install requires *at least one* module name')
exit(2)
}
mut errors := 0
url := get_working_server_url()
for n in module_names {
name := n.trim_space()
modurl := url + '/jsmod/$name'
r := http.get(modurl) or {
println('Http server did not respond to our request for ${modurl}.')
println('Error details: $err')
errors++
continue
}
if r.status_code == 404 {
println('Skipping module "$name", since $url reported that "$name" does not exist.')
errors++
continue
}
if r.status_code != 200 {
println('Skipping module "$name", since $url responded with $r.status_code http status code. Please try again later.')
errors++
continue
}
s := r.text
mod := json.decode(Mod,s) or {
errors++
println('Skipping module "$name", since its information is not in json format.')
continue
}
if ('' == mod.url || '' == mod.name) {
errors++
// a possible 404 error, which means a missing module?
println('Skipping module "$name", since it is missing name or url information.')
continue
}
mut vcs := mod.vcs
if vcs == '' {
vcs = supported_vcs_systems[0]
}
if !vcs in supported_vcs_systems {
errors++
println('Skipping module "$name", since it uses an unsupported VCS {$vcs} .')
continue
}
final_module_path := os.realpath(filepath.join(settings.vmodules_path,mod.name.replace('.', os.path_separator)))
if os.exists(final_module_path) {
vpm_update([name])
continue
}
println('Installing module "$name" from $mod.url to $final_module_path ...')
vcs_install_cmd := supported_vcs_install_cmds[vcs]
cmd := '${vcs_install_cmd} "${mod.url}" "${final_module_path}"'
verbose_println(' command: $cmd')
cmdres := os.exec(cmd) or {
errors++
println('Could not install module "$name" to "$final_module_path" .')
verbose_println('Error command: $cmd')
verbose_println('Error details: $err')
continue
}
if cmdres.exit_code != 0 {
errors++
println('Failed installing module "$name" to "$final_module_path" .')
verbose_println('Failed command: ${cmd}')
verbose_println('Failed command output:\n${cmdres.output}')
continue
}
resolve_dependencies(name, final_module_path, module_names)
}
if errors > 0 {
exit(1)
}
}
fn vpm_update(m []string) {
mut module_names := m
if settings.is_help {
println('Usage: ')
println(' a) v update module [module] [module] [...]')
println(' ^^^^^^^^^^^^ will update the listed modules to their latest versions')
println(' b) v update')
println(' ^^^^^^^^^^^^ will update ALL installed modules to their latest versions')
show_vpm_options()
exit(0)
}
if module_names.len == 0 {
module_names = get_installed_modules()
}
mut errors := 0
for name in module_names {
final_module_path := valid_final_path_of_existing_module(name) or {
continue
}
os.chdir(final_module_path)
println('Updating module "$name"...')
verbose_println(' work folder: $final_module_path')
vcs := vcs_used_in_dir(final_module_path) or {
continue
}
vcs_cmd := supported_vcs_update_cmds[vcs[0]]
verbose_println(' command: $vcs_cmd')
vcs_res := os.exec('${vcs_cmd}') or {
errors++
println('Could not update module "$name".')
verbose_println('Error command: ${vcs_cmd}')
verbose_println('Error details:\n$err')
continue
}
if vcs_res.exit_code != 0 {
errors++
println('Failed updating module "${name}".')
verbose_println('Failed command: ${vcs_cmd}')
verbose_println('Failed details:\n${vcs_res.output}')
continue
}
resolve_dependencies(name, final_module_path, module_names)
}
if errors > 0 {
exit(1)
}
}
fn vpm_remove(module_names []string) {
if settings.is_help {
println('Usage: ')
println(' a) v remove module [module] [module] [...]')
println(' ^^^^^^^^^^^^ will remove the listed modules')
println(' b) v remove')
println(' ^^^^^^^^^^^^ will remove ALL installed modules')
show_vpm_options()
exit(0)
}
if module_names.len == 0 {
println(' v update requires *at least one* module name')
exit(2)
}
for name in module_names {
final_module_path := valid_final_path_of_existing_module(name) or {
continue
}
println('Removing module "$name"...')
verbose_println('removing folder $final_module_path')
os.rmdir_recursive(final_module_path)
// delete author directory if it is empty
author := name.split('.')[0]
author_dir := os.realpath(filepath.join(settings.vmodules_path,author))
if os.is_dir_empty(author_dir) {
verbose_println('removing author folder $author_dir')
os.rmdir(author_dir)
}
}
}
fn valid_final_path_of_existing_module(name string) ?string {
name_of_vmodules_folder := filepath.join(settings.vmodules_path,name.replace('.', os.path_separator))
final_module_path := os.realpath(name_of_vmodules_folder)
if !os.exists(final_module_path) {
println('No module with name "$name" exists at $name_of_vmodules_folder')
return none
}
if !os.is_dir(final_module_path) {
println('Skipping "$name_of_vmodules_folder", since it is not a folder.')
return none
}
vcs_used_in_dir(final_module_path) or {
println('Skipping "$name_of_vmodules_folder", since it does not use a supported vcs.')
return none
}
return final_module_path
}
fn ensure_vmodules_dir_exist() {
if !os.is_dir(settings.vmodules_path) {
println('Creating $settings.vmodules_path/ ...')
os.mkdir(settings.vmodules_path) or {
panic(err)
}
}
}
fn vpm_help(module_names []string) {
println('Usage:')
println(' a) v install module [module] [module] [...]')
println(' b) v update [module] [...]')
println(' c) v remove [module] [...]')
println(' d) v search keyword1 [keyword2] [...]')
println('')
println(' You can also pass -h or --help after each vpm command from the above, to see more details about it.')
}
fn vcs_used_in_dir(dir string) ?[]string {
mut vcs := []string
for repo_subfolder in supported_vcs_folders {
checked_folder := os.realpath(filepath.join(dir,repo_subfolder))
if os.is_dir(checked_folder) {
vcs << repo_subfolder.replace('.', '')
}
}
if vcs.len == 0 {
return none
}
return vcs
}
fn get_installed_modules() []string {
dirs := os.ls(settings.vmodules_path) or {
return []
}
mut modules := []string
for dir in dirs {
adir := filepath.join(settings.vmodules_path,dir)
if dir in excluded_dirs || !os.is_dir(adir) {
continue
}
author := dir
mods := os.ls(adir) or {
continue
}
for m in mods {
vcs_used_in_dir(filepath.join(adir,m)) or {
continue
}
modules << '${author}.$m'
}
}
return modules
}
fn get_all_modules() []string {
url := get_working_server_url()
r := http.get(url) or {
panic(err)
}
if r.status_code != 200 {
println('Failed to search vpm.best. Status code: $r.status_code')
exit(1)
}
s := r.text
mut read_len := 0
mut modules := []string
for read_len < s.len {
mut start_token := '<a href="/mod'
end_token := '</a>'
// get the start index of the module entry
mut start_index := s.index_after(start_token, read_len)
if start_index == -1 {
break
}
// get the index of the end of anchor (a) opening tag
// we use the previous start_index to make sure we are getting a module and not just a random 'a' tag
start_token = '">'
start_index = s.index_after(start_token, start_index) + start_token.len
// get the index of the end of module entry
end_index := s.index_after(end_token, start_index)
if end_index == -1 {
break
}
modules << s[start_index..end_index]
read_len = end_index
if read_len >= s.len {
break
}
}
return modules
}
fn resolve_dependencies(name, module_path string, module_names []string) {
vmod_path := filepath.join(module_path,'v.mod')
if !os.exists(vmod_path) {
return
}
data := os.read_file(vmod_path) or {
return
}
vmod := parse_vmod(data)
mut deps := []string
// filter out dependencies that were already specified by the user
for d in vmod.deps {
if !(d in module_names) {
deps << d
}
}
if deps.len > 0 {
println('Resolving ${deps.len} dependencies for module "$name"...')
verbose_println('Found dependencies: $deps')
vpm_install(deps)
}
}
fn parse_vmod(data string) Vmod {
keys := ['name', 'version', 'deps']
mut m := {
'name': '',
'version': '',
'deps': ''
}
for key in keys {
mut key_index := data.index('$key:') or {
continue
}
key_index += key.len + 1
m[key] = data[key_index..data.index_after('\n', key_index)].trim_space().replace("'", '').replace('[', '').replace(']', '')
}
mut vmod := Vmod{}
vmod.name = m['name']
vmod.version = m['version']
if m['deps'].len > 0 {
vmod.deps = m['deps'].split(',')
}
return vmod
}
fn get_working_server_url() string {
server_urls := if settings.server_urls.len > 0 { settings.server_urls } else { default_vpm_server_urls }
for url in server_urls {
verbose_println('Trying server url: $url')
http.head(url) or {
verbose_println(' $url failed.')
continue
}
return url
}
panic('No responding vpm server found. Please check your network connectivity and try again later.')
}
// settings context:
struct VpmSettings {
mut:
is_help bool
is_verbose bool
server_urls []string
vmodules_path string
}
const (
settings = &VpmSettings{}
)
fn init_settings() {
mut s := &VpmSettings(0)
unsafe{
s = settings
}
s.is_help = '-h' in os.args || '--help' in os.args || 'help' in os.args
s.is_verbose = '-verbose' in os.args || '--verbose' in os.args
s.server_urls = cmdline.many_values(os.args, '-server-url')
s.vmodules_path = os.home_dir() + '.vmodules'
}
fn show_vpm_options() {
println('Options:')
println(' -help - Show usage info')
println(' -verbose - Print more details about the performed operation')
println(' -server-url - When doing network operations, use this vpm server. Can be given multiple times.')
}
fn verbose_println(s string) {
if settings.is_verbose {
println(s)
}
}

245
cmd/tools/vrepl.v Normal file
View File

@@ -0,0 +1,245 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import (
os
term
readline
os.cmdline
filepath
)
struct Repl {
mut:
indent int
in_func bool
line string
lines []string
temp_lines []string
functions_name []string
functions []string
}
fn (r mut Repl) checks() bool {
mut in_string := false
mut is_cut := false
was_indent := r.indent > 0
for i := 0; i < r.line.len; i++ {
if r.line[i] == `\'` && (i == 0 || r.line[i - 1] != `\\`) {
in_string = !in_string
}
if r.line[i] == `{` && !in_string {
r.line = r.line[..i + 1] + '\n' + r.line[i + 1..]
is_cut = true
i++
r.indent++
}
if r.line[i] == `}` && !in_string {
r.line = r.line[..i] + '\n' + r.line[i..]
is_cut = true
i++
r.indent--
if r.indent == 0 {
r.in_func = false
}
}
if i + 2 < r.line.len && r.indent == 0 && r.line[i + 1] == `f` && r.line[i + 2] == `n` {
r.in_func = true
}
}
return r.in_func || (was_indent && r.indent <= 0) || r.indent > 0 || is_cut
}
fn (r &Repl) function_call(line string) bool {
for function in r.functions_name {
if line.starts_with(function) {
return true
}
}
return false
}
pub fn repl_help() {
version := v_version()
println(version)
println('
help Displays this information.
Ctrl-C, Ctrl-D, exit Exits the REPL.
clear Clears the screen.
')
}
pub fn run_repl(workdir string, vrepl_prefix string) []string {
version := v_version()
println(version)
println('Use Ctrl-C or `exit` to exit')
file := filepath.join( workdir, '.${vrepl_prefix}vrepl.v' )
temp_file := filepath.join( workdir, '.${vrepl_prefix}vrepl_temp.v')
mut prompt := '>>> '
defer {
os.rm(file)
os.rm(temp_file)
os.rm(file[..file.len - 2])
os.rm(temp_file[..temp_file.len - 2])
}
mut r := Repl{}
mut readline := readline.Readline{}
vexe := os.getenv('VEXE')
for {
if r.indent == 0 {
prompt = '>>> '
}
else {
prompt = '... '
}
mut line := readline.read_line(prompt) or {
break
}
if line.trim_space() == '' && line.ends_with('\n') {
continue
}
line = line.trim_space()
if line.len <= -1 || line == '' || line == 'exit' {
break
}
r.line = line
if r.line == '\n' {
continue
}
if r.line == 'clear' {
term.erase_display('2')
continue
}
if r.line == 'help' {
repl_help()
continue
}
if r.line.starts_with('fn') {
r.in_func = true
r.functions_name << r.line.all_after('fn').all_before('(').trim_space()
}
was_func := r.in_func
if r.checks() {
for rline in r.line.split('\n') {
if r.in_func || was_func {
r.functions << rline
}
else {
r.temp_lines << rline
}
}
if r.indent > 0 {
continue
}
r.line = ''
}
// Save the source only if the user is printing something,
// but don't add this print call to the `lines` array,
// so that it doesn't get called during the next print.
if r.line.starts_with('print') {
source_code := r.functions.join('\n') + r.lines.join('\n') + '\n' + r.line
os.write_file(file, source_code)
s := os.exec('"$vexe" run $file -repl') or {
rerror(err)
return []
}
print_output(s)
}
else {
mut temp_line := r.line
mut temp_flag := false
func_call := r.function_call(r.line)
if !(
r.line.contains(' ') ||
r.line.contains(':') ||
r.line.contains('=') ||
r.line.contains(',') ||
r.line.ends_with('++') ||
r.line.ends_with('--') ||
r.line == '') && !func_call {
temp_line = 'println($r.line)'
temp_flag = true
}
temp_source_code := r.functions.join('\n') + r.lines.join('\n') + '\n' + r.temp_lines.join('\n') + '\n' + temp_line
os.write_file(temp_file, temp_source_code)
s := os.exec('"$vexe" run $temp_file -repl') or {
println("SDFSDF")
rerror(err)
return []
}
if !func_call && s.exit_code == 0 && !temp_flag {
for r.temp_lines.len > 0 {
if !r.temp_lines[0].starts_with('print') {
r.lines << r.temp_lines[0]
}
r.temp_lines.delete(0)
}
r.lines << r.line
}
else {
for r.temp_lines.len > 0 {
r.temp_lines.delete(0)
}
}
print_output(s)
}
}
return r.lines
}
fn print_output(s os.Result) {
lines := s.output.split('\n')
for line in lines {
if line.contains('.vrepl_temp.v:') {
// Hide the temporary file name
sline := line.all_after('.vrepl_temp.v:')
idx := sline.index(' ') or {
println(sline)
return
}
println(sline[idx+1..])
} else if line.contains('.vrepl.v:') {
// Ensure that .vrepl.v: is at the start, ignore the path
// This is needed to have stable .repl tests.
idx := line.index('.vrepl.v:') or { return }
println(line[idx..])
} else {
println(line)
}
}
}
fn main() {
// Support for the parameters replfolder and replprefix is needed
// so that the repl can be launched in parallel by several different
// threads by the REPL test runner.
args := cmdline.after(os.args, ['repl'])
replfolder := os.realpath( cmdline.option(args, '-replfolder', '.') )
replprefix := cmdline.option(args, '-replprefix', 'noprefix.')
os.chdir( replfolder )
if !os.exists(os.getenv('VEXE')) {
println('Usage:')
println(' VEXE=vexepath vrepl\n')
println(' ... where vexepath is the full path to the v executable file')
return
}
run_repl( replfolder, replprefix )
}
pub fn rerror(s string) {
println('V repl error: $s')
os.flush_stdout()
exit(1)
}
fn v_version() string {
vexe := os.getenv('VEXE')
vversion_res := os.exec('$vexe --version') or { panic('"$vexe --version" is not working') }
return vversion_res.output
}

View File

@@ -0,0 +1,73 @@
module main
import (
os
testing
benchmark
filepath
)
pub const (
v_modules_path = os.home_dir() + '.vmodules'
)
fn main() {
args := os.args
args_string := args[1..].join(' ')
v_test_compiler(args_string.all_before('test-compiler'))
}
fn v_test_compiler(vargs string) {
vexe := testing.vexe_path()
parent_dir := filepath.dir(vexe)
testing.vlib_should_be_present(parent_dir)
// Changing the current directory is needed for some of the compiler tests,
// compiler/tests/local_test.v and compiler/tests/repl/repl_test.v
os.chdir(parent_dir)
/*
if !os.exists(parent_dir + '/v.v') {
eprintln('v.v is missing, it must be next to the V executable')
exit(1)
}
*/
// Make sure v.c can be compiled without warnings
$if macos {
if os.exists('/cmd/v') {
os.system('$vexe -o v.c cmd/v')
if os.system('cc -Werror v.c') != 0 {
eprintln('cc failed to build v.c without warnings')
exit(1)
}
eprintln('v.c can be compiled without warnings. This is good :)')
}
}
building_tools_failed := testing.v_build_failing(vargs, 'cmd/tools')
eprintln('')
testing.eheader('Testing all _test.v files...')
mut compiler_test_session := testing.new_test_session(vargs)
compiler_test_session.files << os.walk_ext(parent_dir, '_test.v')
compiler_test_session.test()
eprintln(compiler_test_session.benchmark.total_message('running V tests'))
eprintln('')
building_examples_failed := testing.v_build_failing(vargs, 'examples')
eprintln('')
building_live_failed := testing.v_build_failing(vargs + '-live', filepath.join('examples','hot_reload'))
eprintln('')
v_module_install_cmd := '$vexe install nedpals.args'
eprintln('')
testing.eheader('Installing a v module with: $v_module_install_cmd')
mut vmark := benchmark.new_benchmark()
ret := os.system(v_module_install_cmd)
if ret != 0 {
eprintln('failed to run v install')
}
if !os.exists(v_modules_path + '/nedpals/args') {
eprintln('v failed to install a test module')
}
vmark.stop()
eprintln('Installing a v module took: ' + vmark.total_duration().str() + 'ms')
if building_tools_failed || compiler_test_session.failed || building_examples_failed || building_live_failed {
exit(1)
}
}

71
cmd/tools/vtest-fmt.v Normal file
View File

@@ -0,0 +1,71 @@
module main
import (
os
testing
)
const (
known_failing_exceptions = ['./examples/vweb/vweb_example.v',
'./cmd/tools/gen_vc.v',
'./cmd/tools/modules/vgit/vgit.v', // generics
'./cmd/tools/preludes/live_main.v',
'./cmd/tools/preludes/live_shared.v',
'./cmd/tools/preludes/tests_assertions.v',
'./cmd/tools/preludes/tests_with_stats.v',
'./cmd/tools/performance_compare.v', // generics
'./cmd/tools/oldv.v', // generics
'./tutorials/code/blog/article.v',
'./tutorials/code/blog/blog.v',
'./vlib/arrays/arrays.v',
'./vlib/arrays/arrays_test.v',
'./vlib/builtin/js/hashmap.v',
'./vlib/compiler/tests/fn_variadic_test.v',
'./vlib/compiler/tests/generic_test.v',
'./vlib/crypto/aes/aes.v',
'./vlib/crypto/aes/aes_cbc.v',
'./vlib/crypto/aes/block_generic.v',
'./vlib/crypto/aes/const.v',
'./vlib/crypto/aes/cypher_generic.v',
'./vlib/crypto/rc4/rc4.v',
'./vlib/eventbus/eventbus_test.v',
'./vlib/os/bare/bare_example_linux.v',
'./vlib/szip/szip.v',
'./vlib/uiold/examples/users_gui/users.v',
'./vlib/vweb/assets/assets.v',
'./vlib/vweb/vweb.v',
]
)
fn main() {
args := os.args
args_string := args[1..].join(' ')
v_test_formatting(args_string.all_before('test-fmt'))
}
fn v_test_formatting(vargs string) {
all_v_files := v_files()
testing.eheader('Run "v fmt" over all .v files')
mut vfmt_test_session := testing.new_test_session('$vargs fmt -worker')
vfmt_test_session.files << all_v_files
vfmt_test_session.test()
eprintln(vfmt_test_session.benchmark.total_message('running vfmt over V files'))
if vfmt_test_session.benchmark.nfail > 0 {
panic('\nWARNING: v fmt failed ${vfmt_test_session.benchmark.nfail} times.\n')
}
}
fn v_files() []string {
mut files_that_can_be_formatted := []string
all_test_files := os.walk_ext('.', '.v')
for tfile in all_test_files {
if tfile in known_failing_exceptions {
continue
}
if tfile.starts_with('./vlib/v/cgen/tests') {
continue
}
files_that_can_be_formatted << tfile
}
return files_that_can_be_formatted
}

56
cmd/tools/vtest.v Normal file
View File

@@ -0,0 +1,56 @@
module main
import (
os
os.cmdline
testing
)
pub fn main() {
args := os.args
if args.last() == 'test' {
println('Usage:')
println(' A)')
println(' v test folder/ : run all v tests in the given folder.')
println(' v -stats test folder/ : the same, but print more stats.')
println(' B)')
println(' v test file_test.v : run test functions in a given test file.')
println(' v -stats test file_test.v : as above, but with more stats.')
println(' NB: you can also give many and mixed folder/ file_test.v arguments after test.')
println('')
return
}
args_to_executable := args[1..]
args_before := cmdline.before(args_to_executable, ['test'])
args_after := cmdline.after(args_to_executable, ['test'])
if args_after.join(' ') == 'v' {
eprintln('`v test v` has been deprecated.')
eprintln('Use `v test-compiler` instead.')
exit(1)
}
mut ts := testing.new_test_session(args_before.join(' '))
for targ in args_after {
if os.exists(targ) && targ.ends_with('_test.v') {
ts.files << targ
continue
}
if os.is_dir(targ) {
// Fetch all tests from the directory
ts.files << os.walk_ext( targ.trim_right(os.path_separator), '_test.v')
continue
}
println('Unrecognized test file $targ .')
}
testing.header('Testing...')
ts.test()
println( ts.benchmark.total_message('running V _test.v files') )
if ts.failed {
exit(1)
}
}

26
cmd/tools/vup.v Normal file
View File

@@ -0,0 +1,26 @@
module main
import (
os
filepath
)
fn main() {
println('Updating V...')
vroot := filepath.dir(os.getenv('VEXE'))
os.chdir(vroot)
s := os.exec('git -C "$vroot" pull --rebase origin master') or { panic(err) }
println(s.output)
$if windows {
v_backup_file := '$vroot/v_old.exe'
if os.exists( v_backup_file ) {
os.rm( v_backup_file )
}
os.mv('$vroot/v.exe', v_backup_file)
s2 := os.exec('"$vroot/make.bat"') or { panic(err) }
println(s2.output)
} $else {
s2 := os.exec('make -C "$vroot"') or { panic(err) }
println(s2.output)
}
}

67
cmd/v/compile.v Normal file
View File

@@ -0,0 +1,67 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import (
benchmark
os
os.cmdline
)
fn compile(command string, args []string) {
// Construct the V object from command line arguments
mut v := new_v(args)
if v.pref.is_verbose {
println(args)
}
if command == 'run' {
// always recompile for now, too error prone to skip recompilation otherwise
// for example for -repl usage, especially when piping lines to v
v.compile()
run_compiled_executable_and_exit(v, args)
}
mut tmark := benchmark.new_benchmark()
if v.pref.x64 {
v.compile_x64()
}
else if v.pref.v2 {
v.compile2()
}
else {
v.compile()
}
if v.pref.is_stats {
tmark.stop()
println('compilation took: ' + tmark.total_duration().str() + 'ms')
}
if v.pref.is_test {
run_compiled_executable_and_exit(v, args)
}
v.finalize_compilation()
}
pub fn run_compiled_executable_and_exit(v &compiler.V, args []string) {
if v.pref.is_verbose {
println('============ running $v.pref.out_name ============')
}
mut cmd := '"${v.pref.out_name}"'
args_after_no_options := cmdline.only_non_options( cmdline.after(args,['run','test']) )
if args_after_no_options.len > 1 {
cmd += ' ' + args_after_no_options[1..].join(' ')
}
if v.pref.is_test {
ret := os.system(cmd)
if ret != 0 {
exit(1)
}
}
if v.pref.is_run {
ret := os.system(cmd)
// TODO: make the runner wrapping as transparent as possible
// (i.e. use execve when implemented). For now though, the runner
// just returns the same exit code as the child process.
exit(ret)
}
exit(0)
}

229
cmd/v/compile_options.v Normal file
View File

@@ -0,0 +1,229 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import (
compiler
filepath
os
os.cmdline
v.pref
)
//TODO Cleanup this file. This file ended up like a dump for functions that do not belong in `compiler`.
//Maybe restructure the functions below into different V files
pub fn new_v(args []string) &compiler.V {
// Create modules dirs if they are missing
if !os.is_dir(compiler.v_modules_path) {
os.mkdir(compiler.v_modules_path)or{
panic(err)
}
os.mkdir('$compiler.v_modules_path${os.path_separator}cache')or{
panic(err)
}
}
vroot := filepath.dir(vexe_path())
// optional, custom modules search path
user_mod_path := cmdline.option(args, '-user_mod_path', '')
vlib_path := cmdline.option(args, '-vlib-path', '')
vpath := cmdline.option(args, '-vpath', '')
target_os := cmdline.option(args, '-os', '')
if target_os == 'msvc' {
// notice that `-os msvc` became `-cc msvc`
println('V error: use the flag `-cc msvc` to build using msvc')
os.flush_stdout()
exit(1)
}
mut out_name := cmdline.option(args, '-o', '')
mut dir := args.last()
if 'run' in args {
args_after_run := cmdline.only_non_options( cmdline.after(args,['run']) )
dir = if args_after_run.len>0 { args_after_run[0] } else { '' }
}
if dir == 'v.v' {
println('looks like you are trying to build V with an old command')
println('use `v -o v cmd/v` instead of `v -o v v.v`')
exit(1)
}
if dir.ends_with(os.path_separator) {
dir = dir.all_before_last(os.path_separator)
}
if dir.starts_with('.$os.path_separator') {
dir = dir[2..]
}
if args.len < 2 {
dir = ''
}
// build mode
mut build_mode := pref.BuildMode.default_mode
mut mod := ''
joined_args := args.join(' ')
if joined_args.contains('build module ') {
build_mode = .build_module
os.chdir(vroot)
// v build module ~/v/os => os.o
mod_path := if dir.contains('vlib') { dir.all_after('vlib' + os.path_separator) } else if dir.starts_with('.\\') || dir.starts_with('./') { dir[2..] } else if dir.starts_with(os.path_separator) { dir.all_after(os.path_separator) } else { dir }
mod = mod_path.replace(os.path_separator, '.')
println('Building module "${mod}" (dir="$dir")...')
// out_name = '$TmpPath/vlib/${base}.o'
if !out_name.ends_with('.c') {
out_name = mod
}
// Cross compiling? Use separate dirs for each os
/*
if target_os != os.user_os() {
os.mkdir('$TmpPath/vlib/$target_os') or { panic(err) }
out_name = '$TmpPath/vlib/$target_os/${base}.o'
println('target_os=$target_os user_os=${os.user_os()}')
println('!Cross compiling $out_name')
}
*/
}
// `v -o dir/exec`, create "dir/" if it doesn't exist
if out_name.contains(os.path_separator) {
d := out_name.all_before_last(os.path_separator)
if !os.is_dir(d) {
println('creating a new directory "$d"')
os.mkdir(d)or{
panic(err)
}
}
}
// println('VROOT=$vroot')
cflags := cmdline.many_values(args, '-cflags').join(' ')
defines := cmdline.many_values(args, '-d')
compile_defines, compile_defines_all := parse_defines( defines )
rdir := os.realpath(dir)
rdir_name := filepath.filename(rdir)
if '-bare' in args {
println('V error: use -freestanding instead of -bare')
os.flush_stdout()
exit(1)
}
is_repl := '-repl' in args
ccompiler := cmdline.option(args, '-cc', '')
mut pref := &pref.Preferences{
os: pref.os_from_string(target_os)
is_so: '-shared' in args
is_solive: '-solive' in args
is_prod: '-prod' in args
is_verbose: '-verbose' in args || '--verbose' in args
is_debug: '-g' in args || '-cg' in args
is_vlines: '-g' in args && !('-cg' in args)
is_keep_c: '-keep_c' in args
is_pretty_c: '-pretty_c' in args
is_cache: '-cache' in args
is_stats: '-stats' in args
obfuscate: '-obf' in args
is_prof: '-prof' in args
is_live: '-live' in args
sanitize: '-sanitize' in args
// nofmt: '-nofmt' in args
show_c_cmd: '-show_c_cmd' in args
translated: 'translated' in args
is_run: 'run' in args
autofree: '-autofree' in args
compress: '-compress' in args
enable_globals: '--enable-globals' in args
fast: '-fast' in args
is_bare: '-freestanding' in args
x64: '-x64' in args
output_cross_c: '-output-cross-platform-c' in args
prealloc: '-prealloc' in args
is_repl: is_repl
build_mode: build_mode
cflags: cflags
ccompiler: ccompiler
building_v: !is_repl && (rdir_name == 'compiler' || rdir_name == 'v.v' || rdir_name == 'vfmt.v' || rdir_name == 'cmd/v' || dir.contains('vlib'))
// is_fmt: comptime_define == 'vfmt'
user_mod_path: user_mod_path
vlib_path: vlib_path
vpath: vpath
v2: '-v2' in args
vroot: vroot
out_name: out_name
path: dir
compile_defines: compile_defines
compile_defines_all: compile_defines_all
mod: mod
}
if pref.is_verbose || pref.is_debug {
println('C compiler=$pref.ccompiler')
}
$if !linux {
if pref.is_bare && !out_name.ends_with('.c') {
println('V error: -freestanding only works on Linux for now')
os.flush_stdout()
exit(1)
}
}
pref.fill_with_defaults()
// v.exe's parent directory should contain vlib
if !os.is_dir(pref.vlib_path) || !os.is_dir(pref.vlib_path + os.path_separator + 'builtin') {
// println('vlib not found, downloading it...')
/*
ret := os.system('git clone --depth=1 https://github.com/vlang/v .')
if ret != 0 {
println('failed to `git clone` vlib')
println('make sure you are online and have git installed')
exit(1)
}
*/
println('vlib not found. It should be next to the V executable.')
println('Go to https://vlang.io to install V.')
println('(os.executable=${os.executable()} vlib_path=$pref.vlib_path vexe_path=${vexe_path()}')
exit(1)
}
if pref.is_script && !os.exists(dir) {
println('`$dir` does not exist')
exit(1)
}
return compiler.new_v(pref)
}
fn find_c_compiler_thirdparty_options(args []string) string {
mut cflags := cmdline.many_values(args,'-cflags')
$if !windows {
cflags << '-fPIC'
}
if '-m32' in args {
cflags << '-m32'
}
return cflags.join(' ')
}
fn parse_defines(defines []string) ([]string,[]string) {
// '-d abc -d xyz=1 -d qwe=0' should produce:
// compile_defines: ['abc','xyz']
// compile_defines_all ['abc','xyz','qwe']
mut compile_defines := []string
mut compile_defines_all := []string
for dfn in defines {
dfn_parts := dfn.split('=')
if dfn_parts.len == 1 {
compile_defines << dfn
compile_defines_all << dfn
continue
}
if dfn_parts.len == 2 {
compile_defines_all << dfn_parts[0]
if dfn_parts[1] == '1' {
compile_defines << dfn_parts[0]
}
}
}
return compile_defines, compile_defines_all
}

78
cmd/v/flag.v Normal file
View File

@@ -0,0 +1,78 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import os
const (
//list_of_flags contains a list of flags where an argument is expected past it.
list_of_flags = [
'-o', '-os', '-cc', '-cflags', '-d'
]
)
fn get_basic_command_and_option(args []string) (string, []string) {
mut option := []string
for i, arg in args {
if i == 0 {
//Skip executable
continue
}
if arg == '--' {
//End of list of options. The next one is the command.
if i+1 < os.args.len {
return os.args[i+1], option
}
//There's no option past this
return '', option
}
if arg in list_of_flags {
i++
continue
}
if arg[0] == `-` {
option << arg
continue
}
//It's not a flag. We did not skip it. It's a command.
return arg, option
}
//There's no arguments that were not part of a flag.
return '', option
}
fn non_empty(arg []string) []string {
return arg.filter(it != '')
}
fn join_flags_and_argument() []string {
vosargs := os.getenv('VOSARGS')
if vosargs != '' {
return non_empty(vosargs.split(' '))
}
mut args := []string
vflags := os.getenv('VFLAGS')
if vflags != '' {
args << os.args[0]
args << vflags.split(' ')
if os.args.len > 1 {
args << os.args[1..]
}
return non_empty(args)
}
return non_empty(os.args)
}
fn vexe_path() string {
vexe := os.getenv('VEXE')
if vexe != '' {
return vexe
}
real_vexe_path := os.realpath(os.executable())
os.setenv('VEXE', real_vexe_path, true)
return real_vexe_path
}

101
cmd/v/help.v Normal file
View File

@@ -0,0 +1,101 @@
module main
const (
help_text = 'Usage: v [options/commands] [file.v | directory]
To run V in REPL mode, run V without any arguments.
To compile a directory/file, pass it as the only argument.
To run a directory/file, use `v run [file.v | directory]`. V will compile and run it for you.
This help message is only intended to be a quick start guide. For a comprehensive help message, use `v help --verbose`.'
verbose_help_text = 'Usage: v [options/commands] [file.v | directory]
When V is run without any arguments, it is run in REPL mode.
When given a .v file, it will be compiled. The executable will have the
same name as the input .v file: `v foo.v` produces `./foo` on *nix systems,
`foo.exe` on Windows.
You can use -o to specify a different output executable\'s name.
When given a directory, all .v files contained in it will be compiled as
part of a single main module.
By default the executable will have the same name as the directory.
To compile all V files in current directory, run `v .`
Any file ending in _test.v, will be treated as a test.
It will be compiled and run, evaluating the assert statements in every
function named test_xxx.
You can put common options inside an environment variable named VFLAGS, so that
you don\'t have to repeat them.
You can set it like this: `export VFLAGS="-cc clang -debug"` on *nix,
`set VFLAGS=-cc msvc` on Windows.
V respects the TMPDIR environment variable, and will put .tmp.c files in TMPDIR/v/ .
If you have not set it, a suitable platform specific folder (like /tmp) will be used.
Options/commands:
-h, help Display this information.
-o <file> Write output to <file>.
-o <file>.c Produce C source without compiling it.
-o <file>.js Produce JavaScript source.
-prod Build an optimized executable.
-v, version Display compiler version and git hash of the compiler source.
-verbose Produce a verbose log about what the compiler is doing, where it seeks for files and so on.
-live Enable hot code reloading (required by functions marked with [live]).
-os <OS> Produce an executable for the selected OS.
OS can be linux, mac, windows, msvc.
Use msvc if you want to use the MSVC compiler on Windows.
-shared Build a shared library.
-stats Show additional stats when compiling/running tests. Try `v -stats test .`
-cache Turn on usage of the precompiled module cache.
It very significantly speeds up secondary compilations.
-obf Obfuscate the resulting binary.
-compress Compress the resulting binary.
- Shorthand for `v repl`.
Options for debugging/troubleshooting v programs:
-g Generate debugging information in the backtraces. Add *V* line numbers to the generated executable.
-cg Same as -g, but add *C* line numbers to the generated executable instead of *V* line numbers.
-keep_c Do NOT remove the generated .tmp.c files after compilation.
It is useful when using debuggers like gdb/visual studio, when given after `-g` / `-cg`.
-pretty_c Run clang-format over the generated C file, so that it looks nicer. Requires you to have clang-format.
-show_c_cmd Print the full C compilation command and how much time it took. See also `-verbose`.
-cc <ccompiler> Specify which C compiler you want to use as a C backend.
The C backend compiler should be able to handle C99 compatible C code.
Common C compilers are gcc, clang, tcc, icc, cl...
-cflags <flags> Pass additional C flags to the C backend compiler.
Example: -cflags `sdl2-config --cflags`
Commands:
up Update V. Run `v up` at least once per day, since V development is rapid and features/bugfixes are added constantly.
run <file.v> Build and execute the V program in file.v. You can add arguments for the V program *after* the file name.
build <module> Compile a module into an object file.
repl Run the V REPL. If V is running in a tty terminal, the REPL is interactive, otherwise it just reads from stdin.
symlink Useful on Unix systems. Symlinks the current V executable to /usr/local/bin/v, so that V is globally available.
test-compiler Run all V test files, and compile all V examples.
test folder/ Run all V test files located in the folder and its subfolders. You can also pass individual _test.v files too.
fmt Run vfmt to format the source code. [wip]
doc Run vdoc over the source code and produce documentation.
translate Translates C to V. [wip, will be available in V 0.3]
create Create a new v project interactively. Answer the questions, and run it with `v run projectname`
V package management commands:
search keywords Search the https://vpm.vlang.io/ module repository for matching modules and shows their details.
install <module> Install a user module from https://vpm.vlang.io/.
update [module] Updates an already installed module, or ALL installed modules at once, when no module name is given.
remove [module] Removes an installed module, or ALL installed modules at once, when no module name is given.
'
)
/*
- To disable automatic formatting:
v -nofmt file.v
*/

70
cmd/v/simple_tool.v Normal file
View File

@@ -0,0 +1,70 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import (
compiler
filepath
os
)
fn launch_tool(is_verbose bool, tname string, cmdname string) {
vexe := vexe_path()
vroot := filepath.dir(vexe)
compiler.set_vroot_folder(vroot)
mut tname_index := os.args.index(cmdname)
if tname_index == -1 {
tname_index = os.args.len
}
mut compilation_options := os.args[1..tname_index].clone()
tool_args := os.args[1..].join(' ')
tool_exe := os.realpath('$vroot/cmd/tools/$tname')
tool_source := os.realpath('$vroot/cmd/tools/${tname}.v')
tool_command := '"$tool_exe" $tool_args'
if is_verbose {
eprintln('launch_tool vexe : $vroot')
eprintln('launch_tool vroot : $vroot')
eprintln('launch_tool tool_args : $tool_args')
eprintln('launch_tool tool_command: $tool_command')
}
mut should_compile := false
if !os.exists(tool_exe) {
should_compile = true
} else {
if os.file_last_mod_unix(tool_exe) <= os.file_last_mod_unix(vexe) {
// v was recompiled, maybe after v up ...
// rebuild the tool too just in case
should_compile = true
}
if os.file_last_mod_unix(tool_exe) <= os.file_last_mod_unix(tool_source) {
// the user changed the source code of the tool
should_compile = true
}
}
if is_verbose {
eprintln('launch_tool should_compile: $should_compile')
}
if should_compile {
if tname == 'vfmt' {
compilation_options << ['-d', 'vfmt']
}
compilation_args := compilation_options.join(' ')
compilation_command := '"$vexe" $compilation_args "$tool_source"'
if is_verbose {
eprintln('Compiling $tname with: "$compilation_command"')
}
tool_compilation := os.exec(compilation_command) or { panic(err) }
if tool_compilation.exit_code != 0 {
panic('V tool "$tool_source" could not be compiled\n' + tool_compilation.output)
}
}
if is_verbose {
eprintln('launch_tool running tool command: $tool_command ...')
}
exit(os.system(tool_command))
}

31
cmd/v/symlink.v Normal file
View File

@@ -0,0 +1,31 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import os
fn create_symlink() {
$if windows {
return
}
vexe := vexe_path()
mut link_path := '/usr/local/bin/v'
mut ret := os.exec('ln -sf $vexe $link_path') or { panic(err) }
if ret.exit_code == 0 {
println('Symlink "$link_path" has been created')
}
else if os.system('uname -o | grep -q \'[A/a]ndroid\'') == 0 {
println('Failed to create symlink "$link_path". Trying again with Termux path for Android.')
link_path = '/data/data/com.termux/files/usr/bin/v'
ret = os.exec('ln -sf $vexe $link_path') or { panic(err) }
if ret.exit_code == 0 {
println('Symlink "$link_path" has been created')
} else {
println('Failed to create symlink "$link_path". Try again with sudo.')
}
} else {
println('Failed to create symlink "$link_path". Try again with sudo.')
}
}

88
cmd/v/v.v Normal file
View File

@@ -0,0 +1,88 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import (
compiler
os
)
const (
simple_cmd = [
'fmt',
'up',
'create',
'test', 'test-fmt', 'test-compiler',
'bin2v',
'repl',
'build-tools', 'build-examples', 'build-vbinaries'
]
)
fn main() {
arg := join_flags_and_argument()
command, option := get_basic_command_and_option(arg)
is_verbose := '-verbose' in arg || '--verbose' in arg
if '-v' in option || '--version' in option || command == 'version' {
// Print the version and exit.
version_hash := compiler.vhash()
println('V $compiler.Version $version_hash')
return
}
if '-h' in option || '--help' in option || command == 'help' {
if is_verbose {
println(verbose_help_text)
} else {
println(help_text)
}
return
}
if is_verbose {
eprintln('v args: $arg')
eprintln('v command: $command')
eprintln('v options: $option')
}
if command in simple_cmd {
//External tools
launch_tool(is_verbose, 'v' + command, command)
return
}
if command == 'run' || command == 'build' || command.ends_with('.v') || os.exists(command) {
compile(command, arg)
return
}
match command {
'', '-' {
if arg.len == 1 {
println('Running REPL as no arguments are provided. For usage information, use `v help`.')
}
launch_tool(is_verbose, 'vrepl', '')
}
'translate' {
println('Translating C to V will be available in V 0.3 (January)')
}
'search', 'install', 'update', 'remove' {
launch_tool(is_verbose, 'vpm', command)
}
'get' {
println('Use `v install` to install modules from vpm.vlang.io.')
}
'symlink' {
create_symlink()
}
'doc' {
println('Currently unimplemented')
}
else {
eprintln('v $command: unknown command\nRun "v help" for usage.')
exit(1)
}
}
}