v/cmd/tools/gen_vc.v

371 lines
11 KiB
V
Raw Normal View History

module main
import os
import log
import flag
import time
import vweb
import net.urllib
2019-08-31 16:44:01 +02:00
// 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
2019-08-31 18:00:40 +02:00
// --purge force purge the local repositories
2019-08-31 16:44:01 +02:00
// --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
2020-03-16 14:20:40 +01:00
// --force force update even if already up to date
2019-08-31 16:44:01 +02:00
// git credentials
const (
2019-08-31 16:44:01 +02:00
git_username = os.getenv('GITUSER')
git_password = os.getenv('GITPASS')
)
// repository
const (
2019-08-31 16:44:01 +02:00
// git repo
git_repo_v = 'github.com/vlang/v'
git_repo_vc = 'github.com/vlang/vc'
2019-08-31 16:44:01 +02:00
// local repo directories
git_repo_dir_v = 'v'
git_repo_dir_vc = 'vc'
)
// gen_vc
const (
2019-08-31 16:44:01 +02:00
// name
app_name = 'gen_vc'
2019-08-31 16:44:01 +02:00
// version
app_version = '0.1.2'
2019-08-31 16:44:01 +02:00
// description
app_description = "This tool regenerates V's bootstrap .c files every time the V master branch is updated."
2019-08-31 16:44:01 +02:00
// 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',
2019-08-31 16:44:01 +02:00
]
)
// default options (overridden by flags)
const (
2019-08-31 16:44:01 +02:00
// gen_vc working directory
work_dir = '/tmp/gen_vc'
2019-08-31 16:44:01 +02:00
// dont push anything to remote repo
dry_run = false
2019-08-31 16:44:01 +02:00
// server port
server_port = 7171
// log file
log_file = '$work_dir/log.txt'
2019-08-31 16:44:01 +02:00
// log_to is either 'file' or 'terminal'
log_to = 'terminal'
2019-08-31 16:44:01 +02:00
)
// errors
const (
2019-08-31 16:44:01 +02:00
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
// flag options
options FlagOptions
2019-08-31 16:44:01 +02:00
mut:
logger &log.Log
2019-08-31 16:44:01 +02:00
// true if error was experienced running generate
gen_error bool
}
// webhook server
2020-06-12 08:22:01 +02:00
struct WebhookServer {
vweb.Context
mut:
gen_vc &GenVC = 0 // initialized in init_server
2019-08-31 16:44:01 +02:00
}
// storage for flag options
struct FlagOptions {
work_dir string
2019-08-31 18:00:40 +02:00
purge bool
2019-08-31 16:44:01 +02:00
serve bool
port int
log_to string
log_file string
dry_run bool
force bool
2019-08-31 16:44:01 +02:00
}
fn main() {
mut fp := flag.new_flag_parser(os.args.clone())
fp.application(app_name)
2020-06-12 08:22:01 +02:00
fp.version(app_version)
fp.description(app_description)
fp.skip_executable()
show_help := fp.bool('help', 0, false, 'Show this help screen\n')
2019-08-31 16:44:01 +02:00
flag_options := parse_flags(mut fp)
if show_help {
println(fp.usage())
exit(0)
}
2019-12-01 10:50:13 +01:00
fp.finalize() or {
eprintln(err)
println(fp.usage())
return
}
2019-08-31 16:44:01 +02:00
// webhook server mode
if flag_options.serve {
2021-05-11 10:08:46 +02:00
vweb.run<WebhookServer>(&WebhookServer{}, flag_options.port)
} else {
// cmd mode
2019-08-31 16:44:01 +02:00
mut gen_vc := new_gen_vc(flag_options)
2019-08-31 18:00:40 +02:00
gen_vc.init()
2019-08-31 16:44:01 +02:00
gen_vc.generate()
}
}
// new GenVC
fn new_gen_vc(flag_options FlagOptions) &GenVC {
2019-12-01 10:50:13 +01:00
mut logger := &log.Log{}
2020-04-06 17:22:53 +02:00
logger.set_level(.debug)
2019-12-01 10:50:13 +01:00
if flag_options.log_to == 'file' {
logger.set_full_logpath(flag_options.log_file)
2019-12-01 10:50:13 +01:00
}
2019-08-31 16:44:01 +02:00
return &GenVC{
options: flag_options
2019-12-01 10:50:13 +01:00
logger: logger
2019-08-31 16:44:01 +02:00
}
}
// WebhookServer init
pub fn (mut ws WebhookServer) init_server() {
2019-08-31 16:44:01 +02:00
mut fp := flag.new_flag_parser(os.args.clone())
flag_options := parse_flags(mut fp)
ws.gen_vc = new_gen_vc(flag_options)
2019-08-31 18:00:40 +02:00
ws.gen_vc.init()
// ws.gen_vc = new_gen_vc(flag_options)
2019-08-31 16:44:01 +02:00
}
pub fn (mut ws WebhookServer) index() {
eprintln('WebhookServer.index() called')
}
2019-08-31 16:44:01 +02:00
// gen webhook
2020-05-17 13:51:18 +02:00
pub fn (mut ws WebhookServer) genhook() {
2020-06-12 15:10:22 +02:00
// request data
// println(ws.vweb.req.data)
// TODO: parse request. json or urlencoded
// json.decode or net.urllib.parse
2019-08-31 16:44:01 +02:00
ws.gen_vc.generate()
// error in generate
if ws.gen_vc.gen_error {
ws.json('{status: "failed"}')
2019-08-31 16:44:01 +02:00
return
}
ws.json('{status: "ok"}')
2019-08-31 16:44:01 +02:00
}
2019-12-21 03:25:28 +01:00
pub fn (ws &WebhookServer) reset() {
}
2019-08-31 16:44:01 +02:00
// parse flags to FlagOptions struct
2020-06-04 10:35:40 +02:00
fn parse_flags(mut fp flag.FlagParser) FlagOptions {
2019-08-31 16:44:01 +02:00
return FlagOptions{
serve: fp.bool('serve', 0, false, 'run in webhook server mode')
work_dir: fp.string('work-dir', 0, work_dir, 'gen_vc working directory')
purge: fp.bool('purge', 0, false, 'force purge the local repositories')
port: fp.int('port', 0, server_port, 'port for web server to listen on')
log_to: fp.string('log-to', 0, log_to, "log to is 'file' or 'terminal'")
log_file: fp.string('log-file', 0, log_file, "log file to use when log-to is 'file'")
dry_run: fp.bool('dry-run', 0, dry_run, 'when specified dont push anything to remote repo')
force: fp.bool('force', 0, false, 'force update even if already up to date')
2019-08-31 16:44:01 +02:00
}
}
2020-05-17 13:51:18 +02:00
fn (mut gen_vc GenVC) init() {
2019-08-31 18:00:40 +02:00
// purge repos if flag is passed
if gen_vc.options.purge {
gen_vc.purge_repos()
}
}
2019-08-31 16:44:01 +02:00
// regenerate
2020-05-17 13:51:18 +02:00
fn (mut gen_vc GenVC) generate() {
2019-08-31 16:44:01 +02:00
// set errors to false
gen_vc.gen_error = false
// check if gen_vc dir exists
if !os.is_dir(gen_vc.options.work_dir) {
2019-08-31 16:44:01 +02:00
// try create
os.mkdir(gen_vc.options.work_dir) or { panic(err) }
2019-08-31 16:44:01 +02:00
// still dosen't exist... we have a problem
if !os.is_dir(gen_vc.options.work_dir) {
2019-08-31 16:44:01 +02:00
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) or {}
2019-08-31 18:00:40 +02:00
// if we are not running with the --serve flag (webhook server)
2019-08-31 16:44:01 +02:00
// 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) {
2019-08-31 16:44:01 +02:00
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.options.force {
2019-08-31 16:44:01 +02:00
gen_vc.logger.warn('v repository is already up to date.')
return
}
}
// delete repos
2019-08-31 18:00:40 +02:00
gen_vc.purge_repos()
2019-08-31 16:44:01 +02:00
// 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"')
2019-08-31 16:44:01 +02:00
// 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) }
2019-08-31 16:44:01 +02:00
// 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().replace("'",
'"')
2019-08-31 16:44:01 +02:00
// 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')
2019-08-31 16:44:01 +02:00
// 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.options.force {
2019-08-31 16:44:01 +02:00
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 {
2021-04-18 07:55:27 +02:00
c_file := if os_name == 'nix' { 'v.c' } else { 'v_win.c' }
2020-03-08 19:00:57 +01:00
v_flags := if os_name == 'nix' { '-os cross' } else { '-os $os_name' }
2019-08-31 16:44:01 +02:00
// try generate .c file
2020-02-09 10:08:04 +01:00
gen_vc.cmd_exec('$v_exec $v_flags -o $c_file $git_repo_dir_v/cmd/v')
2019-08-31 16:44:01 +02:00
// 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')
// 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')
2019-08-31 16:44:01 +02:00
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'")
2019-08-31 16:44:01 +02:00
// 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
2020-05-17 13:51:18 +02:00
fn (mut gen_vc GenVC) cmd_exec_safe(cmd string) string {
2019-08-31 16:44:01 +02:00
return gen_vc.command_execute(cmd, gen_vc.options.dry_run)
}
// always execute command
2020-05-17 13:51:18 +02:00
fn (mut gen_vc GenVC) cmd_exec(cmd string) string {
2019-08-31 16:44:01 +02:00
return gen_vc.command_execute(cmd, false)
}
// execute command
2020-05-17 13:51:18 +02:00
fn (mut gen_vc GenVC) command_execute(cmd string, dry bool) string {
2019-08-31 16:44:01 +02:00
// 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.execute(cmd)
if r.exit_code < 0 {
2019-08-31 16:44:01 +02:00
gen_vc.logger.error('$err_msg_cmd_x: "$cmd" could not start.')
gen_vc.logger.error(r.output)
2019-08-31 16:44:01 +02:00
// something went wrong, better start fresh next time
2019-08-31 18:00:40 +02:00
gen_vc.purge_repos()
2019-08-31 16:44:01 +02:00
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
2019-08-31 18:00:40 +02:00
gen_vc.purge_repos()
2019-08-31 16:44:01 +02:00
gen_vc.gen_error = true
return ''
}
return r.output
}
// just log cmd, dont execute
2020-05-17 13:51:18 +02:00
fn (mut gen_vc GenVC) command_execute_dry(cmd string) string {
2019-08-31 16:44:01 +02:00
gen_vc.logger.info('cmd (dry): "$cmd"')
return ''
}
// delete repo directories
2020-05-17 13:51:18 +02:00
fn (mut gen_vc GenVC) purge_repos() {
2019-08-31 16:44:01 +02:00
// delete old repos (better to be fully explicit here, since these are destructive operations)
2019-08-31 18:00:40 +02:00
mut repo_dir := '$gen_vc.options.work_dir/$git_repo_dir_v'
if os.is_dir(repo_dir) {
2019-08-31 18:00:40 +02:00
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) {
2019-08-31 18:00:40 +02:00
gen_vc.logger.info('purging local repo: "$repo_dir"')
gen_vc.cmd_exec('rm -rf $repo_dir')
}
2019-08-31 16:44:01 +02:00
}
// check if file size is too short
fn (mut gen_vc GenVC) assert_file_exists_and_is_not_too_short(f string, emsg string) {
if !os.exists(f) {
2019-08-31 16:44:01 +02:00
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
}
}