diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index c0c0ccfb7bd6e8..bce0310aa763b3 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -187,6 +187,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession { skip_files << 'examples/coroutines/simple_coroutines.v' $if msvc { skip_files << 'vlib/v/tests/const_comptime_eval_before_vinit_test.v' // _constructor used + skip_files << 'vlib/v/tests/project_with_cpp_code/compiling_cpp_files_with_a_cplusplus_compiler_test.v' } $if solaris { skip_files << 'examples/gg/gg2.v' diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index faa6a288708003..12f5923ecc6e95 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -170,6 +170,7 @@ const ( 'vlib/orm/orm_insert_reserved_name_test.v', 'vlib/v/tests/orm_sub_array_struct_test.v', 'vlib/v/tests/orm_handle_error_for_select_from_not_created_table_test.v', + 'vlib/v/tests/project_with_cpp_code/compiling_cpp_files_with_a_cplusplus_compiler_test.v', // fails compilation with: undefined reference to vtable for __cxxabiv1::__function_type_info' ] skip_with_werror = [ 'do_not_remove', @@ -243,6 +244,7 @@ const ( 'vlib/v/tests/const_fixed_array_containing_references_to_itself_test.v', // error C2099: initializer is not a constant 'vlib/v/tests/const_and_global_with_same_name_test.v', // error C2099: initializer is not a constant 'vlib/v/tests/sumtype_as_cast_test.v', // error: cannot support compound statement expression ({expr; expr; expr;}) + 'vlib/v/tests/project_with_cpp_code/compiling_cpp_files_with_a_cplusplus_compiler_test.v', // TODO ] skip_on_windows = [ 'do_not_remove', @@ -333,6 +335,7 @@ fn main() { } if github_job == 'windows-tcc' { + tsession.skip_files << 'vlib/v/tests/project_with_cpp_code/compiling_cpp_files_with_a_cplusplus_compiler_test.v' // TODO: fix these ASAP tsession.skip_files << 'vlib/net/tcp_test.v' tsession.skip_files << 'vlib/net/udp_test.v' diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index 01ca26a054b630..a820890c5d9975 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -10,6 +10,13 @@ import v.util import v.vcache import term +const ( + c_std = 'c99' + c_std_gnu = 'gnu99' + cpp_std = 'c++17' + cpp_std_gnu = 'gnu++17' +) + const c_verror_message_marker = 'VERROR_MESSAGE ' const c_error_info = ' @@ -131,13 +138,6 @@ fn (mut v Builder) setup_ccompiler_options(ccompiler string) { // arguments for the C compiler ccoptions.args = [v.pref.cflags] ccoptions.ldflags = [v.pref.ldflags] - if !v.pref.no_std { - if v.pref.os == .linux { - ccoptions.args << '-std=gnu99 -D_DEFAULT_SOURCE' - } else { - ccoptions.args << '-std=c99 -D_DEFAULT_SOURCE' - } - } ccoptions.wargs = [ '-Wall', '-Wextra', @@ -343,6 +343,14 @@ fn (mut v Builder) setup_ccompiler_options(ccompiler string) { if v.pref.os == .macos { ccoptions.source_args << '-x none' } + if !v.pref.no_std { + if v.pref.os == .linux { + ccoptions.source_args << '-std=${builder.c_std_gnu}' + } else { + ccoptions.source_args << '-std=${builder.c_std}' + } + ccoptions.source_args << '-D_DEFAULT_SOURCE' + } // Min macos version is mandatory I think? if v.pref.os == .macos { ccoptions.post_args << '-mmacosx-version-min=10.7' @@ -396,7 +404,7 @@ fn (mut v Builder) setup_ccompiler_options(ccompiler string) { // setup the cache too, so that different compilers/options do not interfere: v.pref.cache_manager.set_temporary_options(v.thirdparty_object_args(v.ccoptions, [ ccoptions.guessed_compiler, - ])) + ], false)) } fn (v &Builder) all_args(ccoptions CcompilerOptions) []string { @@ -437,8 +445,26 @@ fn (v &Builder) all_args(ccoptions CcompilerOptions) []string { return all } -fn (v &Builder) thirdparty_object_args(ccoptions CcompilerOptions, middle []string) []string { +fn (v &Builder) thirdparty_object_args(ccoptions CcompilerOptions, middle []string, cpp_file bool) []string { mut all := []string{} + + if !v.pref.no_std { + if v.pref.os == .linux { + if cpp_file { + all << '-std=${builder.cpp_std_gnu}' + } else { + all << '-std=${builder.c_std_gnu}' + } + } else { + if cpp_file { + all << '-std=${builder.cpp_std}' + } else { + all << '-std=${builder.c_std}' + } + } + all << '-D_DEFAULT_SOURCE' + } + all << ccoptions.env_cflags all << ccoptions.args all << middle @@ -888,7 +914,13 @@ fn (mut b Builder) build_thirdparty_obj_files() { fn (mut v Builder) build_thirdparty_obj_file(mod string, path string, moduleflags []cflag.CFlag) { obj_path := os.real_path(path) - cfile := '${obj_path[..obj_path.len - 2]}.c' + mut cfile := '${obj_path[..obj_path.len - 2]}.c' + mut cpp_file := false + if !os.exists(cfile) { + // Guessed C file does not exist, so it may be a CPP file + cfile += 'pp' + cpp_file = true + } opath := v.pref.cache_manager.mod_postfix_with_key2cpath(mod, '.o', obj_path) mut rebuild_reason_message := '${obj_path} not found, building it in ${opath} ...' if os.exists(opath) { @@ -918,8 +950,17 @@ fn (mut v Builder) build_thirdparty_obj_file(mod string, path string, moduleflag all_options << moduleflags.c_options_before_target() all_options << '-o ${os.quoted_path(opath)}' all_options << '-c ${os.quoted_path(cfile)}' - cc_options := v.thirdparty_object_args(v.ccoptions, all_options).join(' ') - cmd := '${v.quote_compiler_name(v.pref.ccompiler)} ${cc_options}' + cc_options := v.thirdparty_object_args(v.ccoptions, all_options, cpp_file).join(' ') + + // If the third party object file requires a CPP file compilation, switch to a CPP compiler + mut ccompiler := v.pref.ccompiler + if cpp_file { + $if trace_thirdparty_obj_files ? { + println('>>> build_thirdparty_obj_files switched from compiler "${ccompiler}" to "${v.pref.cppcompiler}"') + } + ccompiler = v.pref.cppcompiler + } + cmd := '${v.quote_compiler_name(ccompiler)} ${cc_options}' $if trace_thirdparty_obj_files ? { println('>>> build_thirdparty_obj_files cmd: ${cmd}') } diff --git a/vlib/v/pref/default.v b/vlib/v/pref/default.v index 6707d37212eebc..47ed9be2f1fa26 100644 --- a/vlib/v/pref/default.v +++ b/vlib/v/pref/default.v @@ -119,6 +119,9 @@ pub fn (mut p Preferences) fill_with_defaults() { if p.ccompiler == '' { p.default_c_compiler() } + if p.cppcompiler == '' { + p.default_cpp_compiler() + } p.find_cc_if_cross_compiling() p.ccompiler_type = cc_from_string(p.ccompiler) p.is_test = p.path.ends_with('_test.v') || p.path.ends_with('_test.vv') @@ -244,6 +247,14 @@ pub fn (mut p Preferences) default_c_compiler() { return } +pub fn (mut p Preferences) default_cpp_compiler() { + if p.ccompiler.contains('clang') { + p.cppcompiler = 'clang++' + return + } + p.cppcompiler = 'c++' +} + pub fn vexe_path() string { vexe := os.getenv('VEXE') if vexe != '' { diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 28f54358c44ce3..37e52d235214e8 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -163,6 +163,7 @@ pub mut: m64 bool // true = generate 64-bit code, defaults to x64 ccompiler string // the name of the C compiler used ccompiler_type CompilerType // the type of the C compiler used + cppcompiler string // the name of the CPP compiler used third_party_option string building_v bool no_bounds_checking bool // `-no-bounds-checking` turns off *all* bounds checks for all functions at runtime, as if they all had been tagged with `[direct_array_access]` @@ -738,6 +739,10 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin res.build_options << '${arg} "${res.ccompiler}"' i++ } + '-c++' { + res.cppcompiler = cmdline.option(current_args, '-c++', 'c++') + i++ + } '-checker-match-exhaustive-cutoff-limit' { res.checker_match_exhaustive_cutoff_limit = cmdline.option(current_args, arg, '10').int() diff --git a/vlib/v/tests/project_with_cpp_code/compiling_cpp_files_with_a_cplusplus_compiler_test.v b/vlib/v/tests/project_with_cpp_code/compiling_cpp_files_with_a_cplusplus_compiler_test.v new file mode 100644 index 00000000000000..13a341f8e27ea9 --- /dev/null +++ b/vlib/v/tests/project_with_cpp_code/compiling_cpp_files_with_a_cplusplus_compiler_test.v @@ -0,0 +1,19 @@ +module main + +#flag @VMODROOT/implementation.o +#include "@VMODROOT/implementation.h" + +fn C.sizeof_char() int + +fn test_the_implementation_object_file_was_compiled_with_a_c_plus_plus_compiler() { + res := C.sizeof_char() + dump(res) + if res == sizeof(int) { + eprintln('implementation.o was compiled with a C compiler. Fail.') + } else if res == sizeof(char) { + println('implementation.o was compiled with a C++ compiler. Good.') + } else { + eprintln('¯\\_(ツ)_/¯ ... unknown C/C++ compiler') + } + assert res == sizeof(char) +} diff --git a/vlib/v/tests/project_with_cpp_code/implementation.cpp b/vlib/v/tests/project_with_cpp_code/implementation.cpp new file mode 100644 index 00000000000000..b375b7199e63b0 --- /dev/null +++ b/vlib/v/tests/project_with_cpp_code/implementation.cpp @@ -0,0 +1,9 @@ +// This file should be compiled with a C++ compiler: +extern "C" { + int sizeof_char(void); +} + +int sizeof_char(void) { + // see https://stackoverflow.com/a/12887719/1023403 + return sizeof('a'); // 4 for C compilers, 1 for C++ compilers +} diff --git a/vlib/v/tests/project_with_cpp_code/implementation.h b/vlib/v/tests/project_with_cpp_code/implementation.h new file mode 100644 index 00000000000000..a464b15ca2733a --- /dev/null +++ b/vlib/v/tests/project_with_cpp_code/implementation.h @@ -0,0 +1 @@ +int sizeof_char(void); diff --git a/vlib/v/tests/project_with_cpp_code/v.mod b/vlib/v/tests/project_with_cpp_code/v.mod new file mode 100644 index 00000000000000..26faa54e07e0df --- /dev/null +++ b/vlib/v/tests/project_with_cpp_code/v.mod @@ -0,0 +1,5 @@ +Module { + name: 'project_with_cpp_code', + description: 'A simple project, containing a .cpp file with C++ code, that has to be compiled with a C++ compiler.', + dependencies: [] +}