cgen: produce cleaner error on missing C headers (with optional explanation) (#6637)

Implements support for `#include <openssl/rand.h> # Please install OpenSSL`.
pull/6638/head
Delyan Angelov 2020-10-17 18:27:06 +03:00 committed by GitHub
parent aad122334b
commit 3c2202572b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 119 additions and 52 deletions

View File

@ -15,7 +15,7 @@ module openssl
// Brew // Brew
#flag darwin -I/usr/local/opt/openssl/include #flag darwin -I/usr/local/opt/openssl/include
#flag darwin -L/usr/local/opt/openssl/lib #flag darwin -L/usr/local/opt/openssl/lib
#include <openssl/rand.h> #include <openssl/rand.h> # Please install OpenSSL
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/err.h> #include <openssl/err.h>

View File

@ -628,8 +628,10 @@ pub:
mod string mod string
pos token.Position pos token.Position
pub mut: pub mut:
val string val string // example: 'include <openssl/rand.h> # please install openssl // comment'
kind string kind string // : 'include'
main string // : '<openssl/rand.h>'
msg string // : 'please install openssl'
} }
/* /*

View File

@ -10,6 +10,7 @@ import v.pref
import term import term
const ( const (
c_verror_message_marker = 'VERROR_MESSAGE '
c_error_info = ' c_error_info = '
================== ==================
C error. This should never happen. C error. This should never happen.
@ -75,6 +76,37 @@ fn (mut v Builder) find_win_cc() ? {
v.pref.ccompiler_type = pref.cc_from_string(v.pref.ccompiler) 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() { fn (mut v Builder) cc() {
if os.executable().contains('vfmt') { if os.executable().contains('vfmt') {
return return
@ -491,9 +523,10 @@ fn (mut v Builder) cc() {
verror(err) verror(err)
return return
} }
if res.exit_code != 0 { diff := time.ticks() - ticks
// the command could not be found by the system v.timing_message('C ${ccompiler:3}', diff)
if res.exit_code == 127 { if res.exit_code == 127 {
// the command could not be found by the system
$if linux { $if linux {
// TCC problems on linux? Try GCC. // TCC problems on linux? Try GCC.
if ccompiler.contains('tcc') { if ccompiler.contains('tcc') {
@ -506,34 +539,12 @@ fn (mut v Builder) cc() {
'-----------------------------------------------------------\n' + 'Probably your C compiler is missing. \n' + '-----------------------------------------------------------\n' + 'Probably your C compiler is missing. \n' +
'Please reinstall it, or make it available in your PATH.\n\n' + missing_compiler_info()) 'Please reinstall it, or make it available in your PATH.\n\n' + missing_compiler_info())
} }
if v.pref.is_debug { v.post_process_c_compiler_output(res)
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
// Print the C command // Print the C command
if v.pref.is_verbose { if v.pref.is_verbose {
println('$ccompiler took $diff ms') println('$ccompiler took $diff ms')
println('=========\n') println('=========\n')
} }
v.timing_message('C ${ccompiler:3}', diff)
// Link it if we are cross compiling and need an executable // Link it if we are cross compiling and need an executable
/* /*
if v.os == .linux && !linux_host && v.pref.build_mode != .build { if v.os == .linux && !linux_host && v.pref.build_mode != .build {

View File

@ -308,9 +308,7 @@ pub fn (mut v Builder) cc_msvc() {
} }
diff := time.ticks() - ticks diff := time.ticks() - ticks
v.timing_message('C msvc', diff) v.timing_message('C msvc', diff)
if res.exit_code != 0 { v.post_process_c_compiler_output(res)
verror(res.output)
}
// println(res) // println(res)
// println('C OUTPUT:') // println('C OUTPUT:')
// Always remove the object file - it is completely unnecessary // Always remove the object file - it is completely unnecessary

View File

@ -2459,7 +2459,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
return return
} }
if node.kind == 'include' { if node.kind == 'include' {
mut flag := node.val[8..] mut flag := node.main
if flag.contains('@VROOT') { if flag.contains('@VROOT') {
vroot := util.resolve_vroot(flag, c.file.path) or { vroot := util.resolve_vroot(flag, c.file.path) or {
c.error(err, node.pos) c.error(err, node.pos)
@ -2475,7 +2475,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
} }
} else if node.kind == 'flag' { } else if node.kind == 'flag' {
// #flag linux -lm // #flag linux -lm
mut flag := node.val[5..] mut flag := node.main
// expand `@VROOT` to its absolute path // expand `@VROOT` to its absolute path
if flag.contains('@VROOT') { if flag.contains('@VROOT') {
flag = util.resolve_vroot(flag, c.file.path) or { flag = util.resolve_vroot(flag, c.file.path) or {

View File

@ -0,0 +1 @@
builder error: Header file <missing/folder/header1.h>, needed for module `main` was not found. Please install the corresponding development headers.

View File

@ -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 <missing/folder/header1.h>

View File

@ -0,0 +1 @@
builder error: Header file <missing/folder/header.h>, needed for module `main` was not found. Please install missing C library.

View File

@ -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 <missing/folder/header.h> # Please install missing C library

View File

@ -85,6 +85,13 @@ fn (mut tasks []TaskDescription) run() {
$if noskip ? { $if noskip ? {
m_skip_files = [] 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 { for i in 0 .. tasks.len {
if tasks[i].path in m_skip_files { if tasks[i].path in m_skip_files {
tasks[i].is_skipped = true tasks[i].is_skipped = true

View File

@ -960,18 +960,41 @@ fn (mut g Gen) stmt(node ast.Stmt) {
ast.HashStmt { ast.HashStmt {
// #include etc // #include etc
if node.kind == 'include' { 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 == '<errno.h>' {
// 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 // Objective C code import, include it after V types, so that e.g. `string` is
// available there // available there
g.definitions.writeln('// added by module `$node.mod`:') g.definitions.writeln('// added by module `$node.mod`:')
g.definitions.writeln('#$node.val') g.definitions.writeln(guarded_include)
} else { } else {
g.includes.writeln('// added by module `$node.mod`:') g.includes.writeln('// added by module `$node.mod`:')
g.includes.writeln('#$node.val') g.includes.writeln(guarded_include)
} }
} else if node.kind == 'define' { } else if node.kind == 'define' {
g.includes.writeln('// defined by module `$node.mod`:') g.includes.writeln('// defined by module `$node.mod`:')
g.includes.writeln('#$node.val') g.includes.writeln('#define $node.main')
} }
} }
ast.Import {} ast.Import {}

View File

@ -22,11 +22,23 @@ fn (mut p Parser) hash() ast.HashStmt {
val := p.tok.lit val := p.tok.lit
kind := val.all_before(' ') kind := val.all_before(' ')
p.next() 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') // p.trace('a.v', 'kind: ${kind:-10s} | pos: ${pos:-45s} | hash: $val')
return ast.HashStmt{ return ast.HashStmt{
mod: p.mod mod: p.mod
val: val val: val
kind: kind kind: kind
main: main
msg: msg
pos: pos pos: pos
} }
} }

View File

@ -70,7 +70,7 @@ pub fn (mut app App) settings(username string) vweb.Result {
} }
['/:user/:repo/settings'] ['/: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 { if username !in known_users {
return app.vweb.not_found() return app.vweb.not_found()
} }