diff --git a/examples/2048/.gitignore b/examples/2048/.gitignore index 62b7973a6b865f..6bb72ddb213740 100644 --- a/examples/2048/.gitignore +++ b/examples/2048/.gitignore @@ -2,3 +2,5 @@ 2048 main +index.js +index.wasm diff --git a/examples/2048/README.md b/examples/2048/README.md index ae9ce7fb1c3a49..3812d6fafd7894 100644 --- a/examples/2048/README.md +++ b/examples/2048/README.md @@ -28,7 +28,8 @@ Compile & run the game with `./v run examples/2048` ## Compiling to WASM -1. Install Emscripten from https://emscripten.org/docs/getting_started/downloads.html +1. Install Emscripten from: + https://emscripten.org/docs/getting_started/downloads.html 2. Make sure that the environment in your shell is setup correctly, i.e. that `emcc --version` works. @@ -39,15 +40,9 @@ Compile & run the game with `./v run examples/2048` ``` 3. Compile the game to WASM: - - ```sh - v -skip-unused -prod -os wasm32_emscripten examples/2048/` - ``` - -4. Copy the 2048 file to `index.js` (can be done once; this step will be removed soon): - + (the JS file contains a loader for the .wasm file, without the extension): ```sh - cp examples/2048/2048 examples/2048/index.js + v -prod -os wasm32_emscripten -o examples/2048/index.js examples/2048/ ``` 5. Run/test the game: diff --git a/vlib/os/os.v b/vlib/os/os.v index 8c30f9cfba1a86..35de856334dda3 100644 --- a/vlib/os/os.v +++ b/vlib/os/os.v @@ -608,7 +608,7 @@ pub fn find_abs_path_of_executable(exe_name string) !string { $if trace_find_abs_path_of_executable ? { dump(found_abs_path) } - if exists(found_abs_path) && is_executable(found_abs_path) { + if is_file(found_abs_path) && is_executable(found_abs_path) { res = found_abs_path break } diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index 802e05a23b40f8..5bce5c4495c2b1 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -108,6 +108,7 @@ pub enum CC { icc msvc clang + emcc unknown } @@ -210,6 +211,7 @@ fn (mut v Builder) setup_ccompiler_options(ccompiler string) { cc_file_name.contains('clang') || ccoptions.guessed_compiler == 'clang' { .clang } cc_file_name.contains('msvc') || ccoptions.guessed_compiler == 'msvc' { .msvc } cc_file_name.contains('icc') || ccoptions.guessed_compiler == 'icc' { .icc } + cc_file_name.contains('emcc') || ccoptions.guessed_compiler == 'emcc' { .emcc } else { .unknown } // vfmt on } @@ -652,7 +654,7 @@ pub fn (mut v Builder) cc() { // whether to just create a .c or .js file and exit, for example: `v -o v.c cmd.v` ends_with_c := v.pref.out_name.ends_with('.c') ends_with_js := v.pref.out_name.ends_with('.js') - if ends_with_c || ends_with_js { + if ends_with_c || (ends_with_js && v.pref.os != .wasm32_emscripten) { v.pref.skip_running = true msg_mv := 'os.mv_by_cp ${os.quoted_path(v.out_name_c)} => ${os.quoted_path(v.pref.out_name)}' util.timing_start(msg_mv) diff --git a/vlib/v/pref/default.v b/vlib/v/pref/default.v index 318bc7be8f5561..887a330d463513 100644 --- a/vlib/v/pref/default.v +++ b/vlib/v/pref/default.v @@ -61,7 +61,7 @@ fn (mut p Preferences) setup_os_and_arch_when_not_explicitly_set() { host_os := if p.backend == .wasm { OS.wasi } else { get_host_os() } if p.os == ._auto { p.os = host_os - p.build_options << '-os ${host_os}' + p.build_options << '-os ${host_os.lower()}' } if !p.output_cross_c { @@ -83,6 +83,19 @@ fn (mut p Preferences) setup_os_and_arch_when_not_explicitly_set() { } } +pub fn (mut p Preferences) defines_map_unique_keys() string { + mut defines_map := map[string]bool{} + for d in p.compile_defines { + defines_map[d] = true + } + for d in p.compile_defines_all { + defines_map[d] = true + } + keys := defines_map.keys() + skeys := keys.sorted() + return skeys.join(',') +} + pub fn (mut p Preferences) fill_with_defaults() { p.setup_os_and_arch_when_not_explicitly_set() p.expand_lookup_paths() @@ -189,17 +202,20 @@ pub fn (mut p Preferences) fill_with_defaults() { } } } + + final_os := p.os.lower() + p.parse_define(final_os) + // Prepare the cache manager. All options that can affect the generated cached .c files // should go into res.cache_manager.vopts, which is used as a salt for the cache hash. vhash := @VHASH p.cache_manager = vcache.new_cache_manager([ vhash, // ensure that different v versions use separate build artefacts - '${p.backend} | ${p.os} | ${p.ccompiler} | ${p.is_prod} | ${p.sanitize}', + '${p.backend} | ${final_os} | ${p.ccompiler} | ${p.is_prod} | ${p.sanitize}', + p.defines_map_unique_keys(), p.cflags.trim_space(), p.third_party_option.trim_space(), - p.compile_defines_all.str(), - p.compile_defines.str(), p.lookup_path.str(), ]) // eprintln('prefs.cache_manager: $p') diff --git a/vlib/v/pref/os.v b/vlib/v/pref/os.v index 9d46aac301edb5..3f19959180144b 100644 --- a/vlib/v/pref/os.v +++ b/vlib/v/pref/os.v @@ -131,6 +131,41 @@ pub fn os_from_string(os_str string) !OS { } } +// lower returns the name that could be used with `-os osname`, for each OS enum value +// NOTE: it is important to not change the names here, they should match 1:1, since they +// are used as part of the cache keys, when -usecache is passed. +pub fn (o OS) lower() string { + return match o { + ._auto { '' } + .linux { 'linux' } + .windows { 'windows' } + .macos { 'macos' } + .ios { 'ios' } + .freebsd { 'freebsd' } + .openbsd { 'openbsd' } + .netbsd { 'netbsd' } + .dragonfly { 'dragonfly' } + .js_node { 'js' } + .js_freestanding { 'js_freestanding' } + .js_browser { 'js_browser' } + .solaris { 'solaris' } + .serenity { 'serenity' } + .qnx { 'qnx' } + .plan9 { 'plan9' } + .vinix { 'vinix' } + .android { 'android' } + .termux { 'termux' } + .haiku { 'haiku' } + .raw { 'raw' } + .wasm32 { 'wasm32' } + .wasm32_wasi { 'wasm32_wasi' } + .wasm32_emscripten { 'wasm32_emscripten' } + .browser { 'browser' } + .wasi { 'wasi' } + .all { 'all' } + } +} + pub fn (o OS) str() string { // TODO: check more thoroughly, why this method needs to exist at all, // and why should it override the default autogenerated .str() method, diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 3b6d10d8499c64..ad116ebb63f0d6 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -76,6 +76,7 @@ pub enum CompilerType { gcc tinyc clang + emcc mingw msvc cplusplus @@ -830,7 +831,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin res.build_options << '${arg}' } '-os' { - target_os := cmdline.option(args[i..], '-os', '') + target_os := cmdline.option(args[i..], '-os', '').to_lower_ascii() i++ target_os_kind := os_from_string(target_os) or { if target_os == 'cross' { @@ -895,12 +896,6 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin } '-o', '-output' { res.out_name = cmdline.option(args[i..], arg, '') - if res.out_name.ends_with('.js') { - res.backend = .js_node - res.output_cross_c = true - } else if res.out_name.ends_with('.o') { - res.is_o = true - } if !os.is_abs_path(res.out_name) { res.out_name = os.join_path(os.getwd(), res.out_name) } @@ -1053,6 +1048,18 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin res.is_run = true } res.show_asserts = res.show_asserts || res.is_stats || os.getenv('VTEST_SHOW_ASSERTS') != '' + + if res.os != .wasm32_emscripten { + if res.out_name.ends_with('.js') { + res.backend = .js_node + res.output_cross_c = true + } + } + + if res.out_name.ends_with('.o') { + res.is_o = true + } + if command == 'run' && res.is_prod && os.is_atty(1) > 0 { eprintln_cond(show_output && !res.is_quiet, "Note: building an optimized binary takes much longer. It shouldn't be used with `v run`.") eprintln_cond(show_output && !res.is_quiet, 'Use `v run` without optimization, or build an optimized binary with -prod first, then run it separately.') @@ -1226,6 +1233,7 @@ pub fn cc_from_string(s string) CompilerType { cc.contains('tcc') || cc.contains('tinyc') { .tinyc } cc.contains('gcc') { .gcc } cc.contains('clang') { .clang } + cc.contains('emcc') { .emcc } cc.contains('msvc') { .msvc } cc.contains('mingw') { .mingw } cc.contains('++') { .cplusplus } diff --git a/vlib/v/pref/should_compile.v b/vlib/v/pref/should_compile.v index a46f2202bd7cba..6e9723ec5e087e 100644 --- a/vlib/v/pref/should_compile.v +++ b/vlib/v/pref/should_compile.v @@ -15,28 +15,9 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s || file.all_before_last('.v').all_before_last('.').ends_with('_test') { continue } - if prefs.backend in [.c, .interpret] && !prefs.should_compile_c(file) { - continue - } - if prefs.backend.is_js() && !prefs.should_compile_js(file) { - continue - } - if prefs.backend == .native && !prefs.should_compile_native(file) { - continue - } - if !prefs.backend.is_js() && !prefs.should_compile_asm(file) { - continue - } - if file.starts_with('.#') { - continue - } - if !prefs.prealloc && !prefs.output_cross_c && file.ends_with('prealloc.c.v') { - continue - } - if prefs.nofloat && file.ends_with('float.c.v') { - continue - } + mut is_d_notd_file := false if file.contains('_d_') { + is_d_notd_file = true if prefs.compile_defines_all.len == 0 { continue } @@ -58,6 +39,7 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s } } if file.contains('_notd_') { + is_d_notd_file = true mut allowed := true for cdefine in prefs.compile_defines { file_postfixes := ['_notd_${cdefine}.v', '_notd_${cdefine}.c.v'] @@ -75,6 +57,27 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s continue } } + if prefs.backend in [.c, .interpret] && !is_d_notd_file && !prefs.should_compile_c(file) { + continue + } + if prefs.backend.is_js() && !prefs.should_compile_js(file) { + continue + } + if prefs.backend == .native && !prefs.should_compile_native(file) { + continue + } + if !prefs.backend.is_js() && !prefs.should_compile_asm(file) { + continue + } + if file.starts_with('.#') { + continue + } + if !prefs.prealloc && !prefs.output_cross_c && file.ends_with('prealloc.c.v') { + continue + } + if prefs.nofloat && file.ends_with('float.c.v') { + continue + } if prefs.exclude.len > 0 { full_file_path := os.join_path(dir, file) for epattern in prefs.exclude { diff --git a/vlib/v/tests/project_compiling_to_wasm/abc_d_wasm32_emscripten.c.v b/vlib/v/tests/project_compiling_to_wasm/abc_d_wasm32_emscripten.c.v new file mode 100644 index 00000000000000..077f9d9dbc692e --- /dev/null +++ b/vlib/v/tests/project_compiling_to_wasm/abc_d_wasm32_emscripten.c.v @@ -0,0 +1,5 @@ +module main + +fn abc() { + println('This is abc_d_wasm32_emscripten.c.v') +} diff --git a/vlib/v/tests/project_compiling_to_wasm/abc_notd_wasm32_emscripten.c.v b/vlib/v/tests/project_compiling_to_wasm/abc_notd_wasm32_emscripten.c.v new file mode 100644 index 00000000000000..5550b374490634 --- /dev/null +++ b/vlib/v/tests/project_compiling_to_wasm/abc_notd_wasm32_emscripten.c.v @@ -0,0 +1,5 @@ +module main + +fn abc() { + println('This is abc_notd_wasm32_emscripten.c.v') +} diff --git a/vlib/v/tests/project_compiling_to_wasm/compilation_to_wasm_works_with_d_and_notd_files_test.v b/vlib/v/tests/project_compiling_to_wasm/compilation_to_wasm_works_with_d_and_notd_files_test.v new file mode 100644 index 00000000000000..cd1ffd2b6642d2 --- /dev/null +++ b/vlib/v/tests/project_compiling_to_wasm/compilation_to_wasm_works_with_d_and_notd_files_test.v @@ -0,0 +1,51 @@ +import os + +const vexe = os.quoted_path(@VEXE) +const project_folder = os.dir(@FILE) +const output_path = os.join_path(os.vtmp_dir(), 'check_wasm_works') + +fn testsuite_begin() { + os.mkdir_all(output_path) or {} + os.chdir(output_path)! + dump(os.getwd()) +} + +fn testsuite_end() { + os.system('ls -la .') + os.chdir(os.home_dir()) or {} + os.rmdir_all(output_path) or {} +} + +fn test_normal() { + if os.user_os() == 'windows' { + return + } + defer { println('done ${@FN}') } + dump(vexe) + res := os.system('${os.quoted_path(vexe)} -o normal.exe ${os.quoted_path(project_folder)}') + assert res == 0 + dump(res) + assert os.exists('normal.exe') + content := os.read_file('normal.exe')! + assert content.contains('This is abc_notd_wasm32_emscripten.c.v') +} + +fn test_emcc() { + if os.user_os() == 'windows' { + return + } + defer { println('done ${@FN}') } + emcc := os.find_abs_path_of_executable('emcc') or { + println('skipping ${@FN} since `emcc` is not found') + return + } + dump(emcc) + res := os.system('${os.quoted_path(vexe)} -os wasm32_emscripten -o wasm_check.html ${os.quoted_path(project_folder)}') + assert res == 0 + dump(res) + assert os.exists('wasm_check.html') + assert os.exists('wasm_check.js') + assert os.exists('wasm_check.wasm') + content := os.read_file('wasm_check.wasm')! + assert content.contains('This is abc_d_wasm32_emscripten.c.v') +} diff --git a/vlib/v/tests/project_compiling_to_wasm/main.v b/vlib/v/tests/project_compiling_to_wasm/main.v new file mode 100644 index 00000000000000..6fb82924d5c391 --- /dev/null +++ b/vlib/v/tests/project_compiling_to_wasm/main.v @@ -0,0 +1,6 @@ +module main + +fn main() { + abc() + println('hi from main.v') +} diff --git a/vlib/v/tests/project_compiling_to_wasm/v.mod b/vlib/v/tests/project_compiling_to_wasm/v.mod new file mode 100644 index 00000000000000..e69de29bb2d1d6