From f9d5c0110fa9b57a21dc1b6400538ca2480f9163 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 29 Feb 2020 15:23:45 +0200 Subject: [PATCH] compiler: @VMODULE --- doc/docs.md | 25 ++- examples/.gitignore | 1 + v.mod | 8 + vlib/compiler/aparser.v | 3 +- vlib/compiler/comptime.v | 27 ++-- vlib/compiler/main.v | 6 +- vlib/compiler/modules.v | 12 +- .../tests/project_with_c_code/.v.mod.stop | 4 + .../tests/project_with_c_code/mod1/v.mod | 7 + .../tests/project_with_c_code/mod1/wrapper.v | 4 +- vlib/compiler/v_mod_cache.v | 153 ++++++++++++++++++ 11 files changed, 223 insertions(+), 27 deletions(-) create mode 100644 v.mod create mode 100644 vlib/compiler/tests/project_with_c_code/.v.mod.stop create mode 100644 vlib/compiler/tests/project_with_c_code/mod1/v.mod create mode 100644 vlib/compiler/v_mod_cache.v diff --git a/doc/docs.md b/doc/docs.md index 0c133b8282..c9c0e2ab20 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -1274,16 +1274,31 @@ NB: For now you have to use one flag per line: You can also add C code, in your V module. For example, lets say that your C code is located in a folder named 'c' inside your module folder. Then: +* Put a v.mod file inside the toplevel folder of your module (if you +created your module with `v create` you already have v.mod file). For +example: ```v -#flag -I @VMODULE/c -#flag @VMODULE/c/implementation.o -#include "header.h" +Module { + name: 'mymodule', + description: 'My nice module wraps a simple C library.', + version: '0.0.1' + dependencies: [] +} ``` -... will make V look for an compiled .o file in your module folder/c/implementation.o . +* Add these lines to the top of your module: +```v +#flag -I @VROOT/c +#flag @VROOT/c/implementation.o +#include "header.h" +``` +NB: @VROOT will be replaced by V with the *nearest parent folder, where there is a v.mod file*. + +The instructions above will make V look for an compiled .o file in your module folder/c/implementation.o . If V finds it, the .o file will get linked to the main executable, that used the module. -If it does not find it, V assumes that there is a `@VMODULE/c/implementation.c` file, +If it does not find it, V assumes that there is a `@VROOT/c/implementation.c` file, and tries to compile it to a .o file, then will use that. + This allows you to have C code, that is contained in a V module, so that its distribution is easier. You can see a complete example for using C code in a V wrapper module here: [minimal V project, that has a module, which contains C code](https://github.com/vlang/v/tree/master/vlib/compiler/tests/project_with_c_code) diff --git a/examples/.gitignore b/examples/.gitignore index 287844245b..e3602845fd 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -14,6 +14,7 @@ /fibonacci /sqlite /path_tracing +/gg2 *.ppm empty_gg_freetype game_of_life/life_gg diff --git a/v.mod b/v.mod new file mode 100644 index 0000000000..afecfd9d13 --- /dev/null +++ b/v.mod @@ -0,0 +1,8 @@ +#V Project# + +Module { + name: 'V', + description: 'The V programming language.', + version: '0.1.25' + dependencies: [] +} diff --git a/vlib/compiler/aparser.v b/vlib/compiler/aparser.v index 7b4bbb29eb..917e3e9d2d 100644 --- a/vlib/compiler/aparser.v +++ b/vlib/compiler/aparser.v @@ -176,10 +176,11 @@ fn (v mut V) new_parser_from_file(path string) Parser { } } mut p := v.new_parser(new_scanner_file(path)) + path_dir := os.realpath(filepath.dir(path)) p = { p | file_path:path, - file_path_dir:filepath.dir(path), + file_path_dir: path_dir, file_name:path.all_after(filepath.separator), file_platform:path_platform, file_pcguard:path_pcguard, diff --git a/vlib/compiler/comptime.v b/vlib/compiler/comptime.v index 05fe2f9740..055200beac 100644 --- a/vlib/compiler/comptime.v +++ b/vlib/compiler/comptime.v @@ -21,7 +21,7 @@ fn (p mut Parser) comp_time() { } name := p.check_name() p.fspace() - + if name in supported_platforms { os := os_from_string(name) ifdef_name := os_name_to_ifdef(name) @@ -127,13 +127,13 @@ fn (p mut Parser) comp_time() { // // The ? sign, means that `custom` is optional, and when // it is not present at all at the command line, then the - // block will just be ignored, instead of erroring. + // block will just be ignored, instead of erroring. if p.tok == .question { p.next() } p.comptime_if_block('CUSTOM_DEFINE_${name}', not) - } else { - if p.tok == .question { + } else { + if p.tok == .question { p.next() p.comptime_if_block('CUSTOM_DEFINE_${name}', not) }else{ @@ -242,12 +242,18 @@ fn (p mut Parser) chash() { if hash.starts_with('flag ') { if p.first_pass() { mut flag := hash[5..] - // expand `@VROOT` `@VMOD` to absolute path - flag = flag.replace('@VMODULE', p.file_path_dir) - flag = flag.replace('@VROOT', p.vroot) - flag = flag.replace('@VPATH', p.pref.vpath) - flag = flag.replace('@VLIB_PATH', p.pref.vlib_path) - flag = flag.replace('@VMOD', v_modules_path) + // expand `@VROOT` to its absolute path + if flag.contains('@VROOT') { + vmod_file_location := p.v.mod_file_cacher.get( p.file_path_dir ) + if vmod_file_location.vmod_file.len == 0 { + // There was no actual v.mod file found. + p.error_with_token_index('To use @VROOT, you need' + + ' to have a "v.mod" file in ${p.file_path_dir},' + + ' or in one of its parent folders.', + p.cur_tok_index() - 1) + } + flag = flag.replace('@VROOT', vmod_file_location.vmod_folder ) + } // p.log('adding flag "$flag"') _ = p.table.parse_cflag(flag, p.mod, p.v.pref.compile_defines_all ) or { p.error_with_token_index(err, p.cur_tok_index() - 1) @@ -561,4 +567,3 @@ pub fn (e &$typ.name) has(flag $typ.name) bool { return int(*e)&(1 << int(flag)) p.cgen.fns << 'void ${typ.name}_toggle($typ.name *e, $typ.name flag);' p.cgen.fns << 'bool ${typ.name}_has($typ.name *e, $typ.name flag);' } - diff --git a/vlib/compiler/main.v b/vlib/compiler/main.v index b214daea64..7cc9627610 100644 --- a/vlib/compiler/main.v +++ b/vlib/compiler/main.v @@ -36,6 +36,7 @@ enum Pass { pub struct V { pub mut: + mod_file_cacher &ModFileCacher // used during lookup for v.mod to support @VROOT out_name_c string // name of the temporary C file files []string // all V files that need to be parsed and compiled compiled_dir string // contains os.realpath() of the dir of the final file beeing compiled, or the dir itself when doing `v .` @@ -68,6 +69,7 @@ pub fn new_v(pref &pref.Preferences) &V { compiled_dir:=if os.is_dir(rdir) { rdir } else { filepath.dir(rdir) } return &V{ + mod_file_cacher: new_mod_file_cacher() compiled_dir:compiled_dir// if os.is_dir(rdir) { rdir } else { filepath.dir(rdir) } table: new_table(pref.obfuscate) out_name_c: out_name_c @@ -815,8 +817,8 @@ pub fn (v mut V) parse_lib_imports() { if mod in done_imports { continue } - import_path := v.find_module_path(mod) or { - v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)', v.parsers[i].import_table.get_import_tok_idx(mod)) + import_path := v.parsers[i].find_module_path(mod) or { + v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)\n$err', v.parsers[i].import_table.get_import_tok_idx(mod)) break } vfiles := v.v_files_from_dir(import_path) diff --git a/vlib/compiler/modules.v b/vlib/compiler/modules.v index e0d5df565c..7d59c14da5 100644 --- a/vlib/compiler/modules.v +++ b/vlib/compiler/modules.v @@ -182,21 +182,21 @@ fn (v mut V) set_module_lookup_paths() { } } -fn (v &V) find_module_path(mod string) ?string { - mod_path := v.module_path(mod) - for lookup_path in v.module_lookup_paths { +fn (p &Parser) find_module_path(mod string) ?string { + mod_path := p.v.module_path(mod) + for lookup_path in p.v.module_lookup_paths { try_path := filepath.join(lookup_path,mod_path) - if v.pref.is_verbose { + if p.v.pref.is_verbose { println(' >> trying to find $mod in $try_path ...') } if os.is_dir(try_path) { - if v.pref.is_verbose { + if p.v.pref.is_verbose { println(' << found $try_path .') } return try_path } } - return error('module "$mod" not found') + return error('module "$mod" not found in ${p.v.module_lookup_paths}') } [inline] diff --git a/vlib/compiler/tests/project_with_c_code/.v.mod.stop b/vlib/compiler/tests/project_with_c_code/.v.mod.stop new file mode 100644 index 0000000000..abdf0f97cf --- /dev/null +++ b/vlib/compiler/tests/project_with_c_code/.v.mod.stop @@ -0,0 +1,4 @@ +Do not delete this file. +It is used by V to stop the lookup for v.mod, +so that the top level vlib/v.mod is not found, +if you delete mod1/v.mod . diff --git a/vlib/compiler/tests/project_with_c_code/mod1/v.mod b/vlib/compiler/tests/project_with_c_code/mod1/v.mod new file mode 100644 index 0000000000..9a9b5dc167 --- /dev/null +++ b/vlib/compiler/tests/project_with_c_code/mod1/v.mod @@ -0,0 +1,7 @@ +#V Module# + +Module { + name: 'mod1', + description: 'A simple module, containing C code.', + dependencies: [] +} diff --git a/vlib/compiler/tests/project_with_c_code/mod1/wrapper.v b/vlib/compiler/tests/project_with_c_code/mod1/wrapper.v index f31d546a07..1de3be849a 100644 --- a/vlib/compiler/tests/project_with_c_code/mod1/wrapper.v +++ b/vlib/compiler/tests/project_with_c_code/mod1/wrapper.v @@ -1,7 +1,7 @@ module mod1 -#flag -I @VMODULE/c -#flag @VMODULE/c/implementation.o +#flag -I @VROOT/c +#flag @VROOT/c/implementation.o #include "header.h" diff --git a/vlib/compiler/v_mod_cache.v b/vlib/compiler/v_mod_cache.v new file mode 100644 index 0000000000..b066294b82 --- /dev/null +++ b/vlib/compiler/v_mod_cache.v @@ -0,0 +1,153 @@ +module compiler + +import os +import filepath + + +// This file provides a caching mechanism for seeking quickly whether a +// given folder has a v.mod file in it or in any of its parent folders. +// +// ModFileCacher.get(folder) works in such a way, that given this tree: +// examples/hanoi.v +// vlib/v.mod +// vlib/compiler/tests/project_with_c_code/mod1/v.mod +// vlib/compiler/tests/project_with_c_code/mod1/wrapper.v +// ----------------- +// ModFileCacher.get('examples') +// => ModFileAndFolder{'', 'examples'} +// ModFileCacher.get('vlib/compiler/tests') +// => ModFileAndFolder{'vlib/v.mod', 'vlib'} +// ModFileCacher.get('vlib/compiler') +// => ModFileAndFolder{'vlib/v.mod', 'vlib'} +// ModFileCacher.get('vlib/project_with_c_code/mod1') +// => ModFileAndFolder{'vlib/project_with_c_code/mod1/v.mod', 'vlib/project_with_c_code/mod1'} + + +struct ModFileAndFolder { + // vmod_file contains the full path of the found 'v.mod' file, or '' + // if no 'v.mod' file was found in file_path_dir, or in its parent folders. + vmod_file string + + // vmod_folder contains the file_path_dir, if there is no 'v.mod' file in + // *any* of the parent folders, otherwise it is the first parent folder, + // where a v.mod file was found. + vmod_folder string +} + +struct ModFileCacher { +mut: + cache map[string]ModFileAndFolder + // folder_files caches os.ls(key) + folder_files map[string][]string +} + +fn new_mod_file_cacher() &ModFileCacher { + return &ModFileCacher{} +} + +fn (mcache &ModFileCacher) dump() { + $if debug { + eprintln('ModFileCacher DUMP:') + eprintln(' ModFileCacher.cache:') + for k,v in mcache.cache { + eprintln(' K: ${k:-32s} | V: "${v.vmod_file:32s}" | "${v.vmod_folder:32s}" ') + } + eprintln(' ModFileCacher.folder_files:') + for k,v in mcache.folder_files { + eprintln(' K: ${k:-32s} | V: ${v.str()}') + } + } +} + +fn (mcache mut ModFileCacher) get(mfolder string) ModFileAndFolder { + if mfolder in mcache.cache { + return mcache.cache[ mfolder ] + } + traversed_folders, res := mcache.traverse( mfolder ) + for tfolder in traversed_folders { + mcache.add( tfolder, res ) + } + return res +} + +fn (cacher mut ModFileCacher) add(path string, result ModFileAndFolder) { + cacher.cache[ path ] = result +} + +fn (mcache mut ModFileCacher) traverse(mfolder string) ([]string, ModFileAndFolder) { + mut cfolder := mfolder + mut folders_so_far := [cfolder] + mut levels := 0 + for { + $if debug { + eprintln('pdir2vmod mfolder: ${mfolder:-32s} | cfolder: ${cfolder:-20s} | levels: $levels') + } + if levels > 255 { + break + } + if cfolder == '/' || cfolder == '' { + break + } + if cfolder in mcache.cache { + res := mcache.cache[ cfolder ] + if res.vmod_file.len == 0 { + mcache.mark_folders_as_vmod_free( folders_so_far ) + }else{ + mcache.mark_folders_with_vmod( folders_so_far, res ) + } + return []string, res + } + files := mcache.get_files( cfolder ) + if 'v.mod' in files { + // TODO: actually read the v.mod file and parse its contents to see + // if its source folder is different + res := ModFileAndFolder{ vmod_file: filepath.join( cfolder, 'v.mod'), vmod_folder: cfolder } + $if debug { + eprintln('FOUND v.mod:') + eprintln(' ModFileAndFolder{ vmod_file: $res.vmod_file , vmod_folder: $res.vmod_folder } ') + } + return folders_so_far, res + } + if mcache.check_for_stop( cfolder, files ) { + break + } + cfolder = filepath.basedir( cfolder ) + folders_so_far << cfolder + levels++ + } + mcache.mark_folders_as_vmod_free( folders_so_far ) + return [mfolder], ModFileAndFolder{ vmod_file: '', vmod_folder: mfolder } +} + +fn (mcache mut ModFileCacher) mark_folders_with_vmod( folders_so_far []string, vmod ModFileAndFolder ) { + for f in folders_so_far { + mcache.add( f, vmod ) + } +} + +fn (mcache mut ModFileCacher) mark_folders_as_vmod_free( folders_so_far []string ) { + // No need to check these folders anymore, + // because their parents do not contain v.mod files + for f in folders_so_far { + mcache.add( f, ModFileAndFolder{ vmod_file: '', vmod_folder: f } ) + } +} + +const ( MOD_FILE_STOP_PATHS = ['.git', '.hg', '.svn', '.v.mod.stop' ] ) +fn (mcache &ModFileCacher) check_for_stop(cfolder string, files []string) bool { + for i in MOD_FILE_STOP_PATHS { + if i in files { + return true + } + } + return false +} + +fn (mcache mut ModFileCacher) get_files(cfolder string) []string { + if cfolder in mcache.folder_files { + return mcache.folder_files[ cfolder ] + } + files := os.ls(cfolder) or { return [] } + mcache.folder_files[ cfolder ] = files + return files +}