From 8ac0a2b2dd667fb264c8fa06127a0c3f60ce58e8 Mon Sep 17 00:00:00 2001 From: joe-conigliaro Date: Fri, 6 Sep 2019 22:12:04 +1000 Subject: [PATCH] compiler: rework flags & support win path spaces + more --- compiler/cc.v | 49 +++++++++------- compiler/cflags.v | 115 ++++++++++++++++++++++++++++++++++++ compiler/cgen.v | 25 ++++---- compiler/comptime.v | 43 ++------------ compiler/main.v | 10 ++-- compiler/msvc.v | 139 ++++++++++++-------------------------------- compiler/table.v | 5 +- 7 files changed, 201 insertions(+), 185 deletions(-) create mode 100644 compiler/cflags.v diff --git a/compiler/cc.v b/compiler/cc.v index 53c61e4f8a..25f4e31db8 100644 --- a/compiler/cc.v +++ b/compiler/cc.v @@ -10,6 +10,9 @@ import ( ) fn (v mut V) cc() { + // build any thirdparty obj files + v.build_thirdparty_obj_files() + // Just create a c file and exit if v.out_name.ends_with('.c') { os.mv(v.out_name_c, v.out_name) @@ -33,15 +36,6 @@ fn (v mut V) cc() { 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 - mut seenflags := map[string]int - mut uniqueflags := []string - for f in v.table.flags { - seenflags[ f ] = seenflags[ f ] + 1 - if seenflags[ f ] > 1 { continue } - uniqueflags << f - } - flags := uniqueflags.join(' ') - // Set out name if v.pref.is_so { a << '-shared -fPIC '// -Wl,-z,defs' v.out_name = v.out_name + '.so' @@ -92,7 +86,7 @@ fn (v mut V) cc() { // -I flags /* mut args := '' - for flag in v.table.flags { + for flag in v.get_os_cflags() { if !flag.starts_with('-l') { args += flag args += ' ' @@ -133,7 +127,9 @@ mut args := '' if v.os == .mac { a << '-mmacosx-version-min=10.7' } - a << flags + for flag in v.get_os_cflags() { + a << flag.format() + } a << libs // macOS code can include objective C TODO remove once objective C is replaced with C // Without these libs compilation will fail on Linux @@ -224,9 +220,9 @@ fn (c mut V) cc_windows_cross() { } mut args := '-o $c.out_name -w -L. ' // -I flags - for flag in c.table.flags { - if !flag.starts_with('-l') { - args += flag + for flag in c.get_os_cflags() { + if flag.name != '-l' { + args += flag.format() args += ' ' } } @@ -243,9 +239,9 @@ fn (c mut V) cc_windows_cross() { } args += ' $c.out_name_c ' // -l flags (libs) - for flag in c.table.flags { - if flag.starts_with('-l') { - args += flag + for flag in c.get_os_cflags() { + if flag.name == '-l' { + args += flag.format() args += ' ' } } @@ -289,6 +285,19 @@ fn (c mut V) cc_windows_cross() { println('Done!') } +fn (c V) build_thirdparty_obj_files() { + for flag in c.get_os_cflags() { + if flag.value.ends_with('.o') { + if c.os == .msvc { + build_thirdparty_obj_file_with_msvc(flag.value) + } + else { + build_thirdparty_obj_file(flag.value) + } + } + } +} + fn find_c_compiler() string { args := env_vflags_and_os_args().join(' ') defaultcc := find_c_compiler_default() @@ -306,10 +315,6 @@ fn find_c_compiler_default() string { } fn find_c_compiler_thirdparty_options() string { - $if windows { return '' } + $if windows { return '' } return '-fPIC' } - - - - diff --git a/compiler/cflags.v b/compiler/cflags.v new file mode 100644 index 0000000000..fd242ee1f5 --- /dev/null +++ b/compiler/cflags.v @@ -0,0 +1,115 @@ +// 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 main + +import os + +// C flag +struct CFlag{ + os string // eg. windows | darwin | linux + name string // eg. -I + value string // eg. /path/to/incude +} + +// check if cflag is in table +fn (table &Table) has_cflag(cflag CFlag) bool { + for cf in table.cflags { + if cf.os == cflag.os && cf.name == cflag.name && cf.value == cflag.value { + return true + } + } + return false +} + +// get flags for current os +fn (v V) get_os_cflags() []CFlag { + mut flags := []CFlag + for flag in v.table.cflags { + if flag.os == '' + || (flag.os == 'linux' && v.os == .linux) + || (flag.os == 'darwin' && v.os == .mac) + || (flag.os == 'windows' && (v.os == .windows || v.os == .msvc)) { + flags << flag + } + } + return flags +} + +// format flag +fn (cf &CFlag) format() string { + mut value := cf.value + // convert to absolute path + if cf.name == '-I' || cf.name == '-L' || value.ends_with('.o') { + value = '"'+os.realpath(value)+'"' + } + return '$cf.name $value'.trim_space() +} + +// parse the flags to []CFlag +// Note: clean up big time (joe-c) +fn (table mut Table) parse_cflag(cflag string) { + allowed_flags := [ + 'framework', + 'library', + 'I', 'l', 'L', + ] + mut flag := cflag.trim_space() + if flag == '' { + return + } + mut fos := '' + mut name := '' + if flag.starts_with('linux') || flag.starts_with('darwin') || flag.starts_with('windows') { + pos := flag.index(' ') + fos = flag.left(pos).trim_space() + flag = flag.right(pos).trim_space() + } + for { + mut index := -1 + mut value := '' + if flag[0] == `-` { + for f in allowed_flags { + i := 1+f.len + if i < flag.len && f == flag.substr(1,i) { + name = flag.left(i).trim_space() + flag = flag.right(i).trim_space() + break + } + } + } + for i in [flag.index(' '), flag.index(',')] { + if index == -1 || (i != -1 && i < index) { + index = i + } + } if index != -1 && flag[index] == ` ` && flag[index+1] == `-` { + for f in allowed_flags { + i := index+f.len + if i < flag.len && f == flag.substr(index, i) { + index = i + break + } + } + value = flag.left(index).trim_space() + flag = flag.right(index).trim_space() + } else if index != -1 && index < flag.len-2 && flag[index] == `,` { + value = flag.left(index).trim_space() + flag = flag.right(index+1).trim_space() + } else { + value = flag.trim_space() + index = -1 + } + cf := CFlag{ + os: fos, + name: name, + value: value + } + if !table.has_cflag(cf) { + table.cflags << cf + } + if index == -1 { + break + } + } +} diff --git a/compiler/cgen.v b/compiler/cgen.v index 941a885ee2..64bf2ade5e 100644 --- a/compiler/cgen.v +++ b/compiler/cgen.v @@ -240,13 +240,13 @@ fn (g mut CGen) add_to_main(s string) { } -fn build_thirdparty_obj_file(flag string) { - obj_path := flag.all_after(' ') +fn build_thirdparty_obj_file(path string) { + obj_path := os.realpath(path) if os.file_exists(obj_path) { return } println('$obj_path not found, building it...') - parent := os.dir( obj_path ) + parent := os.dir(obj_path) files := os.ls(parent) mut cfiles := '' for file in files { @@ -281,15 +281,15 @@ fn os_name_to_ifdef(name string) string { } fn platform_postfix_to_ifdefguard(name string) string { - switch name { - case '.v': return '' // no guard needed - case '_win.v': return '#ifdef _WIN32' - case '_nix.v': return '#ifndef _WIN32' - case '_lin.v': return '#ifdef __linux__' - case '_mac.v': return '#ifdef __APPLE__' - } - cerror('bad platform_postfix "$name"') - return '' + switch name { + case '.v': return '' // no guard needed + case '_win.v': return '#ifdef _WIN32' + case '_nix.v': return '#ifndef _WIN32' + case '_lin.v': return '#ifdef __linux__' + case '_mac.v': return '#ifdef __APPLE__' + } + cerror('bad platform_postfix "$name"') + return '' } // C struct definitions, ordered @@ -383,4 +383,3 @@ fn sort_structs(types []Type) []Type { } return types_sorted } - diff --git a/compiler/comptime.v b/compiler/comptime.v index a503ff798b..aebeb7103e 100644 --- a/compiler/comptime.v +++ b/compiler/comptime.v @@ -141,46 +141,11 @@ fn (p mut Parser) chash() { is_sig := p.is_sig() if hash.starts_with('flag ') { mut flag := hash.right(5) - // No the right os? Skip! - // mut ok := true - if hash.contains('linux') && p.os != .linux { - return - } - else if hash.contains('darwin') && p.os != .mac { - return - } - else if hash.contains('windows') && (p.os != .windows && p.os != .msvc) { - return - } - // Remove "linux" etc from flag - if flag.contains('linux') || flag.contains('darwin') || flag.contains('windows') { - pos := flag.index(' ') - flag = flag.right(pos) - } - has_vroot := flag.contains('@VROOT') - flag = flag.trim_space().replace('@VROOT', p.vroot) - if p.table.flags.contains(flag) { - return - } - // expand `@VMOD/pg/pg.o` to absolute path - has_vmod := flag.contains('@VMOD') - flag = flag.trim_space().replace('@VMOD', ModPath) - if p.table.flags.contains(flag) { - return - } + // expand `@VROOT` `@VMOD` to absolute path + flag = flag.replace('@VROOT', p.vroot) + flag = flag.replace('@VMOD', ModPath) p.log('adding flag "$flag"') - // `@VROOT/thirdparty/glad/glad.o`, make sure it exists, otherwise build it - if (has_vroot || has_vmod) && flag.contains('.o') { - flag = os.realpath( flag ) - //println( 'absolute filepath to objectfile is now: $flag | os is: $p.os ') - if p.os == .msvc { - build_thirdparty_obj_file_with_msvc(flag) - } - else { - build_thirdparty_obj_file(flag) - } - } - p.table.flags << flag + p.table.parse_cflag(flag) return } if hash.starts_with('include') { diff --git a/compiler/main.v b/compiler/main.v index 4252ca45b8..07d0ac6924 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -325,7 +325,9 @@ fn (v mut V) compile() { cgen.save() if v.pref.is_verbose { v.log('flags=') - println(v.table.flags) + for flag in v.get_os_cflags() { + println(' * ' + flag.format()) + } } v.cc() } @@ -437,7 +439,7 @@ fn (v V) run_compiled_executable_and_exit() { if v.pref.is_verbose { println('============ running $v.out_name ============') } - mut cmd := final_target_out_name(v.out_name).replace('.exe','') + mut cmd := '"' + final_target_out_name(v.out_name).replace('.exe','') + '"' if os.args.len > 3 { cmd += ' ' + os.args.right(3).join(' ') } @@ -781,9 +783,7 @@ fn new_v(args[]string) &V { vroot := os.dir(os.executable()) //println('VROOT=$vroot') // v.exe's parent directory should contain vlib - if os.dir_exists(vroot) && os.dir_exists(vroot + '/vlib/builtin') { - - } else { + if !os.dir_exists(vroot) || !os.dir_exists(vroot + '/vlib/builtin') { println('vlib not found. It should be next to the V executable. ') println('Go to https://vlang.io to install V.') exit(1) diff --git a/compiler/msvc.v b/compiler/msvc.v index 028f9b8c9a..7015d2f960 100644 --- a/compiler/msvc.v +++ b/compiler/msvc.v @@ -207,11 +207,6 @@ fn find_msvc() ?MsvcResult { } } -struct ParsedFlag { - f string - arg string -} - pub fn (v mut V) cc_msvc() { r := find_msvc() or { // TODO: code reuse @@ -305,99 +300,41 @@ pub fn (v mut V) cc_msvc() { mut lib_paths := []string{} mut other_flags := []string{} - // Emily: - // this is a hack to try and support -l -L and object files - // passed on the command line - mut seenflags := map[string]int // no need to add the same flags more than once - for f in v.table.flags { - seenflags[ f ] = seenflags[ f ] + 1 - if seenflags[ f ] > 1 { continue } - // People like to put multiple flags per line (which really complicates things) - // ...so we need to handle that - mut rest := f + for flag in v.get_os_cflags() { + mut arg := flag.value + //println('fl: $flag.name | flag arg: $arg') - mut flags := []ParsedFlag{} - for { - mut base := rest - - fl := if rest.starts_with('-') { - base = rest.right(2).trim_space() - rest.left(2) - } else { - '' - } - - // Which ever one of these is lowest we use - // TODO: we really shouldnt support all of these cmon - mut lowest := base.index('-') - // dont break paths with hyphens - if lowest != 0 { - lowest = -1 - } - for x in [base.index(' '), base.index(',')] { - if (x < lowest && x != -1) || lowest == -1 { - lowest = x - } - } - arg := if lowest != -1 { - rest = base.right(lowest).trim_space().trim(',') - base.left(lowest).trim_space().trim(',') - } else { - rest = '' - base.trim_space() - } - - flags << ParsedFlag { - fl, arg - } - - if rest.len == 0 { - break + // We need to see if the flag contains -l + // -l isnt recognised and these libs will be passed straight to the linker + // by the compiler + if flag.name == '-l' { + if arg.ends_with('.dll') { + cerror('MSVC cannot link against a dll (`#flag -l $arg`)') } + // MSVC has no method of linking against a .dll + // TODO: we should look for .defs aswell + lib_lib := arg + '.lib' + real_libs << lib_lib } - - for flag in flags { - fl := flag.f - mut arg := flag.arg - if fl == '-I' || fl == '-L' { - arg = os.realpath( arg ) - } - //println('fl: $fl | flag arg: $arg') - - // We need to see if the flag contains -l - // -l isnt recognised and these libs will be passed straight to the linker - // by the compiler - if fl == '-l' { - if arg.ends_with('.dll') { - cerror('MSVC cannot link against a dll (`#flag -l $arg`)') - } - // MSVC has no method of linking against a .dll - // TODO: we should look for .defs aswell - lib_lib := arg + '.lib' - real_libs << lib_lib - } - else if fl == '-I' { - inc_paths << ' -I "$arg" ' - } - else if fl == '-L' { - lpath := f.right(2).trim_space() - lib_paths << lpath - lib_paths << lpath + os.PathSeparator + 'msvc' - // The above allows putting msvc specific .lib files in a subfolder msvc/ , - // where gcc will NOT find them, but cl will do... - // NB: gcc is smart enough to not need .lib files at all in most cases, the .dll is enough. - // When both a msvc .lib file and .dll file are present in the same folder, - // as for example for glfw3, compilation with gcc would fail. - } - else if arg.ends_with('.o') { - // msvc expects .obj not .o - other_flags << arg + 'bj' - } - else { - other_flags << arg - } + else if flag.name == '-I' { + inc_paths << ' ' + flag.format() + ' ' + } + else if flag.name == '-L' { + lpath := flag.value + lib_paths << '"' + lpath + '"' + lib_paths << '"' + lpath + os.PathSeparator + 'msvc' + '"' + // The above allows putting msvc specific .lib files in a subfolder msvc/ , + // where gcc will NOT find them, but cl will do... + // NB: gcc is smart enough to not need .lib files at all in most cases, the .dll is enough. + // When both a msvc .lib file and .dll file are present in the same folder, + // as for example for glfw3, compilation with gcc would fail. + } + else if flag.value.ends_with('.o') { + other_flags << flag.format().replace('.o', '.obj') + } + else { + other_flags << arg } - } // Include the base paths @@ -463,18 +400,16 @@ pub fn (v mut V) cc_msvc() { os.rm(out_name_obj) } -fn build_thirdparty_obj_file_with_msvc(flag string) { +fn build_thirdparty_obj_file_with_msvc(path string) { msvc := find_msvc() or { println('Could not find visual studio') return } - mut obj_path := flag.all_after(' ') + // msvc expects .obj not .o + mut obj_path := '${path}bj' - if obj_path.ends_with('.o') { - // msvc expects .obj not .o - obj_path = obj_path + 'bj' - } + obj_path = os.realpath(obj_path) if os.file_exists(obj_path) { println('$obj_path already build.') @@ -482,14 +417,14 @@ fn build_thirdparty_obj_file_with_msvc(flag string) { } println('$obj_path not found, building it (with msvc)...') - parent := os.dir( obj_path ) + parent := os.dir(obj_path) files := os.ls(parent) mut cfiles := '' for file in files { if file.ends_with('.c') { cfiles += '"' + os.realpath( parent + os.PathSeparator + file ) + '" ' - } + } } include_string := '-I "$msvc.ucrt_include_path" -I "$msvc.vs_include_path" -I "$msvc.um_include_path" -I "$msvc.shared_include_path"' diff --git a/compiler/table.v b/compiler/table.v index d14e27cff8..75b5832e46 100644 --- a/compiler/table.v +++ b/compiler/table.v @@ -17,7 +17,7 @@ mut: modules []string // List of all modules registered by the application imports []string // List of all imports file_imports []FileImportTable // List of imports for file - flags []string // ['-framework Cocoa', '-lglfw3'] + cflags []CFlag // ['-framework Cocoa', '-lglfw3'] fn_cnt int //atomic obfuscate bool } @@ -78,8 +78,6 @@ mut: scope_level int } - - struct Type { mut: mod string @@ -106,7 +104,6 @@ struct TypeNode { typ Type } - // For debugging types fn (t Type) str() string { mut s := 'type "$t.name" {'