From 3c2202572b1f09a09ae321248dc4dbde22f4b7f5 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 17 Oct 2020 18:27:06 +0300 Subject: [PATCH] cgen: produce cleaner error on missing C headers (with optional explanation) (#6637) Implements support for `#include # Please install OpenSSL`. --- vlib/net/openssl/c.v | 2 +- vlib/v/ast/ast.v | 6 +- vlib/v/builder/cc.v | 89 +++++++++++-------- vlib/v/builder/msvc.v | 4 +- vlib/v/checker/checker.v | 4 +- .../checker/tests/missing_c_lib_header_1.out | 1 + .../v/checker/tests/missing_c_lib_header_1.vv | 6 ++ ...issing_c_lib_header_with_explanation_2.out | 1 + ...missing_c_lib_header_with_explanation_2.vv | 6 ++ vlib/v/compiler_errors_test.v | 7 ++ vlib/v/gen/cgen.v | 31 ++++++- vlib/v/parser/comptime.v | 12 +++ vlib/vweb/tests/vweb_test_server.v | 2 +- 13 files changed, 119 insertions(+), 52 deletions(-) create mode 100644 vlib/v/checker/tests/missing_c_lib_header_1.out create mode 100644 vlib/v/checker/tests/missing_c_lib_header_1.vv create mode 100644 vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.out create mode 100644 vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv diff --git a/vlib/net/openssl/c.v b/vlib/net/openssl/c.v index 00ff7363d7..9cb5d37db6 100644 --- a/vlib/net/openssl/c.v +++ b/vlib/net/openssl/c.v @@ -15,7 +15,7 @@ module openssl // Brew #flag darwin -I/usr/local/opt/openssl/include #flag darwin -L/usr/local/opt/openssl/lib -#include +#include # Please install OpenSSL #include #include diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 7278ac1bbb..cea9460a88 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -628,8 +628,10 @@ pub: mod string pos token.Position pub mut: - val string - kind string + val string // example: 'include # please install openssl // comment' + kind string // : 'include' + main string // : '' + msg string // : 'please install openssl' } /* diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index bfd874b65d..e860aebf44 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -10,7 +10,8 @@ import v.pref import term const ( - c_error_info = ' + c_verror_message_marker = 'VERROR_MESSAGE ' + c_error_info = ' ================== C error. This should never happen. @@ -20,7 +21,7 @@ https://github.com/vlang/v/issues/new/choose You can also use #help on Discord: https://discord.gg/vlang ' - no_compiler_error = ' + no_compiler_error = ' ================== Error: no C compiler detected. @@ -75,6 +76,37 @@ fn (mut v Builder) find_win_cc() ? { v.pref.ccompiler_type = pref.cc_from_string(v.pref.ccompiler) } +fn (mut v Builder) post_process_c_compiler_output(res os.Result) { + if res.exit_code == 0 { + return + } + for emsg_marker in [c_verror_message_marker, 'error: include file '] { + if res.output.contains(emsg_marker) { + emessage := res.output.all_after(emsg_marker).all_before('\n').all_before('\r').trim_right('\r\n') + verror(emessage) + } + } + if v.pref.is_debug { + eword := 'error:' + khighlight := if term.can_show_color_on_stdout() { term.red(eword) } else { eword } + println(res.output.trim_right('\r\n').replace(eword, khighlight)) + } else { + if res.output.len < 30 { + println(res.output) + } else { + elines := error_context_lines(res.output, 'error:', 1, 12) + println('==================') + for eline in elines { + println(eline) + } + println('...') + println('==================') + println('(Use `v -cg` to print the entire error message)\n') + } + } + verror(c_error_info) +} + fn (mut v Builder) cc() { if os.executable().contains('vfmt') { return @@ -491,49 +523,28 @@ fn (mut v Builder) cc() { verror(err) return } - if res.exit_code != 0 { - // the command could not be found by the system - if res.exit_code == 127 { - $if linux { - // TCC problems on linux? Try GCC. - if ccompiler.contains('tcc') { - v.pref.ccompiler = 'cc' - goto start - } - } - verror('C compiler error, while attempting to run: \n' + - '-----------------------------------------------------------\n' + '$cmd\n' + - '-----------------------------------------------------------\n' + 'Probably your C compiler is missing. \n' + - 'Please reinstall it, or make it available in your PATH.\n\n' + missing_compiler_info()) - } - if v.pref.is_debug { - eword := 'error:' - khighlight := if term.can_show_color_on_stdout() { term.red(eword) } else { eword } - println(res.output.trim_right('\r\n').replace(eword, khighlight)) - verror(c_error_info) - } else { - if res.output.len < 30 { - println(res.output) - } else { - elines := error_context_lines(res.output, 'error:', 1, 12) - println('==================') - for eline in elines { - println(eline) - } - println('...') - println('==================') - println('(Use `v -cg` to print the entire error message)\n') - } - verror(c_error_info) - } - } diff := time.ticks() - ticks + v.timing_message('C ${ccompiler:3}', diff) + if res.exit_code == 127 { + // the command could not be found by the system + $if linux { + // TCC problems on linux? Try GCC. + if ccompiler.contains('tcc') { + v.pref.ccompiler = 'cc' + goto start + } + } + verror('C compiler error, while attempting to run: \n' + + '-----------------------------------------------------------\n' + '$cmd\n' + + '-----------------------------------------------------------\n' + 'Probably your C compiler is missing. \n' + + 'Please reinstall it, or make it available in your PATH.\n\n' + missing_compiler_info()) + } + v.post_process_c_compiler_output(res) // Print the C command if v.pref.is_verbose { println('$ccompiler took $diff ms') println('=========\n') } - v.timing_message('C ${ccompiler:3}', diff) // Link it if we are cross compiling and need an executable /* if v.os == .linux && !linux_host && v.pref.build_mode != .build { diff --git a/vlib/v/builder/msvc.v b/vlib/v/builder/msvc.v index 974fc86804..6390151508 100644 --- a/vlib/v/builder/msvc.v +++ b/vlib/v/builder/msvc.v @@ -308,9 +308,7 @@ pub fn (mut v Builder) cc_msvc() { } diff := time.ticks() - ticks v.timing_message('C msvc', diff) - if res.exit_code != 0 { - verror(res.output) - } + v.post_process_c_compiler_output(res) // println(res) // println('C OUTPUT:') // Always remove the object file - it is completely unnecessary diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 2c7b050ad9..f6b3bdd173 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2459,7 +2459,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { return } if node.kind == 'include' { - mut flag := node.val[8..] + mut flag := node.main if flag.contains('@VROOT') { vroot := util.resolve_vroot(flag, c.file.path) or { c.error(err, node.pos) @@ -2475,7 +2475,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { } } else if node.kind == 'flag' { // #flag linux -lm - mut flag := node.val[5..] + mut flag := node.main // expand `@VROOT` to its absolute path if flag.contains('@VROOT') { flag = util.resolve_vroot(flag, c.file.path) or { diff --git a/vlib/v/checker/tests/missing_c_lib_header_1.out b/vlib/v/checker/tests/missing_c_lib_header_1.out new file mode 100644 index 0000000000..c6dc4b2fa4 --- /dev/null +++ b/vlib/v/checker/tests/missing_c_lib_header_1.out @@ -0,0 +1 @@ +builder error: Header file , needed for module `main` was not found. Please install the corresponding development headers. diff --git a/vlib/v/checker/tests/missing_c_lib_header_1.vv b/vlib/v/checker/tests/missing_c_lib_header_1.vv new file mode 100644 index 0000000000..d8bdb47832 --- /dev/null +++ b/vlib/v/checker/tests/missing_c_lib_header_1.vv @@ -0,0 +1,6 @@ +module main + +// The following header file is intentionally missing. +// The #include does not have the optional explanation part +// after a `#` sign: +#include diff --git a/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.out b/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.out new file mode 100644 index 0000000000..176ee7b9d7 --- /dev/null +++ b/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.out @@ -0,0 +1 @@ +builder error: Header file , needed for module `main` was not found. Please install missing C library. diff --git a/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv b/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv new file mode 100644 index 0000000000..a76cc0b2ce --- /dev/null +++ b/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv @@ -0,0 +1,6 @@ +module main + +// The following header file is intentionally missing. +// The part after `#` is an explanation message, that V will +// show, when it is not found: +#include # Please install missing C library diff --git a/vlib/v/compiler_errors_test.v b/vlib/v/compiler_errors_test.v index 2a2ad8a511..d8dd988695 100644 --- a/vlib/v/compiler_errors_test.v +++ b/vlib/v/compiler_errors_test.v @@ -85,6 +85,13 @@ fn (mut tasks []TaskDescription) run() { $if noskip ? { m_skip_files = [] } + $if tinyc { + // NB: tcc does not support __has_include, so the detection mechanism + // used for the other compilers does not work. It still provides a + // cleaner error message, than a generic C error, but without the explanation. + m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_1.vv' + m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv' + } for i in 0 .. tasks.len { if tasks[i].path in m_skip_files { tasks[i].is_skipped = true diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 30f17143bd..5e2a7ab3e8 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -960,18 +960,41 @@ fn (mut g Gen) stmt(node ast.Stmt) { ast.HashStmt { // #include etc if node.kind == 'include' { - if node.val.contains('.m') { + mut missing_message := 'Header file $node.main, needed for module `$node.mod` was not found.' + if node.msg != '' { + missing_message += ' ${node.msg}.' + } else { + missing_message += ' Please install the corresponding development headers.' + } + mut guarded_include := ' + |#if defined(__has_include) + | + |#if __has_include($node.main) + |#include $node.main + |#else + |#error VERROR_MESSAGE $missing_message + |#endif + | + |#else + |#include $node.main + |#endif + '.strip_margin() + if node.main == '' { + // fails with musl-gcc and msvc; but an unguarded include works: + guarded_include = '#include $node.main' + } + if node.main.contains('.m') { // Objective C code import, include it after V types, so that e.g. `string` is // available there g.definitions.writeln('// added by module `$node.mod`:') - g.definitions.writeln('#$node.val') + g.definitions.writeln(guarded_include) } else { g.includes.writeln('// added by module `$node.mod`:') - g.includes.writeln('#$node.val') + g.includes.writeln(guarded_include) } } else if node.kind == 'define' { g.includes.writeln('// defined by module `$node.mod`:') - g.includes.writeln('#$node.val') + g.includes.writeln('#define $node.main') } } ast.Import {} diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 807bf0493a..5ee33fe3ef 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -22,11 +22,23 @@ fn (mut p Parser) hash() ast.HashStmt { val := p.tok.lit kind := val.all_before(' ') p.next() + mut main := '' + mut msg := '' + content := val.all_after('$kind ').all_before('//') + if content.contains(' #') { + main = content.all_before(' #').trim_space() + msg = content.all_after(' #').trim_space() + } else { + main = content.trim_space() + msg = '' + } // p.trace('a.v', 'kind: ${kind:-10s} | pos: ${pos:-45s} | hash: $val') return ast.HashStmt{ mod: p.mod val: val kind: kind + main: main + msg: msg pos: pos } } diff --git a/vlib/vweb/tests/vweb_test_server.v b/vlib/vweb/tests/vweb_test_server.v index a6db6a70c6..18c7a11a73 100644 --- a/vlib/vweb/tests/vweb_test_server.v +++ b/vlib/vweb/tests/vweb_test_server.v @@ -70,7 +70,7 @@ pub fn (mut app App) settings(username string) vweb.Result { } ['/:user/:repo/settings'] -pub fn (mut app App) user_repo_settings(username, repository string) vweb.Result { +pub fn (mut app App) user_repo_settings(username string, repository string) vweb.Result { if username !in known_users { return app.vweb.not_found() }