v/vlib/v/builder/rebuilding.v

369 lines
11 KiB
V

module builder
import os
import hash
import time
import rand
import strings
import v.util
import v.pref
import v.vcache
pub fn (mut b Builder) rebuild_modules() {
if !b.pref.use_cache || b.pref.build_mode == .build_module {
return
}
all_files := b.parsed_files.map(it.path)
$if trace_invalidations ? {
eprintln('> rebuild_modules all_files: $all_files')
}
invalidations := b.find_invalidated_modules_by_files(all_files)
$if trace_invalidations ? {
eprintln('> rebuild_modules invalidations: $invalidations')
}
if invalidations.len > 0 {
vexe := pref.vexe_path()
for imp in invalidations {
b.v_build_module(vexe, imp)
}
}
}
pub fn (mut b Builder) find_invalidated_modules_by_files(all_files []string) []string {
util.timing_start('${@METHOD} source_hashing')
mut new_hashes := map[string]string{}
mut old_hashes := map[string]string{}
mut sb_new_hashes := strings.new_builder(1024)
//
mut cm := vcache.new_cache_manager(all_files)
sold_hashes := cm.load('.hashes', 'all_files') or { ' ' }
// eprintln(sold_hashes)
sold_hashes_lines := sold_hashes.split('\n')
for line in sold_hashes_lines {
if line.len == 0 {
continue
}
x := line.split(' ')
chash := x[0]
cpath := x[1]
old_hashes[cpath] = chash
}
// eprintln('old_hashes: $old_hashes')
for cpath in all_files {
ccontent := util.read_file(cpath) or { '' }
chash := hash.sum64_string(ccontent, 7).hex_full()
new_hashes[cpath] = chash
sb_new_hashes.write_string(chash)
sb_new_hashes.write_u8(` `)
sb_new_hashes.write_string(cpath)
sb_new_hashes.write_u8(`\n`)
}
snew_hashes := sb_new_hashes.str()
// eprintln('new_hashes: $new_hashes')
// eprintln('> new_hashes != old_hashes: ' + ( old_hashes != new_hashes ).str())
// eprintln(snew_hashes)
cm.save('.hashes', 'all_files', snew_hashes) or {}
util.timing_measure('${@METHOD} source_hashing')
mut invalidations := []string{}
if new_hashes != old_hashes {
util.timing_start('${@METHOD} rebuilding')
// eprintln('> b.mod_invalidates_paths: $b.mod_invalidates_paths')
// eprintln('> b.mod_invalidates_mods: $b.mod_invalidates_mods')
// eprintln('> b.path_invalidates_mods: $b.path_invalidates_mods')
$if trace_invalidations ? {
for k, v in b.mod_invalidates_paths {
mut m := map[string]bool{}
for mm in b.mod_invalidates_mods[k] {
m[mm] = true
}
eprintln('> module `$k` invalidates: $m.keys()')
for fpath in v {
eprintln(' $fpath')
}
}
}
mut invalidated_paths := map[string]int{}
mut invalidated_mod_paths := map[string]int{}
for npath, nhash in new_hashes {
if npath !in old_hashes {
invalidated_paths[npath]++
continue
}
if old_hashes[npath] != nhash {
invalidated_paths[npath]++
continue
}
}
for opath, ohash in old_hashes {
if opath !in new_hashes {
invalidated_paths[opath]++
continue
}
if new_hashes[opath] != ohash {
invalidated_paths[opath]++
continue
}
}
$if trace_invalidations ? {
eprintln('invalidated_paths: $invalidated_paths')
}
mut rebuild_everything := false
for cycle := 0; true; cycle++ {
$if trace_invalidations ? {
eprintln('> cycle: $cycle | invalidated_paths: $invalidated_paths')
}
mut new_invalidated_paths := map[string]int{}
for npath, _ in invalidated_paths {
invalidated_mods := b.path_invalidates_mods[npath]
if invalidated_mods == ['main'] {
continue
}
if 'builtin' in invalidated_mods {
// When `builtin` is invalid, there is no point in
// extracting a finer grained dependency resolution
// of the dependencies any more. Instead, just rebuild
// every module.
rebuild_everything = true
break
}
for imod in invalidated_mods {
if imod == 'main' {
continue
}
for np in b.mod_invalidates_paths[imod] {
new_invalidated_paths[np]++
}
}
$if trace_invalidations ? {
eprintln('> npath -> invalidated_mods | $npath -> $invalidated_mods')
}
mpath := os.dir(npath)
invalidated_mod_paths[mpath]++
}
if rebuild_everything {
break
}
if new_invalidated_paths.len == 0 {
break
}
invalidated_paths = new_invalidated_paths.clone()
}
if rebuild_everything {
invalidated_mod_paths = {}
for npath, _ in new_hashes {
mpath := os.dir(npath)
pimods := b.path_invalidates_mods[npath]
if pimods == ['main'] {
continue
}
invalidated_mod_paths[mpath]++
}
}
$if trace_invalidations ? {
eprintln('invalidated_mod_paths: $invalidated_mod_paths')
eprintln('rebuild_everything: $rebuild_everything')
}
if invalidated_mod_paths.len > 0 {
impaths := invalidated_mod_paths.keys()
for imp in impaths {
invalidations << imp
}
}
util.timing_measure('${@METHOD} rebuilding')
}
return invalidations
}
fn (mut b Builder) v_build_module(vexe string, imp_path string) {
pwd := os.getwd()
defer {
os.chdir(pwd) or {}
}
// do run `v build-module x` always in main vfolder; x can be a relative path
vroot := os.dir(vexe)
os.chdir(vroot) or {}
boptions := b.pref.build_options.join(' ')
rebuild_cmd := '${os.quoted_path(vexe)} $boptions build-module ${os.quoted_path(imp_path)}'
vcache.dlog('| Builder.' + @FN, 'vexe: $vexe | imp_path: $imp_path | rebuild_cmd: $rebuild_cmd')
$if trace_v_build_module ? {
eprintln('> Builder.v_build_module: $rebuild_cmd')
}
os.system(rebuild_cmd)
}
fn (mut b Builder) rebuild_cached_module(vexe string, imp_path string) string {
res := b.pref.cache_manager.exists('.o', imp_path) or {
if b.pref.is_verbose {
println('Cached $imp_path .o file not found... Building .o file for $imp_path')
}
b.v_build_module(vexe, imp_path)
rebuilded_o := b.pref.cache_manager.exists('.o', imp_path) or {
panic('could not rebuild cache module for $imp_path, error: $err.msg()')
}
return rebuilded_o
}
return res
}
fn (mut b Builder) handle_usecache(vexe string) {
if !b.pref.use_cache || b.pref.build_mode == .build_module {
return
}
mut libs := []string{} // builtin.o os.o http.o etc
mut built_modules := []string{}
builtin_obj_path := b.rebuild_cached_module(vexe, 'vlib/builtin')
libs << builtin_obj_path
for ast_file in b.parsed_files {
if b.pref.is_test && ast_file.mod.name != 'main' {
imp_path := b.find_module_path(ast_file.mod.name, ast_file.path) or {
verror('cannot import module "$ast_file.mod.name" (not found)')
break
}
obj_path := b.rebuild_cached_module(vexe, imp_path)
libs << obj_path
built_modules << ast_file.mod.name
}
for imp_stmt in ast_file.imports {
imp := imp_stmt.mod
// strconv is already imported inside builtin, so skip generating its object file
// TODO: incase we have other modules with the same name, make sure they are vlib
// is this even doign anything?
if util.module_is_builtin(imp) {
continue
}
if imp in built_modules {
continue
}
if util.should_bundle_module(imp) {
continue
}
// The problem is cmd/v is in module main and imports
// the relative module named help, which is built as cmd.v.help not help
// currently this got this workign by building into main, see ast.FnDecl in cgen
if imp == 'help' {
continue
}
imp_path := b.find_module_path(imp, ast_file.path) or {
verror('cannot import module "$imp" (not found)')
break
}
obj_path := b.rebuild_cached_module(vexe, imp_path)
libs << obj_path
built_modules << imp
}
}
b.ccoptions.post_args << libs
}
pub fn (mut b Builder) should_rebuild() bool {
mut exe_name := b.pref.out_name
$if windows {
exe_name = exe_name + '.exe'
}
if !os.is_file(exe_name) {
return true
}
if !b.pref.is_crun {
return true
}
mut v_program_files := []string{}
is_file := os.is_file(b.pref.path)
is_dir := os.is_dir(b.pref.path)
if is_file {
v_program_files << b.pref.path
} else if is_dir {
v_program_files << b.v_files_from_dir(b.pref.path)
}
v_program_files.sort() // ensure stable keys for the dependencies cache
b.crun_cache_keys = v_program_files
b.crun_cache_keys << exe_name
// just check the timestamps for now:
exe_stamp := os.file_last_mod_unix(exe_name)
source_stamp := most_recent_timestamp(v_program_files)
if exe_stamp <= source_stamp {
return true
}
////////////////////////////////////////////////////////////////////////////
// The timestamps for the top level files were found ok,
// however we want to *also* make sure that a full rebuild will be done
// if any of the dependencies (if we know them) are changed.
mut cm := vcache.new_cache_manager(b.crun_cache_keys)
// always rebuild, when the compilation options changed between 2 sequential cruns:
sbuild_options := cm.load('.build_options', '.crun') or { return true }
if sbuild_options != b.pref.build_options.join('\n') {
return true
}
sdependencies := cm.load('.dependencies', '.crun') or {
// empty/wiped out cache, we do not know what the dependencies are, so just
// rebuild, which will fill in the dependencies cache for the next crun
return true
}
dependencies := sdependencies.split('\n')
// we have already compiled these source files, and have their dependencies
dependencies_stamp := most_recent_timestamp(dependencies)
if dependencies_stamp < exe_stamp {
return false
}
return true
}
fn most_recent_timestamp(files []string) i64 {
mut res := i64(0)
for f in files {
f_stamp := os.file_last_mod_unix(f)
if res <= f_stamp {
res = f_stamp
}
}
return res
}
pub fn (mut b Builder) rebuild(backend_cb FnBackend) {
mut sw := time.new_stopwatch()
backend_cb(mut b)
if b.pref.is_crun {
// save the dependencies after the first compilation, they will be used for subsequent ones:
mut cm := vcache.new_cache_manager(b.crun_cache_keys)
dependency_files := b.parsed_files.map(it.path)
cm.save('.dependencies', '.crun', dependency_files.join('\n')) or {}
cm.save('.build_options', '.crun', b.pref.build_options.join('\n')) or {}
}
mut timers := util.get_timers()
timers.show_remaining()
if b.pref.is_stats {
compilation_time_micros := 1 + sw.elapsed().microseconds()
scompilation_time_ms := util.bold('${f64(compilation_time_micros) / 1000.0:6.3f}')
mut all_v_source_lines, mut all_v_source_bytes := 0, 0
for pf in b.parsed_files {
all_v_source_lines += pf.nr_lines
all_v_source_bytes += pf.nr_bytes
}
mut sall_v_source_lines := all_v_source_lines.str()
mut sall_v_source_bytes := all_v_source_bytes.str()
sall_v_source_lines = util.bold('${sall_v_source_lines:10s}')
sall_v_source_bytes = util.bold('${sall_v_source_bytes:10s}')
println(' V source code size: $sall_v_source_lines lines, $sall_v_source_bytes bytes')
//
mut slines := b.stats_lines.str()
mut sbytes := b.stats_bytes.str()
slines = util.bold('${slines:10s}')
sbytes = util.bold('${sbytes:10s}')
println('generated target code size: $slines lines, $sbytes bytes')
//
vlines_per_second := int(1_000_000.0 * f64(all_v_source_lines) / f64(compilation_time_micros))
svlines_per_second := util.bold(vlines_per_second.str())
println('compilation took: $scompilation_time_ms ms, compilation speed: $svlines_per_second vlines/s')
}
}
pub fn (mut b Builder) get_vtmp_filename(base_file_name string, postfix string) string {
vtmp := util.get_vtmp_folder()
mut uniq := ''
if !b.pref.reuse_tmpc {
uniq = '.$rand.u64()'
}
fname := os.file_name(os.real_path(base_file_name)) + '$uniq$postfix'
return os.real_path(os.join_path(vtmp, fname))
}