diff --git a/.github/actions/wasi/base/action.yml b/.github/actions/wasi/base/action.yml new file mode 100644 index 0000000..08cd74d --- /dev/null +++ b/.github/actions/wasi/base/action.yml @@ -0,0 +1,16 @@ +name: "Ubuntu base dependencies" + +runs: + using: "composite" + steps: + - name: Install system dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y ninja-build wget + - name: Install CMake + shell: bash + run: | + wget -nv -O ./cmake_install.sh https://github.com/Kitware/CMake/releases/download/v3.28.0-rc5/cmake-3.28.0-rc5-linux-x86_64.sh + sudo bash ./cmake_install.sh --skip-license --prefix=/usr --exclude-subdir + rm -f ./cmake_install.sh diff --git a/.github/actions/wasi/clang/action.yml b/.github/actions/wasi/clang/action.yml new file mode 100644 index 0000000..1223e8e --- /dev/null +++ b/.github/actions/wasi/clang/action.yml @@ -0,0 +1,17 @@ +name: "Ubuntu latest clang" + +runs: + using: "composite" + steps: + - name: Install clang/llvm 17 + shell: bash + run: | + sudo apt-add-repository 'deb https://apt.llvm.org/jammy llvm-toolchain-jammy-17 main' + sudo wget -qO /etc/apt/trusted.gpg.d/llvm.asc https://apt.llvm.org/llvm-snapshot.gpg.key + sudo apt-get update + sudo apt-get install -y -t llvm-toolchain-jammy-17 \ + clang-17 llvm-17 lld-17 lldb-17 libc++-17-dev \ + libc++abi-17-dev libclang-rt-17-dev + for f in /usr/lib/llvm-*/bin/*; do sudo ln -sf "$f" /usr/bin; done + sudo mkdir -p /usr/lib/llvm-17/lib/clang/17/lib/wasi + sudo chmod 0777 /usr/lib/llvm-17/lib/clang/17/lib/wasi diff --git a/.github/ci.yaml b/.github/ci.yaml index 1aa56c1..5303801 100644 --- a/.github/ci.yaml +++ b/.github/ci.yaml @@ -9,6 +9,7 @@ jobs: fail-fast: false matrix: target: [ + { os: ubuntu-22.04, preset: wasm32-wasi-clang-static }, { os: ubuntu-latest, preset: x64-linux-gcc-dynamic }, { os: ubuntu-latest, preset: x86-linux-gcc-dynamic }, { os: ubuntu-latest, preset: x64-linux-clang-dynamic }, diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d73fdbd..fcd131d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,6 +9,7 @@ jobs: fail-fast: false matrix: target: [ + { os: ubuntu-22.04, preset: wasm32-wasi-clang-static }, { os: ubuntu-latest, preset: x64-linux-gcc-dynamic }, { os: ubuntu-latest, preset: x86-linux-gcc-dynamic }, { os: ubuntu-latest, preset: x64-linux-clang-dynamic }, @@ -34,6 +35,15 @@ jobs: - uses: ./.github/actions/vcpkg-cache - name: CMake workflow + id: cmake-workflow shell: bash run: | cmake --workflow --preset ${{matrix.target.preset}} + + - name: Upload vcpkg error logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: logs-${{matrix.target.preset}} + path: | + /usr/local/share/vcpkg/**/*.log diff --git a/CMakeLists.txt b/CMakeLists.txt index 8eb78fa..41f3de9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,5 +9,7 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") add_subdirectory(src) -enable_testing() -add_subdirectory(tests) +if (NOT WASI) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/CMakePresets.json b/CMakePresets.json index 6d4744f..ab060f8 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,12 +8,14 @@ "include": [ "cmake/presets/arm64-darwin-gcc.json", "cmake/presets/arm64-darwin-clang.json", + "cmake/presets/wasm32-wasi-clang.json", "cmake/presets/x64-darwin-gcc.json", "cmake/presets/x64-darwin-clang.json", - "cmake/presets/x64-linux-gcc.json", "cmake/presets/x64-windows-clang.json", "cmake/presets/x64-windows-msvc.json", "cmake/presets/x86-linux-gcc.json", + "cmake/presets/x86-linux-clang.json", + "cmake/presets/x64-linux-gcc.json", "cmake/presets/x64-linux-clang.json" ] } diff --git a/README.md b/README.md index 4b83610..37a0362 100644 --- a/README.md +++ b/README.md @@ -62,4 +62,17 @@ find_package( CONFIG REQUIRED) target_link_libraries( PUBLIC ::) ``` -This will require that your library is published and installed via vcpkg or found locally by setting the `CMAKE_PREFIX_PATH` environment variable in your other project during configure. \ No newline at end of file +This will require that your library is published and installed via vcpkg or found locally by setting the `CMAKE_PREFIX_PATH` environment variable in your other project during configure. + +If you wish to expose parts of your library as a WebAssembly module, you can add the `WASM_EXPORT("")` macro to any function you wish to expose in `src/wasm/interface.cpp`, and compile using the `wasm32-wasi-clang` preset. This will generate a minimal WebAssembly binary exposing your exported functions at `build//src/wasm//lib.wasm`. This binary conforms to the [WASI WebAssembly standard](https://wasi.dev/), so it can be utilized in any WASI-supporting runtime like [`wasmer.io`](https://wasmer.io/) + +```sh +wasmer run build//src/cli//_cli + +# Prints: +# version: 0.0.1 +``` + +> [!NOTE] +> Exposed functions with `WASM_EXPORT` currently only accept parameters and return values of numerical type. This is because the proposal for WebAssembly Interface Types has not yet been standardized, and until then, complex data-types cannot be sent over the runtime's ABI boundary. Complex data must be transferred via pointer into exported/shared memory. + diff --git a/cmake/Platform/WASI.cmake b/cmake/Platform/WASI.cmake new file mode 100644 index 0000000..b49713f --- /dev/null +++ b/cmake/Platform/WASI.cmake @@ -0,0 +1 @@ +set(WASI 1) diff --git a/cmake/presets/toolchains/wasi-sdk.json b/cmake/presets/toolchains/wasi-sdk.json new file mode 100644 index 0000000..c716e79 --- /dev/null +++ b/cmake/presets/toolchains/wasi-sdk.json @@ -0,0 +1,13 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "wasi-sdk", + "hidden": true, + "cacheVariables": { + "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/cmake/wasi-sdk/wasi-sdk.toolchain.cmake", + "VCPKG_OVERLAY_PORTS": "${sourceDir}/cmake/vcpkg/ports/wasm32-wasi" + } + } + ] +} diff --git a/cmake/presets/wasm32-wasi-clang.json b/cmake/presets/wasm32-wasi-clang.json new file mode 100644 index 0000000..c9d0877 --- /dev/null +++ b/cmake/presets/wasm32-wasi-clang.json @@ -0,0 +1,41 @@ +{ + "version": 6, + "include": [ + "base.json", + "compilers/clang.json", + "toolchains/wasi-sdk.json" + ], + "configurePresets": [ + { + "name": "wasm32-wasi-clang-static", + "inherits": [ + "base", + "wasi-sdk", + "clang" + ], + "displayName": "WASM32 WASI clang static libs" + } + ], + "buildPresets": [ + { + "name": "wasm32-wasi-clang-static", + "inherits": "base", + "configurePreset": "wasm32-wasi-clang-static" + } + ], + "workflowPresets": [ + { + "name": "wasm32-wasi-clang-static", + "steps": [ + { + "type": "configure", + "name": "wasm32-wasi-clang-static" + }, + { + "type": "build", + "name": "wasm32-wasi-clang-static" + } + ] + } + ] +} diff --git a/cmake/vcpkg/ports/wasm32-wasi/fmt/001-fix-write-batch.patch b/cmake/vcpkg/ports/wasm32-wasi/fmt/001-fix-write-batch.patch new file mode 100644 index 0000000..7c17d60 --- /dev/null +++ b/cmake/vcpkg/ports/wasm32-wasi/fmt/001-fix-write-batch.patch @@ -0,0 +1,13 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index f21cf45..691a632 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -157,7 +157,7 @@ if (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio") + join(netfxpath + "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\" + ".NETFramework\\v4.0") +- file(WRITE run-msbuild.bat " ++ file(WRITE ${CMAKE_BINARY_DIR}/run-msbuild.bat " + ${MSBUILD_SETUP} + ${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*") + endif () diff --git a/cmake/vcpkg/ports/wasm32-wasi/fmt/002-disable-posix-fd.patch b/cmake/vcpkg/ports/wasm32-wasi/fmt/002-disable-posix-fd.patch new file mode 100644 index 0000000..bc927aa --- /dev/null +++ b/cmake/vcpkg/ports/wasm32-wasi/fmt/002-disable-posix-fd.patch @@ -0,0 +1,47 @@ +diff --git a/src/os.cc b/src/os.cc +index 7813c433..b472f0a6 100644 +--- a/src/os.cc ++++ b/src/os.cc +@@ -279,7 +279,7 @@ std::size_t file::write(const void* buffer, std::size_t count) { + file file::dup(int fd) { + // Don't retry as dup doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html +- int new_fd = FMT_POSIX_CALL(dup(fd)); ++ int new_fd = -1; + if (new_fd == -1) + FMT_THROW(system_error( + errno, FMT_STRING("cannot duplicate file descriptor {}"), fd)); +@@ -287,8 +287,7 @@ file file::dup(int fd) { + } + + void file::dup2(int fd) { +- int result = 0; +- FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); ++ int result = -1; + if (result == -1) { + FMT_THROW(system_error( + errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_, +@@ -297,8 +296,7 @@ void file::dup2(int fd) { + } + + void file::dup2(int fd, std::error_code& ec) noexcept { +- int result = 0; +- FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); ++ int result = -1; + if (result == -1) ec = std::error_code(errno, std::generic_category()); + } + +@@ -335,11 +333,11 @@ pipe::pipe() { + # ifdef _WIN32 + // Make the default pipe capacity same as on Linux 2.6.11+. + enum { DEFAULT_CAPACITY = 65536 }; +- int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY)); ++ int result = -1; + # else + // Don't retry as the pipe function doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html +- int result = FMT_POSIX_CALL(pipe(fds)); ++ int result = -1; + # endif + if (result != 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe"))); diff --git a/cmake/vcpkg/ports/wasm32-wasi/fmt/README.md b/cmake/vcpkg/ports/wasm32-wasi/fmt/README.md new file mode 100644 index 0000000..8e5ca8d --- /dev/null +++ b/cmake/vcpkg/ports/wasm32-wasi/fmt/README.md @@ -0,0 +1 @@ +This overlay port is a direct copy from https://github.com/microsoft/vcpkg/tree/master/ports/fmt with modifications to allow compilation on wasm32-wasi as a target by removing POSIX filesystem calls. \ No newline at end of file diff --git a/cmake/vcpkg/ports/wasm32-wasi/fmt/portfile.cmake b/cmake/vcpkg/ports/wasm32-wasi/fmt/portfile.cmake new file mode 100644 index 0000000..1cc2cd9 --- /dev/null +++ b/cmake/vcpkg/ports/wasm32-wasi/fmt/portfile.cmake @@ -0,0 +1,38 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO fmtlib/fmt + REF "${VERSION}" + SHA512 27df90c681ec37e55625062a79e3b83589b6d7e94eff37a3b412bb8c1473f757a8adb727603acc9185c3490628269216843b7d7bd5a3cb37f0029da5d1495ffa + HEAD_REF master + PATCHES + 001-fix-write-batch.patch + 002-disable-posix-fd.patch +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DFMT_CMAKE_DIR=share/fmt + -DFMT_TEST=OFF + -DFMT_DOC=OFF +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup() +vcpkg_fixup_pkgconfig() +vcpkg_copy_pdbs() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL dynamic) + vcpkg_replace_string(${CURRENT_PACKAGES_DIR}/include/fmt/core.h + "defined(FMT_SHARED)" + "1" + ) +endif() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" +) + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") \ No newline at end of file diff --git a/cmake/vcpkg/ports/wasm32-wasi/fmt/usage b/cmake/vcpkg/ports/wasm32-wasi/fmt/usage new file mode 100644 index 0000000..aa9ebfb --- /dev/null +++ b/cmake/vcpkg/ports/wasm32-wasi/fmt/usage @@ -0,0 +1,8 @@ +The package fmt provides CMake targets: + + find_package(fmt CONFIG REQUIRED) + target_link_libraries(main PRIVATE fmt::fmt) + + # Or use the header-only version + find_package(fmt CONFIG REQUIRED) + target_link_libraries(main PRIVATE fmt::fmt-header-only) \ No newline at end of file diff --git a/cmake/vcpkg/ports/wasm32-wasi/fmt/vcpkg.json b/cmake/vcpkg/ports/wasm32-wasi/fmt/vcpkg.json new file mode 100644 index 0000000..729dedc --- /dev/null +++ b/cmake/vcpkg/ports/wasm32-wasi/fmt/vcpkg.json @@ -0,0 +1,17 @@ +{ + "name": "fmt", + "version": "10.2.1", + "description": "Formatting library for C++. It can be used as a safe alternative to printf or as a fast alternative to IOStreams.", + "homepage": "https://github.com/fmtlib/fmt", + "license": "MIT", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} \ No newline at end of file diff --git a/cmake/vcpkg/triplets/wasm32-wasi.cmake b/cmake/vcpkg/triplets/wasm32-wasi.cmake new file mode 100644 index 0000000..814c0ec --- /dev/null +++ b/cmake/vcpkg/triplets/wasm32-wasi.cmake @@ -0,0 +1,9 @@ +set(VCPKG_TARGET_ARCHITECTURE wasm32) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_CRT_LINKAGE static) + +set(VCPKG_CMAKE_SYSTEM_NAME WASI) +set(VCPKG_CMAKE_SYSTEM_VERSION 1) +set(VCPKG_CMAKE_SYSTEM_PROCESSOR wasm32) + +set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/../../wasi-sdk/wasi-sdk.toolchain.cmake") diff --git a/cmake/vcpkg/triplets/x86-linux-clang-dynamic.cmake b/cmake/vcpkg/triplets/x86-linux-clang-dynamic.cmake index 5d0d6a6..7065a24 100644 --- a/cmake/vcpkg/triplets/x86-linux-clang-dynamic.cmake +++ b/cmake/vcpkg/triplets/x86-linux-clang-dynamic.cmake @@ -3,7 +3,3 @@ set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE dynamic) set(VCPKG_CMAKE_SYSTEM_NAME Linux) - -set(VCPKG_CXX_FLAGS -stdlib=libc++) -set(VCPKG_C_FLAGS -stdlib=libc++) -set(VCPKG_LINKER_FLAGS "-stdlib=libc++ -lc++abi") diff --git a/cmake/wasi-sdk/wasi-sdk.toolchain.cmake b/cmake/wasi-sdk/wasi-sdk.toolchain.cmake new file mode 100644 index 0000000..bbce634 --- /dev/null +++ b/cmake/wasi-sdk/wasi-sdk.toolchain.cmake @@ -0,0 +1,52 @@ +include_guard(GLOBAL) + +cmake_minimum_required(VERSION 3.25) + +get_property(IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE) + +if (IN_TRY_COMPILE) + return() +endif() + +unset(IN_TRY_COMPILE) + +# Until Platform/WASI.cmake is upstream we need to inject the path to it +# into CMAKE_MODULE_PATH. +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/..") + +include(${CMAKE_CURRENT_LIST_DIR}/wasi-sdk_bootstrap.cmake) +wasi_sdk_bootstrap( + TAG wasi-sdk-21 + WASI_SYSROOT_OUTPUT CMAKE_SYSROOT +) + +set(CMAKE_SYSTEM_NAME WASI) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(triple wasm32-wasi) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_ASM_COMPILER clang) +set(CMAKE_AR llvm-ar) +set(CMAKE_RANLIB llvm-ranlib) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) + +# Don't look in the sysroot for executables to run during the build +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Only look in the sysroot (not in the host paths) for the rest +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +set(CLANG_DEFAULT_RTLIB "compiler-rt") +set(LIBCXX_USE_COMPILER_RT "YES") +set(LIBCXXABI_USE_COMPILER_RT "YES") + +add_compile_options( + "-stdlib=libc++" + "-fno-exceptions" + "-D_WASI_EMULATED_SIGNAL" +) diff --git a/cmake/wasi-sdk/wasi-sdk_bootstrap.cmake b/cmake/wasi-sdk/wasi-sdk_bootstrap.cmake new file mode 100644 index 0000000..8fddec0 --- /dev/null +++ b/cmake/wasi-sdk/wasi-sdk_bootstrap.cmake @@ -0,0 +1,122 @@ +function(_wasi_sdk_find_root wasi_sdk_version out_wasi_sdk_root) + if (DEFINED ENV{WASI_SDK_INSTALLATION_ROOT}) + set(root "$ENV{WASI_SDK_INSTALLATION_ROOT}") + elseif(WIN32) + set(root "$ENV{LOCALAPPDATA}/wask-sdk/${wasi_sdk_version}/cache") + else() + set(root "$ENV{HOME}/.cache/wasi-sdk/${wasi_sdk_version}") + endif() + + set(${out_wasi_sdk_root} + ${root} + PARENT_SCOPE + ) +endfunction() + +function(wasi_sdk_bootstrap) + cmake_parse_arguments( + PARSE_ARGV 0 "arg" + "" + "TAG;WASI_SYSROOT_OUTPUT" + "" + ) + if (DEFINED arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR + "internal error: ${CMAKE_CURRENT_FUNCTION} passed extra args:" + "${arg_UNPARSED_ARGUMENTS}" + ) + endif() + + # Parse release version + string(REGEX MATCH "([0-9.])+" wasi_sdk_version ${arg_TAG}) + if (NOT "${wasi_sdk_version}" MATCHES "[.]+") + set(wasi_sdk_version "${wasi_sdk_version}.0") + endif() + + # Locate cache root + if(DEFINED CACHE{_WASI_SDK_ROOT}) + set(wasi_sdk_root $CACHE{_WASI_SDK_ROOT}) + else() + _wasi_sdk_find_root("${wasi_sdk_version}" wasi_sdk_root) + endif() + if (NOT EXISTS ${wasi_sdk_root}) + message(STATUS "Creating wasi-sdk cache directory: ${wasi_sdk_root}") + file(MAKE_DIRECTORY ${wasi_sdk_root}) + else() + message(STATUS "Cache directory for wasi-sdk exists already: ${wasi_sdk_root}") + endif() + + # Define wasi-sdk dependencies, paths and download locations + set(wasi_sdk_sysroot_tarball_name "wasi-sysroot-${wasi_sdk_version}.tar.gz") + set(wasi_sdk_libclang_tarball_name "libclang_rt.builtins-wasm32-wasi-${wasi_sdk_version}.tar.gz") + set(wasi_sdk_sysroot_tarball_path "${wasi_sdk_root}/${wasi_sdk_sysroot_tarball_name}") + set(wasi_sdk_libclang_tarball_path "${wasi_sdk_root}/${wasi_sdk_libclang_tarball_name}") + set(wasi_sdk_release_base_url "https://github.com/WebAssembly/wasi-sdk/releases/download/${arg_TAG}") + set(wasi_sdk_sysroot_tarball_url "${wasi_sdk_release_base_url}/${wasi_sdk_sysroot_tarball_name}") + set(wasi_sdk_libclang_tarball_url "${wasi_sdk_release_base_url}/${wasi_sdk_libclang_tarball_name}") + + # Download wasi-sdk sysroot + if (NOT EXISTS ${wasi_sdk_sysroot_tarball_path}) + message(STATUS "Downloading wasi-sdk sysroot from ${wasi_sdk_sysroot_tarball_url}") + file(DOWNLOAD ${wasi_sdk_sysroot_tarball_url} ${wasi_sdk_sysroot_tarball_path} STATUS wasi_sdk_sysroot_dl_status) + list(GET wasi_sdk_sysroot_dl_status 0 wasi_sdk_sysroot_dl_failed) + if (wasi_sdk_sysroot_dl_failed) + file(REMOVE ${wasi_sdk_sysroot_tarball_path}) + message(FATAL_ERROR "Download for wasi-sdk sysroot failed.") + else() + message(STATUS "Successfully downloaded wasi-sdk sysroot to ${wasi_sdk_sysroot_tarball_path}") + endif() + else() + message(STATUS "wasi-sdk sysroot has already been downloaded and cached.") + endif() + + # Download wasi-sdk libclang runtime builtins + if (NOT EXISTS ${wasi_sdk_libclang_tarball_path}) + message(STATUS "Downloading wasi-sdk libclang runtime builtins from ${wasi_sdk_libclang_tarball_url}") + file(DOWNLOAD ${wasi_sdk_libclang_tarball_url} ${wasi_sdk_libclang_tarball_path} STATUS wasi_sdk_libclang_dl_status) + list(GET wasi_sdk_libclang_dl_status 0 wasi_sdk_libclang_dl_failed) + if (wasi_sdk_libclang_dl_failed) + file(REMOVE ${wasi_sdk_libclang_tarball_path}) + message(FATAL_ERROR "Download for wasi-sdk libclang runtime builtins failed.") + else() + message(STATUS "Successfully downloaded wasi-sdk libclang runtime builtins to ${wasi_sdk_libclang_tarball_path}") + endif() + else() + message(STATUS "wasi-sdk libclang runtime builtins have already been downloaded and cached.") + endif() + + # Extract wasi-sdk sysroot to cache directory + message(STATUS "Extracting wasi-sdk sysroot to ${wasi_sdk_root}") + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf ${wasi_sdk_sysroot_tarball_path} + WORKING_DIRECTORY ${wasi_sdk_root} + ) + + # Extract wasi-sdk libclang runtime builtins to cache directory + message(STATUS "Extracting wasi-sdk libclang runtime builtins to ${wasi_sdk_root}") + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf ${wasi_sdk_libclang_tarball_path} + WORKING_DIRECTORY ${wasi_sdk_root} + ) + + # Move wasi-sdk libclang runtime builtins to clang resource directory + execute_process(COMMAND clang --print-resource-dir OUTPUT_VARIABLE clang_resource_dir OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "Moving wasi-sdk libclang runtime builtins to ${clang_resource_dir}") + if (NOT EXISTS "${clang_resource_dir}/lib/wasi") + message(STATUS "Creating wasi-sdk libclang runtime builtin directory: ${clang_resource_dir}/lib/wasi") + file(MAKE_DIRECTORY "${clang_resource_dir}/lib/wasi") + endif() + file( + COPY_FILE "${wasi_sdk_root}/lib/wasi/libclang_rt.builtins-wasm32.a" "${clang_resource_dir}/lib/wasi/libclang_rt.builtins-wasm32.a" + ONLY_IF_DIFFERENT + RESULT wasi_sdk_runtime_copy_failed + ) + if (wasi_sdk_runtime_copy_failed) + message(FATAL_ERROR "Moving wasi-sdk libclang runtime builtins to ${clang_resource_dir}/lib/wasi failed. \n${wasi_sdk_runtime_copy_failed}\n") + endif() + + set(${arg_WASI_SYSROOT_OUTPUT} + "${wasi_sdk_root}/wasi-sysroot" + PARENT_SCOPE + ) +endfunction() diff --git a/init.cmake b/init.cmake index 2d62393..4a5a3f3 100644 --- a/init.cmake +++ b/init.cmake @@ -71,6 +71,9 @@ set(templates src/cmake/cpp-pt-install-targets.cmake src/cmake/set_cpp_pt_target_properties.cmake src/CMakeLists.txt + src/wasm/src/interface.cpp + src/wasm/include/interface.hpp + src/wasm/CMakeLists.txt tests/module-1 tests/${cpp_pt_module}/header-1_test.cpp tests/${cpp_pt_module}/CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 03034b3..8662b61 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,3 +17,7 @@ include(add_@cpp_pt_cmake@_executable) add_subdirectory(@cpp_pt_module@) add_subdirectory(cli) + +if (WASI) + add_subdirectory(wasm) +endif() diff --git a/src/cmake/add_cpp_pt_module.cmake b/src/cmake/add_cpp_pt_module.cmake index fb4181b..ca86221 100644 --- a/src/cmake/add_cpp_pt_module.cmake +++ b/src/cmake/add_cpp_pt_module.cmake @@ -11,10 +11,23 @@ include(GNUInstallDirs) # generaete header with export macro function(_@cpp_pt_cmake@_module_generate_export_headers target) set(export_file_dir "${CMAKE_CURRENT_BINARY_DIR}/include/@cpp_pt_name@") + set(export_file "${export_file_dir}/${module_name}/export.hpp") generate_export_header(${module_target} - EXPORT_FILE_NAME "${export_file_dir}/${module_name}/export.hpp" + EXPORT_FILE_NAME ${export_file} ) + if (WASI) + file(APPEND ${export_file} "\ + \n#ifndef WASM_EXPORT\ + \n#define WASM_EXPORT(name) extern \"C\" __attribute__((export_name(name)))\ + \n#endif") + else () + file(APPEND ${export_file} "\ + \n#ifndef WASM_EXPORT\ + \n#define WASM_EXPORT(name)\ + \n#endif") + endif () + target_include_directories( ${module_target} ${module_type} $ diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt new file mode 100644 index 0000000..ce2f9bc --- /dev/null +++ b/src/wasm/CMakeLists.txt @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: Copyright 2023 Mikhail Svetkin +# SPDX-License-Identifier: MIT + +add_@cpp_pt_cmake@_executable(wasm) + +target_sources(${@cpp_pt_cmake@_executable_target} PRIVATE + src/interface.cpp + include/interface.hpp +) + +target_link_libraries(${@cpp_pt_cmake@_executable_target} + PRIVATE + @cpp_pt_name@::@cpp_pt_module@ +) + +set_target_properties(${@cpp_pt_cmake@_executable_target} PROPERTIES OUTPUT_NAME "lib@cpp_pt_module@.wasm") +target_link_options(${@cpp_pt_cmake@_executable_target} PRIVATE -nostartfiles -Wl,--no-entry) diff --git a/src/wasm/include/interface.hpp b/src/wasm/include/interface.hpp new file mode 100644 index 0000000..005b3d8 --- /dev/null +++ b/src/wasm/include/interface.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "@cpp_pt_name@/@cpp_pt_module@/export.hpp" + +#include + +// Memory management exports +WASM_EXPORT("wasm_malloc") void* wasm_malloc(size_t nbytes); +WASM_EXPORT("wasm_free") void wasm_free(void* ptr); + +// Exported WASM functions +WASM_EXPORT("version") uint8_t* version(); + +// WASM CLI entrypoint (_start) +int main(int /*argc*/, char* /*argv*/[]); diff --git a/src/wasm/src/interface.cpp b/src/wasm/src/interface.cpp new file mode 100644 index 0000000..ad7596c --- /dev/null +++ b/src/wasm/src/interface.cpp @@ -0,0 +1,33 @@ +#include "../include/interface.hpp" + +#include "@cpp_pt_name@/@cpp_pt_module@/@cpp_pt_module_header@.hpp" + +#include + +#include +#include +#include + +// Memory management exports +void* wasm_malloc(size_t nbytes) { + return std::malloc(nbytes); +} + +void wasm_free(void* ptr) { + return std::free(ptr); +} + +// Exported WASM functions +uint8_t* version() { + std::string version = @cpp_pt_name@::@cpp_pt_module@::version(); + uint8_t* versionPointer = (uint8_t*)wasm_malloc(version.length()); + std::memcpy(versionPointer, version.c_str(), version.length()); + return versionPointer; +} + +// WASM CLI entrypoint (_start) +int main(int /*argc*/, char* /*argv*/[]) { + fmt::println("Hello from the CLI!"); + fmt::println("@cpp_pt_name@ version: {}", @cpp_pt_name@::@cpp_pt_module@::version()); + return 0; +} diff --git a/vcpkg.json b/vcpkg.json index d2de829..2057205 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,11 +1,11 @@ { - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "name": "@cpp_pt_name@", "version-string": "0.0.1", "dependencies": [ "fmt", + { "name": "catch2", "platform": "!wasm32" }, "range-v3", - "ms-gsl", - "catch2" + "ms-gsl" ] }