// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module compiler import ( os time filepath ) fn todo() { } fn no_mingw_installed() bool { $if !windows { panic('no_mingw_installed() can only run on Windows') } os.exec('gcc -v') or { println('mingw not found, trying to build with msvc...') return true } return false } fn (v mut V) cc() { v.build_thirdparty_obj_files() vexe := vexe_path() vdir := os.dir(vexe) // Just create a C/JavaScript file and exit // for example: `v -o v.c compiler` if v.out_name.ends_with('.c') || v.out_name.ends_with('.js') { // Translating V code to JS by launching vjs. // Using a separate process for V.js is for performance mostly, // to avoid constant is_js checks. $if !js { if v.out_name.ends_with('.js') { vjs_path := vexe + 'js' if !os.file_exists(vjs_path) { println('V.js compiler not found, building...') // Build V.js. Specifying `-os js` makes V include // only _js.v files and ignore _c.v files. ret := os.system('$vexe -o $vjs_path -os js $vdir/v.v') if ret == 0 { println('Done.') } else { println('Failed.') exit(1) } } ret := os.system('$vjs_path -o $v.out_name $v.dir') if ret == 0 { println('Done. Run it with `node $v.out_name`') println('JS backend is at a very early stage.') } } } os.mv(v.out_name_c, v.out_name) exit(0) } // Cross compiling for Windows if v.os == .windows { $if !windows { v.cc_windows_cross() return } } $if windows { if v.pref.ccompiler == 'msvc' || no_mingw_installed() { v.cc_msvc() return } } // TCC on Linux by default, unless -cc was provided // TODO if -cc = cc, TCC is still used, default compiler should be // used instead. if v.pref.fast { $if linux { $if !android { tcc_3rd := '$vdir/thirdparty/tcc/bin/tcc' //println('tcc third "$tcc_3rd"') tcc_path := '/var/tmp/tcc/bin/tcc' if os.file_exists(tcc_3rd) && !os.file_exists(tcc_path) { //println('moving tcc') // if there's tcc in thirdparty/, that means this is // a prebuilt V_linux.zip. // Until the libtcc1.a bug is fixed, we neeed to move // it to /var/tmp/ os.system('mv $vdir/thirdparty/tcc /var/tmp/') } if v.pref.ccompiler == 'cc' && os.file_exists(tcc_path) { // TODO tcc bug, needs an empty libtcc1.a fila //os.mkdir('/var/tmp/tcc/lib/tcc/') //os.create('/var/tmp/tcc/lib/tcc/libtcc1.a') v.pref.ccompiler = tcc_path } } } $else { verror('-fast is only supported on Linux right now') } } //linux_host := os.user_os() == 'linux' v.log('cc() isprod=$v.pref.is_prod outname=$v.out_name') mut a := [v.pref.cflags, '-std=gnu11', '-w'] // arguments for the C compiler if v.pref.is_so { a << '-shared -fPIC '// -Wl,-z,defs' v.out_name = v.out_name + '.so' } if v.pref.is_bare { a << '-nostdlib $vdir/vlib/os/bare/bare.S' } if v.pref.build_mode == .build_module { // Create the modules & out directory if it's not there. mut out_dir := if v.dir.starts_with('vlib') { '$v_modules_path${os.path_separator}cache${os.path_separator}$v.dir' } else { '$v_modules_path${os.path_separator}$v.dir' } pdir := out_dir.all_before_last(os.path_separator) if !os.dir_exists(pdir) { os.mkdir_all(pdir) } v.out_name = '${out_dir}.o' //v.out_name println('Building ${v.out_name}...') } debug_mode := v.pref.is_debug mut debug_options := '-g' mut optimization_options := '-O2' if v.pref.ccompiler.contains('clang') { if debug_mode { debug_options = '-g -O0 -no-pie' } optimization_options = '-O3 -flto' } if v.pref.ccompiler.contains('gcc') { if debug_mode { debug_options = '-g3 -no-pie' } optimization_options = '-O3 -fno-strict-aliasing -flto' } if debug_mode { a << debug_options } if v.pref.is_prod { a << optimization_options } if debug_mode && os.user_os() != 'windows'{ a << ' -rdynamic ' // needed for nicer symbolic backtraces } if v.pref.ccompiler != 'msvc' && v.os != .freebsd { a << '-Werror=implicit-function-declaration' } for f in v.generate_hotcode_reloading_compiler_flags() { a << f } mut libs := ''// builtin.o os.o http.o etc if v.pref.build_mode == .build_module { a << '-c' } else if v.pref.is_cache { builtin_o_path := filepath.join(v_modules_path, 'cache', 'vlib', 'builtin.o') a << builtin_o_path.replace('builtin.o', 'strconv.o') // TODO hack no idea why this is needed if os.file_exists(builtin_o_path) { libs = builtin_o_path } else { println('$builtin_o_path not found... building module builtin') os.system('$vexe build module vlib${os.path_separator}builtin') } for imp in v.table.imports { if imp.contains('vweb') { continue } // not working if imp == 'webview' { continue } imp_path := imp.replace('.', os.path_separator) path := '$v_modules_path${os.path_separator}cache${os.path_separator}vlib${os.path_separator}${imp_path}.o' //println('adding ${imp_path}.o') if os.file_exists(path) { libs += ' ' + path } else { println('$path not found... building module $imp') if path.ends_with('vlib/ui.o') { println('copying ui...') os.cp('$vdir/thirdparty/ui/ui.o', path) or { panic('error copying ui files') } os.cp('$vdir/thirdparty/ui/ui.vh', v_modules_path + '/vlib/ui.vh') or { panic('error copying ui files') } } else { os.system('$vexe build module vlib${os.path_separator}$imp_path') } } if path.ends_with('vlib/ui.o') { a << '-framework Cocoa -framework Carbon' } } } if v.pref.sanitize { a << '-fsanitize=leak' } // Cross compiling linux TODO /* sysroot := '/tmp/lld/linuxroot/' if v.os == .linux && !linux_host { // Build file.o a << '-c --sysroot=$sysroot -target x86_64-linux-gnu' // Right now `out_name` can be `file`, not `file.o` if !v.out_name.ends_with('.o') { v.out_name = v.out_name + '.o' } } */ // Cross compiling windows // // Output executable name a << '-o "$v.out_name"' if os.dir_exists(v.out_name) { verror('\'$v.out_name\' is a directory') } // macOS code can include objective C TODO remove once objective C is replaced with C if v.os == .mac { a << '-x objective-c' } // The C file we are compiling a << '"$v.out_name_c"' if v.os == .mac { a << '-x none' } // Min macos version is mandatory I think? if v.os == .mac { a << '-mmacosx-version-min=10.7' } cflags := v.get_os_cflags() // add .o files a << cflags.c_options_only_object_files() // add all flags (-I -l -L etc) not .o files a << cflags.c_options_without_object_files() a << libs // Without these libs compilation will fail on Linux // || os.user_os() == 'linux' if v.pref.build_mode != .build_module && (v.os == .linux || v.os == .freebsd || v.os == .openbsd || v.os == .netbsd || v.os == .dragonfly || v.os == .solaris) { a << '-lm -lpthread ' // -ldl is a Linux only thing. BSDs have it in libc. if v.os == .linux { a << ' -ldl ' } } if v.os == .js && os.user_os() == 'linux' { a << '-lm' } args := a.join(' ') start: todo() // TODO remove cmd := '${v.pref.ccompiler} $args' // Run if v.pref.show_c_cmd || v.pref.is_verbose { println('\n==========') println(cmd) } ticks := time.ticks() res := os.exec(cmd) or { // C compilation failed. // If we are on Windows, try msvc println('C compilation failed.') /* if os.user_os() == 'windows' && v.pref.ccompiler != 'msvc' { println('Trying to build with MSVC') v.cc_msvc() return } */ 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 v.pref.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.') } if v.pref.is_debug { println(res.output) } else { partial_output := res.output.limit(200).trim_right('\r\n') print(partial_output) if res.output.len > partial_output.len { println('...\n(Use `v -g` to print the entire error message)\n') }else{ println('') } } verror('C error. This should never happen. ' + '\nPlease create a GitHub issue: https://github.com/vlang/v/issues/new/choose') } diff := time.ticks() - ticks // Print the C command if v.pref.show_c_cmd || v.pref.is_verbose { println('${v.pref.ccompiler} took $diff ms') println('=========\n') } // Link it if we are cross compiling and need an executable /* if v.os == .linux && !linux_host && v.pref.build_mode != .build { v.out_name = v.out_name.replace('.o', '') obj_file := v.out_name + '.o' println('linux obj_file=$obj_file out_name=$v.out_name') ress := os.exec('/usr/local/Cellar/llvm/8.0.0/bin/ld.lld --sysroot=$sysroot ' + '-v -o $v.out_name ' + '-m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 ' + '/usr/lib/x86_64-linux-gnu/crt1.o ' + '$sysroot/lib/x86_64-linux-gnu/libm-2.28.a ' + '/usr/lib/x86_64-linux-gnu/crti.o ' + obj_file + ' /usr/lib/x86_64-linux-gnu/libc.so ' + '/usr/lib/x86_64-linux-gnu/crtn.o') or { verror(err) return } println(ress.output) println('linux cross compilation done. resulting binary: "$v.out_name"') } */ if !v.pref.is_keep_c && v.out_name_c != 'v.c' { os.rm(v.out_name_c) } if v.pref.compress { $if windows { println('-compress does not work on Windows for now') return } ret := os.system('strip $v.out_name') if ret != 0 { println('strip failed') return } ret2 := os.system('upx --lzma -qqq $v.out_name') if ret2 != 0 { println('upx failed') $if mac { println('install upx with `brew install upx`') } $if linux { println('install upx\n' + 'for example, on Debian/Ubuntu run `sudo apt install upx`') } $if windows { // :) } } } } fn (c mut V) cc_windows_cross() { if !c.out_name.ends_with('.exe') { c.out_name = c.out_name + '.exe' } mut args := '-o $c.out_name -w -L. ' cflags := c.get_os_cflags() // -I flags args += if c.pref.ccompiler == 'msvc' { cflags.c_options_before_target_msvc() } else { cflags.c_options_before_target() } mut libs := '' if c.pref.build_mode == .default_mode { libs = '"$v_modules_path/vlib/builtin.o"' if !os.file_exists(libs) { println('`$libs` not found') exit(1) } for imp in c.table.imports { libs += ' "$v_modules_path/vlib/${imp}.o"' } } args += ' $c.out_name_c ' args += if c.pref.ccompiler == 'msvc' { cflags.c_options_after_target_msvc() } else { cflags.c_options_after_target() } println('Cross compiling for Windows...') winroot := '$v_modules_path/winroot' if !os.dir_exists(winroot) { winroot_url := 'https://github.com/vlang/v/releases/download/v0.1.10/winroot.zip' println('"$winroot" not found.') println('Download it from $winroot_url and save it in $v_modules_path') println('Unzip it afterwards.\n') println('winroot.zip contains all library and header files needed '+ 'to cross-compile for Windows.') exit(1) } mut obj_name := c.out_name obj_name = obj_name.replace('.exe', '') obj_name = obj_name.replace('.o.o', '.o') include := '-I $winroot/include ' cmd := 'clang -o $obj_name -w $include -m32 -c -target x86_64-win32 $v_modules_path/$c.out_name_c' if c.pref.show_c_cmd { println(cmd) } if os.system(cmd) != 0 { println('Cross compilation for Windows failed. Make sure you have clang installed.') exit(1) } if c.pref.build_mode != .build_module { link_cmd := 'lld-link $obj_name $winroot/lib/libcmt.lib ' + '$winroot/lib/libucrt.lib $winroot/lib/kernel32.lib $winroot/lib/libvcruntime.lib ' + '$winroot/lib/uuid.lib' if c.pref.show_c_cmd { println(link_cmd) } if os.system(link_cmd) != 0 { println('Cross compilation for Windows failed. Make sure you have lld linker installed.') exit(1) } // os.rm(obj_name) } println('Done!') } fn (c &V) build_thirdparty_obj_files() { for flag in c.get_os_cflags() { if flag.value.ends_with('.o') { rest_of_module_flags := c.get_rest_of_module_cflags( flag ) if c.pref.ccompiler == 'msvc' { build_thirdparty_obj_file_with_msvc(flag.value, rest_of_module_flags) } else { build_thirdparty_obj_file(flag.value, rest_of_module_flags) } } } } fn find_c_compiler() string { args := env_vflags_and_os_args().join(' ') defaultcc := find_c_compiler_default() return get_arg( args, 'cc', defaultcc ) } fn find_c_compiler_default() string { //fast_clang := '/usr/local/Cellar/llvm/8.0.0/bin/clang' //if os.file_exists(fast_clang) { // return fast_clang //} // TODO fix $if after 'string' $if windows { return 'gcc' } return 'cc' } fn find_c_compiler_thirdparty_options() string { fullargs := env_vflags_and_os_args() mut cflags := get_cmdline_cflags( fullargs ) $if !windows { cflags += ' -fPIC' } if '-m32' in fullargs { cflags += ' -m32' } return cflags } fn get_cmdline_cflags(args []string) string { mut cflags := '' for ci, cv in args { if cv == '-cflags' { cflags += args[ci+1] + ' ' } } return cflags }