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

v: add VCACHE support for thirdparty object files and for v build-module

This commit is contained in:
Delyan Angelov 2020-10-25 03:09:07 +03:00
parent 89daec4e93
commit 5f6259dde6
6 changed files with 166 additions and 35 deletions

View File

@ -118,6 +118,21 @@ fn (mut v Builder) post_process_c_compiler_output(res os.Result) {
verror(c_error_info) verror(c_error_info)
} }
fn (mut v Builder) rebuild_cached_module(vexe string, imp_path string) string {
res := v.pref.cache_manager.exists('.o', imp_path) or {
println('Cached $imp_path .o file not found... Building .o file for $imp_path')
boptions := v.pref.build_options.join(' ')
rebuild_cmd := '$vexe $boptions build-module $imp_path'
// eprintln('>> rebuild_cmd: $rebuild_cmd')
os.system(rebuild_cmd)
rebuilded_o := v.pref.cache_manager.exists('.o', imp_path) or {
panic('could not rebuild cache module for $imp_path, error: $err')
}
return rebuilded_o
}
return res
}
fn (mut v Builder) cc() { fn (mut v Builder) cc() {
if os.executable().contains('vfmt') { if os.executable().contains('vfmt') {
return return
@ -260,14 +275,9 @@ fn (mut v Builder) cc() {
linker_flags << '-nostdlib' linker_flags << '-nostdlib'
} }
if v.pref.build_mode == .build_module { if v.pref.build_mode == .build_module {
// Create the modules & out directory if it's not there. v.pref.out_name = v.pref.cache_manager.postfix_with_key2cpath('.o', v.pref.path) // v.out_name
out_dir := os.join_path(pref.default_module_path, 'cache', v.pref.path) println('Building $v.pref.path to $v.pref.out_name ...')
pdir := out_dir.all_before_last(os.path_separator) v.pref.cache_manager.save('.description.txt', v.pref.path, '${v.pref.path:-30} @ $v.pref.cache_manager.vopts\n')
if !os.is_dir(pdir) {
os.mkdir_all(pdir)
}
v.pref.out_name = '${out_dir}.o' // v.out_name
println('Building ${v.pref.out_name}...')
// println('v.table.imports:') // println('v.table.imports:')
// println(v.table.imports) // println(v.table.imports)
} }
@ -349,10 +359,7 @@ fn (mut v Builder) cc() {
args << '-c' args << '-c'
} else if v.pref.use_cache { } else if v.pref.use_cache {
mut built_modules := []string{} mut built_modules := []string{}
builtin_obj_path := os.join_path(pref.default_module_path, 'cache', 'vlib', 'builtin.o') builtin_obj_path := v.rebuild_cached_module(vexe, 'vlib/builtin')
if !os.exists(builtin_obj_path) {
os.system('$vexe build-module vlib/builtin')
}
libs += ' ' + builtin_obj_path libs += ' ' + builtin_obj_path
for ast_file in v.parsed_files { for ast_file in v.parsed_files {
for imp_stmt in ast_file.imports { for imp_stmt in ast_file.imports {
@ -382,14 +389,8 @@ fn (mut v Builder) cc() {
// continue // continue
// } // }
imp_path := os.join_path('vlib', mod_path) imp_path := os.join_path('vlib', mod_path)
cache_path := os.join_path(pref.default_module_path, 'cache') obj_path := v.rebuild_cached_module(vexe, imp_path)
obj_path := os.join_path(cache_path, '${imp_path}.o') libs += ' ' + obj_path
if os.exists(obj_path) {
libs += ' ' + obj_path
} else {
println('$obj_path not found... building module $imp')
os.system('$vexe build-module $imp_path')
}
if obj_path.ends_with('vlib/ui.o') { if obj_path.ends_with('vlib/ui.o') {
args << '-framework Cocoa -framework Carbon' args << '-framework Cocoa -framework Carbon'
} }
@ -777,21 +778,19 @@ fn (mut v Builder) build_thirdparty_obj_file(path string, moduleflags []cflag.CF
if v.pref.os == .windows { if v.pref.os == .windows {
// Cross compiling for Windows // Cross compiling for Windows
$if !windows { $if !windows {
if os.exists(obj_path) {
os.rm(obj_path)
}
v.pref.ccompiler = mingw_cc v.pref.ccompiler = mingw_cc
} }
} }
if os.exists(obj_path) { opath := v.pref.cache_manager.postfix_with_key2cpath('.o', obj_path)
if os.exists(opath) {
return return
} }
println('$obj_path not found, building it...') println('$obj_path not found, building it in $opath ...')
cfile := '${path[..path.len - 2]}.c' cfile := '${path[..path.len - 2]}.c'
btarget := moduleflags.c_options_before_target() btarget := moduleflags.c_options_before_target()
atarget := moduleflags.c_options_after_target() atarget := moduleflags.c_options_after_target()
cppoptions := if v.pref.ccompiler.contains('++') { ' -fpermissive -w ' } else { '' } cppoptions := if v.pref.ccompiler.contains('++') { ' -fpermissive -w ' } else { '' }
cmd := '$v.pref.ccompiler $cppoptions $v.pref.third_party_option $btarget -c -o "$obj_path" "$cfile" $atarget' cmd := '$v.pref.ccompiler $cppoptions $v.pref.third_party_option $btarget -c -o "$opath" "$cfile" $atarget'
res := os.exec(cmd) or { res := os.exec(cmd) or {
eprintln('exec failed for thirdparty object build cmd:\n$cmd') eprintln('exec failed for thirdparty object build cmd:\n$cmd')
verror(err) verror(err)
@ -802,6 +801,7 @@ fn (mut v Builder) build_thirdparty_obj_file(path string, moduleflags []cflag.CF
verror(res.output) verror(res.output)
return return
} }
v.pref.cache_manager.save('.description.txt', obj_path, '${obj_path:-30} @ $cmd\n')
println(res.output) println(res.output)
} }

View File

@ -1,15 +1,19 @@
module builder module builder
import os
import v.cflag import v.cflag
// get flags for current os // get flags for current os
fn (v &Builder) get_os_cflags() []cflag.CFlag { fn (mut v Builder) get_os_cflags() []cflag.CFlag {
mut flags := []cflag.CFlag{} mut flags := []cflag.CFlag{}
mut ctimedefines := []string{} mut ctimedefines := []string{}
if v.pref.compile_defines.len > 0 { if v.pref.compile_defines.len > 0 {
ctimedefines << v.pref.compile_defines ctimedefines << v.pref.compile_defines
} }
for flag in v.table.cflags { for mut flag in v.table.cflags {
if flag.value.ends_with('.o') {
flag.cached = v.pref.cache_manager.postfix_with_key2cpath('.o', os.real_path(flag.value))
}
if flag.os == '' || if flag.os == '' ||
(flag.os == 'linux' && v.pref.os == .linux) || (flag.os == 'linux' && v.pref.os == .linux) ||
(flag.os == 'macos' && v.pref.os == .macos) || (flag.os == 'macos' && v.pref.os == .macos) ||
@ -27,7 +31,7 @@ fn (v &Builder) get_os_cflags() []cflag.CFlag {
return flags return flags
} }
fn (v &Builder) get_rest_of_module_cflags(c &cflag.CFlag) []cflag.CFlag { fn (mut v Builder) get_rest_of_module_cflags(c &cflag.CFlag) []cflag.CFlag {
mut flags := []cflag.CFlag{} mut flags := []cflag.CFlag{}
cflags := v.get_os_cflags() cflags := v.get_os_cflags()
for flag in cflags { for flag in cflags {

View File

@ -8,19 +8,24 @@ import os
// parsed cflag // parsed cflag
pub struct CFlag { pub struct CFlag {
pub: pub:
mod string // the module in which the flag was given mod string // the module in which the flag was given
os string // eg. windows | darwin | linux os string // eg. windows | darwin | linux
name string // eg. -I name string // eg. -I
value string // eg. /path/to/include value string // eg. /path/to/include
pub mut:
cached string // eg. ~/.vmodules/cache/ea/ea9878886727367672163.o (for .o files)
} }
pub fn (c &CFlag) str() string { pub fn (c &CFlag) str() string {
return 'CFlag{ name: "$c.name" value: "$c.value" mod: "$c.mod" os: "$c.os" }' return 'CFlag{ name: "$c.name" value: "$c.value" mod: "$c.mod" os: "$c.os" cached: "$c.cached" }'
} }
// format flag // format flag
pub fn (cf &CFlag) format() string { pub fn (cf &CFlag) format() string {
mut value := cf.value mut value := cf.value
if cf.cached != '' {
value = cf.cached
}
if cf.name in ['-l', '-Wa', '-Wl', '-Wp'] && value.len > 0 { if cf.name in ['-l', '-Wa', '-Wl', '-Wp'] && value.len > 0 {
return '$cf.name$value'.trim_space() return '$cf.name$value'.trim_space()
} }

View File

@ -71,6 +71,17 @@ pub fn (mut p Preferences) fill_with_defaults() {
} }
} }
} }
// Prepare the cache manager. All options that can affect the generated cached .c files
// should go into res.cache_manager.vopts, which is used as a salt for the cache hash.
p.cache_manager = new_cache_manager([
'$p.backend | $p.os | $p.ccompiler',
p.cflags.trim_space(),
p.third_party_option.trim_space(),
'$p.compile_defines_all',
'$p.compile_defines',
'$p.lookup_path',
])
// eprintln('prefs.cache_manager: $p')
} }
fn default_c_compiler() string { fn default_c_compiler() string {

View File

@ -129,6 +129,8 @@ pub mut:
is_ios_simulator bool is_ios_simulator bool
is_apk bool // build as Android .apk format is_apk bool // build as Android .apk format
cleanup_files []string // list of temporary *.tmp.c and *.tmp.c.rsp files. Cleaned up on successfull builds. cleanup_files []string // list of temporary *.tmp.c and *.tmp.c.rsp files. Cleaned up on successfull builds.
build_options []string // list of options, that should be passed down to `build-module`, if needed for -usecache
cache_manager CacheManager
} }
pub fn parse_args(args []string) (&Preferences, string) { pub fn parse_args(args []string) (&Preferences, string) {
@ -142,6 +144,7 @@ pub fn parse_args(args []string) (&Preferences, string) {
match arg { match arg {
'-apk' { '-apk' {
res.is_apk = true res.is_apk = true
res.build_options << arg
} }
'-show-timings' { '-show-timings' {
res.show_timings = true res.show_timings = true
@ -167,10 +170,12 @@ pub fn parse_args(args []string) (&Preferences, string) {
'-g' { '-g' {
res.is_debug = true res.is_debug = true
res.is_vlines = true res.is_vlines = true
res.build_options << arg
} }
'-cg' { '-cg' {
res.is_debug = true res.is_debug = true
res.is_vlines = false res.is_vlines = false
res.build_options << arg
} }
'-repl' { '-repl' {
res.is_repl = true res.is_repl = true
@ -190,19 +195,23 @@ pub fn parse_args(args []string) (&Preferences, string) {
} }
'-autofree' { '-autofree' {
res.autofree = true res.autofree = true
res.build_options << arg
} }
'-compress' { '-compress' {
res.compress = true res.compress = true
} }
'-freestanding' { '-freestanding' {
res.is_bare = true res.is_bare = true
res.build_options << arg
} }
'-no-preludes' { '-no-preludes' {
res.no_preludes = true res.no_preludes = true
res.build_options << arg
} }
'-prof', '-profile' { '-prof', '-profile' {
res.profile_file = cmdline.option(current_args, '-profile', '-') res.profile_file = cmdline.option(current_args, '-profile', '-')
res.is_prof = true res.is_prof = true
res.build_options << '$arg $res.profile_file'
i++ i++
} }
'-profile-no-inline' { '-profile-no-inline' {
@ -210,6 +219,7 @@ pub fn parse_args(args []string) (&Preferences, string) {
} }
'-prod' { '-prod' {
res.is_prod = true res.is_prod = true
res.build_options << arg
} }
'-simulator' { '-simulator' {
res.is_ios_simulator = true res.is_ios_simulator = true
@ -240,6 +250,7 @@ pub fn parse_args(args []string) (&Preferences, string) {
} }
'-prealloc' { '-prealloc' {
res.prealloc = true res.prealloc = true
res.build_options << arg
} }
'-keepc' { '-keepc' {
eprintln('-keepc is deprecated. V always keeps the generated .tmp.c files now.') eprintln('-keepc is deprecated. V always keeps the generated .tmp.c files now.')
@ -249,6 +260,7 @@ pub fn parse_args(args []string) (&Preferences, string) {
} }
'-x64' { '-x64' {
res.backend = .x64 res.backend = .x64
res.build_options << arg
} }
'-W' { '-W' {
res.warns_are_errors = true res.warns_are_errors = true
@ -277,6 +289,7 @@ pub fn parse_args(args []string) (&Preferences, string) {
exit(1) exit(1)
} }
res.os = target_os_kind res.os = target_os_kind
res.build_options << '$arg $target_os'
} }
'-printfn' { '-printfn' {
res.printfn_list << cmdline.option(current_args, '-printfn', '') res.printfn_list << cmdline.option(current_args, '-printfn', '')
@ -284,6 +297,7 @@ pub fn parse_args(args []string) (&Preferences, string) {
} }
'-cflags' { '-cflags' {
res.cflags += ' ' + cmdline.option(current_args, '-cflags', '') res.cflags += ' ' + cmdline.option(current_args, '-cflags', '')
res.build_options << '$arg "$res.cflags.trim_space()"'
i++ i++
} }
'-define', '-d' { '-define', '-d' {
@ -295,6 +309,7 @@ pub fn parse_args(args []string) (&Preferences, string) {
} }
'-cc' { '-cc' {
res.ccompiler = cmdline.option(current_args, '-cc', 'cc') res.ccompiler = cmdline.option(current_args, '-cc', 'cc')
res.build_options << '$arg "$res.ccompiler"'
i++ i++
} }
'-o' { '-o' {
@ -302,7 +317,9 @@ pub fn parse_args(args []string) (&Preferences, string) {
i++ i++
} }
'-b' { '-b' {
b := backend_from_string(cmdline.option(current_args, '-b', 'c')) or { sbackend := cmdline.option(current_args, '-b', 'c')
res.build_options << '$arg $sbackend'
b := backend_from_string(sbackend) or {
continue continue
} }
res.backend = b res.backend = b
@ -310,11 +327,13 @@ pub fn parse_args(args []string) (&Preferences, string) {
} }
'-path' { '-path' {
path := cmdline.option(current_args, '-path', '') path := cmdline.option(current_args, '-path', '')
res.build_options << '$arg "$path"'
res.lookup_path = path.replace('|', os.path_delimiter).split(os.path_delimiter) res.lookup_path = path.replace('|', os.path_delimiter).split(os.path_delimiter)
i++ i++
} }
'-custom-prelude' { '-custom-prelude' {
path := cmdline.option(current_args, '-custom-prelude', '') path := cmdline.option(current_args, '-custom-prelude', '')
res.build_options << '$arg $path'
prelude := os.read_file(path) or { prelude := os.read_file(path) or {
eprintln('cannot open custom prelude file: $err') eprintln('cannot open custom prelude file: $err')
exit(1) exit(1)
@ -390,6 +409,13 @@ pub fn parse_args(args []string) (&Preferences, string) {
res.build_mode = .build_module res.build_mode = .build_module
res.path = args[command_pos + 1] res.path = args[command_pos + 1]
} }
// keep only the unique res.build_options:
mut m := map[string]string{}
for x in res.build_options {
m[x] = ''
}
res.build_options = m.keys()
// eprintln('>> res.build_options: $res.build_options')
res.fill_with_defaults() res.fill_with_defaults()
return res, command return res, command
} }

85
vlib/v/pref/vcache.v Normal file
View File

@ -0,0 +1,85 @@
module pref
import os
import crypto.md5
// Using a 2 level cache, ensures a more even distribution of cache entries,
// so there will not be cramped folders that contain many thousands of them.
// Most filesystems can not handle performantly such folders, and slow down.
// The first level will contain a max of 256 folders, named from 00/ to ff/.
// Each of them will contain many entries, but hopefully < 1000.
// NB: using a hash here, makes the cache storage immune to special
// characters in the keys, like quotes, spaces and so on.
// Cleanup of the cache is simple: just delete the $VCACHE folder.
// The cache tree will look like this:
// │ $VCACHE
// │ ├── 0f
// │ │ ├── 0f004f983ab9c487b0d7c1a0a73840a5.txt
// │ │ ├── 0f599edf5e16c2756fbcdd4c865087ac.description.txt <-- build details
// │ │ └── 0f599edf5e16c2756fbcdd4c865087ac.vh
// │ ├── 29
// │ │ ├── 294717dd02a1cca5f2a0393fca2c5c22.o
// │ │ └── 294717dd02a1cca5f2a0393fca2c5c22.description.txt <-- build details
// │ ├── 62
// │ │ └── 620d60d6b81fdcb3cab030a37fd86996.h
// │ └── 76
// │ └── 7674f983ab9c487b0d7c1a0ad73840a5.c
pub struct CacheManager {
pub:
basepath string
pub mut:
vopts string
k2cpath map[string]string // key -> filesystem cache path for the object
}
fn new_cache_manager(opts []string) CacheManager {
mut vcache_basepath := os.getenv('VCACHE')
if vcache_basepath == '' {
vcache_basepath = os.join_path(os.home_dir(), '.vmodules', 'cache')
}
return CacheManager{
basepath: vcache_basepath
vopts: opts.join('|')
}
}
pub fn (mut cm CacheManager) key2cpath(key string) string {
mut cpath := cm.k2cpath[key]
if cpath == '' {
hk := cm.vopts + key
hash := md5.sum(hk.bytes()).hex()
prefix := hash[0..2]
cprefix_folder := os.join_path(cm.basepath, prefix)
cpath = os.join_path(cprefix_folder, hash)
if !os.is_dir(cprefix_folder) {
os.mkdir_all(cprefix_folder)
os.chmod(cprefix_folder, 0o777)
}
cm.k2cpath[key] = cpath
}
return cpath
}
pub fn (mut cm CacheManager) postfix_with_key2cpath(postfix string, key string) string {
return cm.key2cpath(key) + postfix
}
pub fn (mut cm CacheManager) exists(postfix string, key string) ?string {
fpath := cm.postfix_with_key2cpath(postfix, key)
if !os.exists(fpath) {
return error('does not exist yet')
}
return fpath
}
pub fn (mut cm CacheManager) save(postfix string, key string, content string) ?string {
fpath := cm.postfix_with_key2cpath(postfix, key)
os.write_file(fpath, content) ?
return fpath
}
pub fn (mut cm CacheManager) load(postfix string, key string) ?string {
fpath := cm.exists(postfix, key) ?
content := os.read_file(fpath) ?
return content
}