diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb93149b..85121d1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,31 +9,43 @@ jobs: fail-fast: false matrix: platform: - # TODO: Build on more platforms - os: macos-latest cc: clang cxx: clang++ type: static + backend: JavaScriptCore - os: macos-latest cc: clang cxx: clang++ type: shared + backend: JavaScriptCore + # TODO(RaisinTen): Support other OSs and static configuration for + # the V8 backend. + - os: macos-latest + cc: clang + cxx: clang++ + type: shared + backend: V8 - os: ubuntu-latest cc: clang cxx: clang++ type: static + backend: JavaScriptCore - os: ubuntu-latest cc: gcc cxx: g++ type: static + backend: JavaScriptCore - os: ubuntu-latest cc: clang cxx: clang++ type: shared + backend: JavaScriptCore - os: ubuntu-latest cc: gcc cxx: g++ type: shared + backend: JavaScriptCore # Sanitizers - os: ubuntu-latest @@ -41,21 +53,25 @@ jobs: cxx: clang++ type: static options: -DINCLUDEJS_ADDRESS_SANITIZER:BOOL=ON + backend: JavaScriptCore - os: ubuntu-latest cc: clang cxx: clang++ type: static options: -DINCLUDEJS_UNDEFINED_SANITIZER:BOOL=ON + backend: JavaScriptCore - os: macos-latest cc: clang cxx: clang++ type: static options: -DINCLUDEJS_ADDRESS_SANITIZER:BOOL=ON + backend: JavaScriptCore - os: macos-latest cc: clang cxx: clang++ type: static options: -DINCLUDEJS_UNDEFINED_SANITIZER:BOOL=ON + backend: JavaScriptCore runs-on: ${{ matrix.platform.os }} env: @@ -88,7 +104,7 @@ jobs: run: > cmake -S . -B ./build -DCMAKE_BUILD_TYPE:STRING=Release - -DINCLUDE_BACKEND:STRING=JavaScriptCore + -DINCLUDEJS_BACKEND:STRING=${{ matrix.platform.backend }} -DINCLUDEJS_TESTS:BOOL=ON -DINCLUDEJS_DOCS:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF @@ -99,7 +115,7 @@ jobs: run: > cmake -S . -B ./build -DCMAKE_BUILD_TYPE:STRING=Release - -DINCLUDE_BACKEND:STRING=JavaScriptCore + -DINCLUDEJS_BACKEND:STRING=${{ matrix.platform.backend }} -DINCLUDEJS_TESTS:BOOL=ON -DINCLUDEJS_DOCS:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=ON @@ -109,10 +125,10 @@ jobs: - run: cmake --build ./build --config Release --parallel 4 - run: > cmake --install ./build --prefix ./build/dist --config Release --verbose - --component sourcemeta_includejs + --component includejs - run: > cmake --install ./build --prefix ./build/dist --config Release --verbose - --component sourcemeta_includejs_dev + --component includejs_dev # Not every CTest version supports the --test-dir option. If such option # is not recognized, `ctest` will successfully exit finding no tests. diff --git a/Brewfile b/Brewfile index 97725f39..92ae3cd0 100644 --- a/Brewfile +++ b/Brewfile @@ -1,3 +1,4 @@ brew "cmake" brew "clang-format" brew "doxygen" +brew "v8" diff --git a/CMakeLists.txt b/CMakeLists.txt index ffa8cdfa..297f612a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" - COMPONENT sourcemeta_includejs_dev) + COMPONENT includejs_dev) if(INCLUDEJS_ENGINE) add_subdirectory(src/engine) diff --git a/DEPENDENCIES b/DEPENDENCIES index 59d4fdc7..b8e7575f 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,3 +1,3 @@ vendorpull https://github.com/sourcemeta/vendorpull 70342aaf458e6cb80baeb5b718901075fc42ede6 -noa https://github.com/sourcemeta/noa 653bda26413812241e503fd0b550a66f2df4700f +noa https://github.com/sourcemeta/noa 9da0f1877859f60e439e31856cd4ef5fd4ca6063 googletest https://github.com/google/googletest 987e225614755fec7253aa95bf959c09e0d380d7 diff --git a/Makefile b/Makefile index 5854a76e..27ee24da 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ CTEST = ctest # Options PRESET = Debug SHARED = OFF +BACKEND = JavaScriptCore all: configure compile test @@ -12,7 +13,7 @@ configure: .always $(CMAKE) -S . -B ./build \ -DCMAKE_BUILD_TYPE:STRING=$(PRESET) \ -DCMAKE_COMPILE_WARNING_AS_ERROR:BOOL=ON \ - -DINCLUDE_BACKEND:STRING=JavaScriptCore \ + -DINCLUDEJS_BACKEND:STRING=$(BACKEND) \ -DINCLUDEJS_ENGINE:BOOL=ON \ -DINCLUDEJS_TESTS:BOOL=ON \ -DINCLUDEJS_DOCS:BOOL=ON \ @@ -22,9 +23,9 @@ compile: .always $(CMAKE) --build ./build --config $(PRESET) --target clang_format $(CMAKE) --build ./build --config $(PRESET) --parallel 4 $(CMAKE) --install ./build --prefix ./build/dist --config $(PRESET) --verbose \ - --component sourcemeta_includejs + --component includejs $(CMAKE) --install ./build --prefix ./build/dist --config $(PRESET) --verbose \ - --component sourcemeta_includejs_dev + --component includejs_dev lint: .always $(CMAKE) --build ./build --config $(PRESET) --target clang_tidy diff --git a/README.markdown b/README.markdown index 411284b5..a1db321a 100644 --- a/README.markdown +++ b/README.markdown @@ -1 +1,90 @@ -![IncludeJS](./assets/banner.png) +

includejs logo

+

includejs

+

Build your own JavasScript runtime

+

+ github action build +

+ +

+ + 📖 Documentation +

+ +`includejs` offers a unified API for building your own JavaScript runtime, bridging multiple engines and platforms with ease. + +## Example + +In our journey to enhance `includejs`, we've also developed small - a minimalist, experimental runtime +aimed to provide ground for concepts and features we're considering for it. + +Hosted on GitHub at [`crossnx/small`](https://github.com/crossnx/small). + +## Features + +- Multiple runtime support: `JavascriptCore`, `v8` (more in the furture) +- Cross platform support +- Direclty consumable from `CMake` + +> 🤙 Stay tuned as we continue to expand our feature set. + +### JavasScript + +| Feature | JavaScriptCore | V8 | +|----------------|-------------------|------------------| +| Boolean | ✅ Supported | ❌ Not Supported | +| Number | ✅ Supported | ❌ Not Supported | +| String | ✅ Supported | ❌ Not Supported | +| Object | ✅ Supported | ❌ Not Supported | +| Array | ✅ Supported | ❌ Not Supported | +| Promise | ✅ Supported | ❌ Not Supported | +| Function | ✅ Supported | ❌ Not Supported | +| Error | ✅ Supported | ❌ Not Supported | +| Class | ❌ Not Supported | ❌ Not Supported | +| Set | ❌ Not Supported | ❌ Not Supported | +| Map | ❌ Not Supported | ❌ Not Supported | +| BigInt | ❌ Not Supported | ❌ Not Supported | +| Symbol | ❌ Not Supported | ❌ Not Supported | +| ArrayBuffer | ❌ Not Supported | ❌ Not Supported | +| TypedArray | ❌ Not Supported | ❌ Not Supported | + +### WHATWG APIs + +- [ ] Timers +- [ ] Console + +### Platform support + +| Platform | JavaScriptCore | V8 | +|-----------|-------------------|------------------| +| macOS | ✅ Supported | ✅ Supported | +| Linux | ✅ Supported | ✅ Supported | +| Windows | ❌ Not Supported | ❌ Not Supported | + +## Dependencies + +- C++ +- CMake +- JavascriptCore +- Brew (macOS) + +## Usage + +You can consume `includejs` just with CMake: + +```cmake +cmake_minimum_required(VERSION 3.18) + +project(your_runtime VERSION 0.0.1 LANGUAGES CXX) + +include(FetchContent) +FetchContent_Declare( + includejs + GIT_REPOSITORY https://github.com/crossnx/includejs + GIT_TAG main + DOWNLOAD_EXTRACT_TIMESTAMP NO) +FetchContent_MakeAvailable(includejs) + +add_executable(your_runtime main.cc) +target_link_libraries(your_runtime PRIVATE includejs::engine) +``` + diff --git a/assets/banner.png b/assets/banner.png old mode 100644 new mode 100755 index adca42b3..d7d8ff46 Binary files a/assets/banner.png and b/assets/banner.png differ diff --git a/assets/logo.png b/assets/logo.png old mode 100644 new mode 100755 index 5983dad6..faca564d Binary files a/assets/logo.png and b/assets/logo.png differ diff --git a/assets/logo.svg b/assets/logo.svg old mode 100644 new mode 100755 index 4c162864..285f8676 --- a/assets/logo.svg +++ b/assets/logo.svg @@ -1,21 +1 @@ - - - logo@2x - - \ No newline at end of file + \ No newline at end of file diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 918ec110..8db8a300 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -1,4 +1,4 @@ -function(sourcemeta_includejs_add_compile_options target) +function(includejs_add_compile_options target) if(NOA_COMPILER_MSVC) # See https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category target_compile_options("${target}" PRIVATE diff --git a/cmake/FindV8.cmake b/cmake/FindV8.cmake new file mode 100644 index 00000000..eb6cb15f --- /dev/null +++ b/cmake/FindV8.cmake @@ -0,0 +1,27 @@ +if(NOT V8_FOUND) + if(APPLE) + add_library(v8 INTERFACE IMPORTED) + # Since the "includejs" library is compiled with the `-isysroot` flag of + # Clang, it is unable to find the V8 library and headers which have been + # downloaded from Homebrew. + # Refs: https://github.com/Homebrew/homebrew-core/issues/45061#issuecomment-541420664 + set(V8_VERSION "12.1.285.24") + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") + set(HOMEBREW_CELLAR "/opt/homebrew/Cellar") + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(HOMEBREW_CELLAR "/usr/local/Cellar") + endif() + target_include_directories(v8 + INTERFACE "${HOMEBREW_CELLAR}/v8/${V8_VERSION}/include") + target_link_directories(v8 + INTERFACE "${HOMEBREW_CELLAR}/v8/${V8_VERSION}/lib") + target_link_libraries(v8 INTERFACE "-lv8") + target_link_libraries(v8 INTERFACE "-lv8_libplatform") + target_compile_definitions(v8 + INTERFACE V8_COMPRESS_POINTERS) + target_compile_definitions(v8 + INTERFACE V8_ENABLE_SANDBOX) + add_library(V8::V8 ALIAS v8) + set(V8_FOUND ON) + endif() +endif() diff --git a/config.cmake.in b/config.cmake.in index 821612ad..17dce9c9 100644 --- a/config.cmake.in +++ b/config.cmake.in @@ -7,11 +7,13 @@ if(NOT INCLUDEJS_COMPONENTS) list(APPEND INCLUDEJS_COMPONENTS engine) endif() +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + foreach(component ${INCLUDEJS_COMPONENTS}) if(component STREQUAL "engine") include(CMakeFindDependencyMacro) find_dependency(@INCLUDEJS_BACKEND@) - include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_includejs_engine.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/includejs_engine.cmake") else() message(FATAL_ERROR "Unknown IncludeJS component: ${component}") endif() diff --git a/doxygen/logo.png b/doxygen/logo.png old mode 100644 new mode 100755 index bfe3b9d9..a681d326 Binary files a/doxygen/logo.png and b/doxygen/logo.png differ diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index 2cbb7cc5..e54272d3 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -8,25 +8,46 @@ if(INCLUDEJS_BACKEND STREQUAL "JavaScriptCore") list(APPEND INCLUDEJS_ENGINE_SOURCES javascript_core/engine_context.cc) list(APPEND INCLUDEJS_ENGINE_SOURCES javascript_core/engine_promise.cc) list(APPEND INCLUDEJS_ENGINE_SOURCES javascript_core/engine_value.cc) +elseif(INCLUDEJS_BACKEND STREQUAL "V8") + list(APPEND INCLUDEJS_ENGINE_SOURCES v8/engine.cc) else() message(FATAL_ERROR "Unknown IncludeJS backend: ${INCLUDEJS_BACKEND}") endif() -noa_library(NAMESPACE sourcemeta PROJECT includejs NAME engine +noa_library(PROJECT includejs NAME engine FOLDER "IncludeJS/Engine" PRIVATE_HEADERS context.h function.h error.h promise.h value.h SOURCES ${INCLUDEJS_ENGINE_SOURCES}) + if(INCLUDEJS_INSTALL) - noa_library_install(NAMESPACE sourcemeta PROJECT includejs NAME engine) + noa_library_install(PROJECT includejs NAME engine) + if(INCLUDEJS_BACKEND STREQUAL "JavaScriptCore") + install(FILES + "${PROJECT_SOURCE_DIR}/cmake/FindJavaScriptCore.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/includejs" + COMPONENT includejs_dev) + elseif(INCLUDEJS_BACKEND STREQUAL "V8") + install(FILES + "${PROJECT_SOURCE_DIR}/cmake/FindV8.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/includejs" + COMPONENT includejs_dev) + endif() endif() -sourcemeta_includejs_add_compile_options(sourcemeta_includejs_engine) + +includejs_add_compile_options(includejs_engine) if(INCLUDEJS_BACKEND STREQUAL "JavaScriptCore") find_package(JavaScriptCore REQUIRED) - target_link_libraries(sourcemeta_includejs_engine + target_link_libraries(includejs_engine PRIVATE JavaScriptCore::JavaScriptCore) - target_compile_definitions(sourcemeta_includejs_engine - PUBLIC SOURCEMETA_INCLUDEJS_ENGINE_JAVASCRIPT_CORE) + target_compile_definitions(includejs_engine + PUBLIC INCLUDEJS_ENGINE_JAVASCRIPT_CORE) +elseif(INCLUDEJS_BACKEND STREQUAL "V8") + find_package(V8 REQUIRED) + target_link_libraries(includejs_engine + PRIVATE V8::V8) + target_compile_definitions(includejs_engine + PUBLIC INCLUDEJS_ENGINE_V8) else() message(FATAL_ERROR "Unknown IncludeJS backend: ${INCLUDEJS_BACKEND}") endif() diff --git a/src/engine/common/engine.cc b/src/engine/common/engine.cc index 9cb91238..1ea5f3a5 100644 --- a/src/engine/common/engine.cc +++ b/src/engine/common/engine.cc @@ -1,4 +1,4 @@ -#include +#include #include // assert #include // std::ifstream @@ -8,7 +8,8 @@ #include // std::move #include // std::vector -namespace sourcemeta { +#if !defined(INCLUDEJS_ENGINE_V8) + namespace includejs { // For convenience @@ -84,4 +85,5 @@ auto Engine::on_error(const Value &exception) -> void { } } // namespace includejs -} // namespace sourcemeta + +#endif diff --git a/src/engine/common/engine_context.cc b/src/engine/common/engine_context.cc index 5f2e84a9..9875e302 100644 --- a/src/engine/common/engine_context.cc +++ b/src/engine/common/engine_context.cc @@ -1,6 +1,7 @@ -#include +#include + +#if !defined(INCLUDEJS_ENGINE_V8) -namespace sourcemeta { namespace includejs { auto Context::from(int value) const -> Value { @@ -13,4 +14,5 @@ auto Context::from(const std::string &value) const -> Value { } } // namespace includejs -} // namespace sourcemeta + +#endif diff --git a/src/engine/common/engine_error.cc b/src/engine/common/engine_error.cc index abc6f181..a7714423 100644 --- a/src/engine/common/engine_error.cc +++ b/src/engine/common/engine_error.cc @@ -1,9 +1,8 @@ -#include +#include #include // std::cbegin, std::cend #include // std::move -namespace sourcemeta { namespace includejs { Error::Error(std::string &&new_message, FrameContainer &&new_stacktrace) @@ -22,4 +21,3 @@ auto Error::end() const -> FrameContainer::const_iterator { } } // namespace includejs -} // namespace sourcemeta diff --git a/src/engine/include/sourcemeta/includejs/engine.h b/src/engine/include/includejs/engine.h similarity index 80% rename from src/engine/include/sourcemeta/includejs/engine.h rename to src/engine/include/includejs/engine.h index 6afd911f..e2d1c3f8 100644 --- a/src/engine/include/sourcemeta/includejs/engine.h +++ b/src/engine/include/includejs/engine.h @@ -1,15 +1,15 @@ -#ifndef SOURCEMETA_INCLUDEJS_ENGINE_H_ -#define SOURCEMETA_INCLUDEJS_ENGINE_H_ +#ifndef INCLUDEJS_ENGINE_H_ +#define INCLUDEJS_ENGINE_H_ /// @defgroup engine Engine /// @brief The IncludeJS higher-level abstraction of a JavaScript engine #include "engine_export.h" -#include -#include -#include -#include +#include +#include +#include +#include #include // std::filesystem::path #include // std::initializer_list @@ -20,11 +20,10 @@ #include // std::string #include // std::vector -namespace sourcemeta { namespace includejs { /// @ingroup engine -class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Engine { +class INCLUDEJS_ENGINE_EXPORT Engine { public: Engine(); ~Engine(); @@ -35,8 +34,11 @@ class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Engine { auto evaluate(std::ifstream &stream, const std::filesystem::path &path) -> Value; + // TODO(RaisinTen): Add support for bind_function() to the V8 backend. +#if !defined(INCLUDEJS_ENGINE_V8) auto bind_function(std::initializer_list location, Function function) -> void; +#endif auto bind_global(std::initializer_list location, Value value) -> void; auto bind_global(std::initializer_list location, bool value) @@ -55,6 +57,5 @@ class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Engine { }; } // namespace includejs -} // namespace sourcemeta #endif diff --git a/src/engine/include/sourcemeta/includejs/engine_context.h b/src/engine/include/includejs/engine_context.h similarity index 74% rename from src/engine/include/sourcemeta/includejs/engine_context.h rename to src/engine/include/includejs/engine_context.h index 640950d5..80d6c93a 100644 --- a/src/engine/include/sourcemeta/includejs/engine_context.h +++ b/src/engine/include/includejs/engine_context.h @@ -1,19 +1,18 @@ -#ifndef SOURCEMETA_INCLUDEJS_ENGINE_CONTEXT_H_ -#define SOURCEMETA_INCLUDEJS_ENGINE_CONTEXT_H_ +#ifndef INCLUDEJS_ENGINE_CONTEXT_H_ +#define INCLUDEJS_ENGINE_CONTEXT_H_ #include "engine_export.h" -#include -#include +#include +#include #include // std::unique_ptr #include // std::string -namespace sourcemeta { namespace includejs { /// @ingroup engine -class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Context { +class INCLUDEJS_ENGINE_EXPORT Context { public: // Consumers are not meant to create this class directly #ifndef DOXYGEN @@ -24,6 +23,7 @@ class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Context { auto make_error(const std::string &message) const -> Value; auto make_object() const -> Value; auto make_promise() const -> Promise; + auto make_array() const -> Value; auto from(const std::string &value) const -> Value; auto from(const char *) const -> Value; auto from(int value) const -> Value; @@ -38,6 +38,5 @@ class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Context { }; } // namespace includejs -} // namespace sourcemeta #endif diff --git a/src/engine/include/sourcemeta/includejs/engine_error.h b/src/engine/include/includejs/engine_error.h similarity index 76% rename from src/engine/include/sourcemeta/includejs/engine_error.h rename to src/engine/include/includejs/engine_error.h index f1053dfd..8e0b27e6 100644 --- a/src/engine/include/sourcemeta/includejs/engine_error.h +++ b/src/engine/include/includejs/engine_error.h @@ -1,5 +1,5 @@ -#ifndef SOURCEMETA_INCLUDEJS_ENGINE_ERROR_H_ -#define SOURCEMETA_INCLUDEJS_ENGINE_ERROR_H_ +#ifndef INCLUDEJS_ENGINE_ERROR_H_ +#define INCLUDEJS_ENGINE_ERROR_H_ #include "engine_export.h" @@ -9,11 +9,9 @@ #include // std::string #include // std::vector -namespace sourcemeta { namespace includejs { - /// @ingroup engine -struct SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Frame { +struct INCLUDEJS_ENGINE_EXPORT Frame { const std::optional scope; const std::optional path; const unsigned long long line; @@ -21,7 +19,7 @@ struct SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Frame { }; /// @ingroup engine -class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Error : public std::exception { +class INCLUDEJS_ENGINE_EXPORT Error : public std::exception { private: using FrameContainer = std::vector; @@ -35,8 +33,6 @@ class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Error : public std::exception { const std::string message; const FrameContainer stacktrace; }; - } // namespace includejs -} // namespace sourcemeta #endif diff --git a/src/engine/include/sourcemeta/includejs/engine_function.h b/src/engine/include/includejs/engine_function.h similarity index 59% rename from src/engine/include/sourcemeta/includejs/engine_function.h rename to src/engine/include/includejs/engine_function.h index 6b0f51d9..e6d4129f 100644 --- a/src/engine/include/sourcemeta/includejs/engine_function.h +++ b/src/engine/include/includejs/engine_function.h @@ -1,31 +1,29 @@ -#ifndef SOURCEMETA_INCLUDEJS_ENGINE_FUNCTION_H_ -#define SOURCEMETA_INCLUDEJS_ENGINE_FUNCTION_H_ +#ifndef INCLUDEJS_ENGINE_FUNCTION_H_ +#define INCLUDEJS_ENGINE_FUNCTION_H_ #include "engine_export.h" -#include -#include +#include +#include -#if defined(SOURCEMETA_INCLUDEJS_ENGINE_JAVASCRIPT_CORE) +#if defined(INCLUDEJS_ENGINE_JAVASCRIPT_CORE) #include // std::exception #include // std::vector #endif #ifdef DOXYGEN /// @ingroup engine -#define SOURCEMETA_INCLUDEJS_ARGS +#define INCLUDEJS_ARGS #else #if __cplusplus >= 202002L #include // std::span -#define SOURCEMETA_INCLUDEJS_ARGS std::span +#define INCLUDEJS_ARGS std::span #else -#define SOURCEMETA_INCLUDEJS_ARGS \ - const std::vector & +#define INCLUDEJS_ARGS const std::vector & #endif #endif -#if defined(SOURCEMETA_INCLUDEJS_ENGINE_JAVASCRIPT_CORE) -namespace sourcemeta { +#if defined(INCLUDEJS_ENGINE_JAVASCRIPT_CORE) namespace includejs { // This is a opaque function signature that can be force-casted into // JSObjectCallAsFunctionCallback @@ -33,15 +31,14 @@ namespace includejs { using Function = const void *(*)(const void *, const void *, const void *, const size_t, const void *[], const void **); } // namespace includejs -} // namespace sourcemeta #endif -#if defined(SOURCEMETA_INCLUDEJS_ENGINE_JAVASCRIPT_CORE) -#define __SOURCEMETA_INCLUDEJS_EXPOSE_FUNCTION_INTERNAL(function, call_as) \ +#if defined(INCLUDEJS_ENGINE_JAVASCRIPT_CORE) +#define _INCLUDEJS_EXPOSE_FUNCTION_INTERNAL(function, call_as) \ static const void *function(const void *context, const void *, const void *, \ const size_t argc, const void *raw_arguments[], \ const void **exception) { \ - std::vector<::sourcemeta::includejs::Value> arguments; \ + std::vector<::includejs::Value> arguments; \ arguments.reserve(argc); \ for (std::size_t index = 0; index < argc; index++) { \ arguments.emplace_back(context, raw_arguments[index]); \ @@ -49,7 +46,7 @@ using Function = const void *(*)(const void *, const void *, const void *, try { \ return call_as({context}, arguments).native(); \ } catch (const std::exception &error) { \ - const ::sourcemeta::includejs::Context ignition_context{context}; \ + const ::includejs::Context ignition_context{context}; \ *exception = ignition_context.make_error(error.what()).native(); \ return ignition_context.make_undefined().native(); \ } \ @@ -58,15 +55,15 @@ using Function = const void *(*)(const void *, const void *, const void *, #ifdef DOXYGEN /// @ingroup engine -#define SOURCEMETA_INCLUDEJS_EXPOSE_FUNCTION(function) +#define INCLUDEJS_EXPOSE_FUNCTION(function) /// @ingroup engine -#define SOURCEMETA_INCLUDEJS_EXPOSE_TEMPLATE_FUNCTION(function) +#define INCLUDEJS_EXPOSE_TEMPLATE_FUNCTION(function) #else -#define SOURCEMETA_INCLUDEJS_EXPOSE_FUNCTION(function) \ - __SOURCEMETA_INCLUDEJS_EXPOSE_FUNCTION_INTERNAL(function, function) -#define SOURCEMETA_INCLUDEJS_EXPOSE_TEMPLATE_FUNCTION(function) \ +#define INCLUDEJS_EXPOSE_FUNCTION(function) \ + _INCLUDEJS_EXPOSE_FUNCTION_INTERNAL(function, function) +#define INCLUDEJS_EXPOSE_TEMPLATE_FUNCTION(function) \ template \ - __SOURCEMETA_INCLUDEJS_EXPOSE_FUNCTION_INTERNAL(function, function) + _INCLUDEJS_EXPOSE_FUNCTION_INTERNAL(function, function) #endif #endif diff --git a/src/engine/include/sourcemeta/includejs/engine_promise.h b/src/engine/include/includejs/engine_promise.h similarity index 66% rename from src/engine/include/sourcemeta/includejs/engine_promise.h rename to src/engine/include/includejs/engine_promise.h index 7b0d69c8..b7b1ec08 100644 --- a/src/engine/include/sourcemeta/includejs/engine_promise.h +++ b/src/engine/include/includejs/engine_promise.h @@ -1,17 +1,16 @@ -#ifndef SOURCEMETA_INCLUDEJS_ENGINE_PROMISE_H_ -#define SOURCEMETA_INCLUDEJS_ENGINE_PROMISE_H_ +#ifndef INCLUDEJS_ENGINE_PROMISE_H_ +#define INCLUDEJS_ENGINE_PROMISE_H_ #include "engine_export.h" -#include +#include #include // std::unique_ptr -namespace sourcemeta { namespace includejs { /// @ingroup engine -class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Promise { +class INCLUDEJS_ENGINE_EXPORT Promise { public: // Consumers are not meant to create this class directly #ifndef DOXYGEN @@ -27,8 +26,6 @@ class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Promise { std::unique_ptr internal; bool completed{false}; }; - } // namespace includejs -} // namespace sourcemeta #endif diff --git a/src/engine/include/sourcemeta/includejs/engine_value.h b/src/engine/include/includejs/engine_value.h similarity index 70% rename from src/engine/include/sourcemeta/includejs/engine_value.h rename to src/engine/include/includejs/engine_value.h index f5e08c6b..907debd3 100644 --- a/src/engine/include/sourcemeta/includejs/engine_value.h +++ b/src/engine/include/includejs/engine_value.h @@ -1,5 +1,5 @@ -#ifndef SOURCEMETA_INCLUDEJS_ENGINE_VALUE_H_ -#define SOURCEMETA_INCLUDEJS_ENGINE_VALUE_H_ +#ifndef INCLUDEJS_ENGINE_VALUE_H_ +#define INCLUDEJS_ENGINE_VALUE_H_ #include "engine_export.h" @@ -10,12 +10,24 @@ #include // std::string #include // std::vector -namespace sourcemeta { namespace includejs { +enum class ValueType { + Number, + String, + Error, + Object, + Boolean, + Undefined, + Null, + Array, + Function, + Unknown +}; + // Inspired by https://github.com/sourcemeta/jsontoolkit /// @ingroup engine -class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Value { +class INCLUDEJS_ENGINE_EXPORT Value { public: // Consumers are not meant to create this class directly #ifndef DOXYGEN @@ -34,13 +46,22 @@ class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Value { auto is_error() const -> bool; auto is_object() const -> bool; auto is_boolean() const -> bool; + auto is_undefined() const -> bool; + auto is_null() const -> bool; + auto is_array() const -> bool; + auto is_function() const -> bool; auto to_number() const -> double; auto to_string() const -> std::string; auto to_boolean() const -> bool; + auto to_function() const -> Function; + auto type() const -> ValueType; auto at(const std::string &property) const -> std::optional; + auto at(const unsigned int &position) const -> std::optional; auto set(const std::string &property, Value value) -> void; auto set(const std::string &property, Function function) -> void; + auto push(Value value) -> void; auto to_map() const -> std::map; + auto to_vector() const -> std::vector; // For internal use only #ifndef DOXYGEN @@ -53,6 +74,5 @@ class SOURCEMETA_INCLUDEJS_ENGINE_EXPORT Value { }; } // namespace includejs -} // namespace sourcemeta #endif diff --git a/src/engine/javascript_core/engine.cc b/src/engine/javascript_core/engine.cc index da3936a5..d548b408 100644 --- a/src/engine/javascript_core/engine.cc +++ b/src/engine/javascript_core/engine.cc @@ -1,4 +1,4 @@ -#include +#include extern "C" { #include @@ -12,7 +12,6 @@ extern "C" { #include // std::move #include // std::vector -namespace sourcemeta { namespace includejs { struct Engine::Internal { @@ -197,4 +196,3 @@ auto Engine::bind_global(std::initializer_list location, } } // namespace includejs -} // namespace sourcemeta diff --git a/src/engine/javascript_core/engine_context.cc b/src/engine/javascript_core/engine_context.cc index 650ee970..374c3461 100644 --- a/src/engine/javascript_core/engine_context.cc +++ b/src/engine/javascript_core/engine_context.cc @@ -1,4 +1,4 @@ -#include +#include extern "C" { #include @@ -6,7 +6,6 @@ extern "C" { #include // assert -namespace sourcemeta { namespace includejs { struct Context::Internal { @@ -59,6 +58,11 @@ auto Context::make_object() const -> Value { return {this->internal->context, res}; } +auto Context::make_array() const -> Value { + return {this->internal->context, + JSObjectMakeArray(this->internal->context, 0, nullptr, nullptr)}; +} + auto Context::make_promise() const -> Promise { return {static_cast(this->internal->context)}; } @@ -80,9 +84,12 @@ auto Context::from(const char *value) const -> Value { return {this->internal->context, result}; } +auto Context::from(std::nullptr_t) const -> Value { + return {this->internal->context, JSValueMakeNull(this->internal->context)}; +} + auto Context::global() const -> Value { return {this->internal->context, this->internal->global}; } } // namespace includejs -} // namespace sourcemeta diff --git a/src/engine/javascript_core/engine_promise.cc b/src/engine/javascript_core/engine_promise.cc index a3289077..e60e4e87 100644 --- a/src/engine/javascript_core/engine_promise.cc +++ b/src/engine/javascript_core/engine_promise.cc @@ -1,4 +1,4 @@ -#include +#include extern "C" { #include @@ -6,7 +6,6 @@ extern "C" { #include // assert -namespace sourcemeta { namespace includejs { struct Promise::Internal { @@ -54,4 +53,3 @@ auto Promise::value() -> Value { } } // namespace includejs -} // namespace sourcemeta diff --git a/src/engine/javascript_core/engine_value.cc b/src/engine/javascript_core/engine_value.cc index dc05eab9..e29fdf90 100644 --- a/src/engine/javascript_core/engine_value.cc +++ b/src/engine/javascript_core/engine_value.cc @@ -1,4 +1,4 @@ -#include +#include extern "C" { #include @@ -10,7 +10,6 @@ extern "C" { #include // std::runtime_error #include // std::vector -namespace sourcemeta { namespace includejs { struct Value::Internal { @@ -18,6 +17,59 @@ struct Value::Internal { JSValueRef value; }; +static auto js_string_to_std_string(JSStringRef value) -> std::string { + // JavaScriptCore doesn't have a function to fetch the UTF-8 byte size + // of a given string. It can only give us the amount of Unicode code-points + // in the string, which may be more than the bytes required to store it. + // As a consequence, we can't do much than allocating the maximum possible + // buffer to hold the string. + const size_t max_size = JSStringGetMaximumUTF8CStringSize(value); + std::vector buffer(max_size); + // Converts a JavaScript string into a null-terminated UTF-8 string, + // and copies the result into an external byte buffer. + JSStringGetUTF8CString(value, buffer.data(), max_size); + return {buffer.data()}; +} + +// Converting a value into a string first requires copying +// the value reference into a string reference. +static auto js_value_to_std_string(JSContextRef context, JSValueRef value) + -> std::string { + JSValueRef exception = nullptr; + JSStringRef copy = JSValueToStringCopy(context, value, &exception); + assert(!exception); + + try { + std::string result{js_string_to_std_string(copy)}; + JSStringRelease(copy); + return result; + } catch (const std::exception &) { + JSStringRelease(copy); + throw; + } +} + +static auto get_current_object(JSContextRef context, JSValueRef value) + -> JSObjectRef { + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(context, value, &exception); + assert(!exception); + assert(object == value); + return object; +} + +static auto get_object_length(JSContextRef context, JSObjectRef object) + -> std::size_t { + JSValueRef exception = nullptr; + JSStringRef length_string = JSStringCreateWithUTF8CString("length"); + const double length = JSValueToNumber( + context, JSObjectGetProperty(context, object, length_string, &exception), + &exception); + assert(!exception); + JSStringRelease(length_string); + return static_cast(length); +} + Value::Value(const void *context, const void *value) : internal{std::make_unique()} { this->internal->context = static_cast(context); @@ -30,6 +82,32 @@ Value::~Value() {} Value::Value(Value &&other) noexcept : internal{std::move(other.internal)} {} +auto Value::type() const -> ValueType { + if (is_number()) { + return ValueType::Number; + } else if (is_string()) { + return ValueType::String; + } else if (is_error()) { + return ValueType::Error; + } else if (is_function()) { + return ValueType::Function; + } else if (is_array()) { + return ValueType::Array; + } else if (is_object()) { + return ValueType::Object; + } else if (is_boolean()) { + return ValueType::Boolean; + } else if (is_undefined()) { + return ValueType::Undefined; + } else if (is_null()) { + return ValueType::Null; + } else if (is_function()) { + return ValueType::Function; + } else { + return ValueType::Unknown; + } +} + auto Value::is_number() const -> bool { return JSValueIsNumber(this->internal->context, this->internal->value); } @@ -38,6 +116,14 @@ auto Value::is_string() const -> bool { return JSValueIsString(this->internal->context, this->internal->value); } +auto Value::is_undefined() const -> bool { + return JSValueIsUndefined(this->internal->context, this->internal->value); +} + +auto Value::is_array() const -> bool { + return JSValueIsArray(this->internal->context, this->internal->value); +} + auto Value::is_error() const -> bool { JSObjectRef global = JSContextGetGlobalObject(this->internal->context); JSStringRef class_name = JSStringCreateWithUTF8CString("Error"); @@ -66,6 +152,21 @@ auto Value::is_boolean() const -> bool { return JSValueIsBoolean(this->internal->context, this->internal->value); } +auto Value::is_null() const -> bool { + return JSValueIsNull(this->internal->context, this->internal->value); +} + +auto Value::is_function() const -> bool { + if (!is_object()) { + return false; + } + + JSObjectRef object = + get_current_object(this->internal->context, this->internal->value); + + return JSObjectIsFunction(this->internal->context, object); +} + auto Value::to_number() const -> double { assert(is_number()); JSValueRef exception = nullptr; @@ -75,38 +176,6 @@ auto Value::to_number() const -> double { return result; } -static auto js_string_to_std_string(JSStringRef value) -> std::string { - // JavaScriptCore doesn't have a function to fetch the UTF-8 byte size - // of a given string. It can only give us the amount of Unicode code-points - // in the string, which may be more than the bytes required to store it. - // As a consequence, we can't do much than allocating the maximum possible - // buffer to hold the string. - const size_t max_size = JSStringGetMaximumUTF8CStringSize(value); - std::vector buffer(max_size); - // Converts a JavaScript string into a null-terminated UTF-8 string, - // and copies the result into an external byte buffer. - JSStringGetUTF8CString(value, buffer.data(), max_size); - return {buffer.data()}; -} - -// Converting a value into a string first requires copying -// the value reference into a string reference. -static auto js_value_to_std_string(JSContextRef context, JSValueRef value) - -> std::string { - JSValueRef exception = nullptr; - JSStringRef copy = JSValueToStringCopy(context, value, &exception); - assert(!exception); - - try { - std::string result{js_string_to_std_string(copy)}; - JSStringRelease(copy); - return result; - } catch (const std::exception &) { - JSStringRelease(copy); - throw; - } -} - auto Value::to_string() const -> std::string { assert(is_string()); return js_value_to_std_string(this->internal->context, this->internal->value); @@ -117,6 +186,31 @@ auto Value::to_boolean() const -> bool { return JSValueToBoolean(this->internal->context, this->internal->value); } +auto Value::to_function() const -> Function { + assert(is_function()); + + return [context = this->internal->context, + value = this->internal->value](std::vector args) { + JSValueRef exception = nullptr; + JSObjectRef func = JSValueToObject(context, value, &exception); + assert(!exception); + assert(func != nullptr); + + std::vector js_args; + js_args.reserve(args.size()); + for (Value &arg : args) { + js_args.push_back(static_cast(arg.native())); + } + + // Now we can call the function, get the result and return through + // the Value class. + JSValueRef result = JSObjectCallAsFunction( + context, func, nullptr, js_args.size(), js_args.data(), nullptr); + assert(result != nullptr); + return Value{context, result}; + }; +} + auto Value::at(const std::string &property) const -> std::optional { assert(is_object()); JSValueRef exception = nullptr; @@ -135,6 +229,15 @@ auto Value::at(const std::string &property) const -> std::optional { property_string, &exception); assert(!exception); JSStringRelease(property_string); + + // The function could not be called outside of this object context + // as the C++ function store is tied to the object. + // This is why we don't support function properties. + if (JSObjectIsFunction( + this->internal->context, + JSValueToObject(this->internal->context, result, &exception))) { + throw std::runtime_error{"Function properties are not supported"}; + } return std::make_optional(this->internal->context, result); } @@ -248,9 +351,57 @@ auto Value::to_map() const -> std::map { return map; } +auto Value::to_vector() const -> std::vector { + assert(is_array()); + JSObjectRef object = + get_current_object(this->internal->context, this->internal->value); + std::size_t length = get_object_length(this->internal->context, object); + + JSValueRef exception = nullptr; + std::vector vector; + for (unsigned int index = 0; index < static_cast(length); + index++) { + JSValueRef value = JSObjectGetPropertyAtIndex(this->internal->context, + object, index, &exception); + assert(!exception); + vector.emplace_back(static_cast(this->internal->context), + value); + } + return vector; +} + +auto Value::at(const unsigned int &position) const -> std::optional { + assert(is_array()); + JSObjectRef object = + get_current_object(this->internal->context, this->internal->value); + std::size_t length = get_object_length(this->internal->context, object); + + if (position >= static_cast(length)) { + return std::nullopt; + } + + JSValueRef exception = nullptr; + JSValueRef result = JSObjectGetPropertyAtIndex(this->internal->context, + object, position, &exception); + assert(!exception); + return std::make_optional(this->internal->context, result); +} + +auto Value::push(Value value) -> void { + assert(is_array()); + JSObjectRef object = + get_current_object(this->internal->context, this->internal->value); + std::size_t length = get_object_length(this->internal->context, object); + + JSValueRef exception = nullptr; + JSObjectSetPropertyAtIndex( + this->internal->context, object, static_cast(length), + static_cast(value.native()), &exception); + assert(!exception); +} + auto Value::native() const -> const void * { return static_cast(this->internal->value); } } // namespace includejs -} // namespace sourcemeta diff --git a/src/engine/v8/engine.cc b/src/engine/v8/engine.cc new file mode 100644 index 00000000..d6a2721b --- /dev/null +++ b/src/engine/v8/engine.cc @@ -0,0 +1,93 @@ +#include + +#include + +#include +#include + +namespace includejs { + +struct Engine::Internal {}; + +Engine::Engine() : internal{std::make_unique()} { + // TODO(RaisinTen): This code is here to make it easy to verify if the V8 + // integration is working. + + // This example code has been taken from: + // https://chromium.googlesource.com/v8/v8/+/branch-heads/11.9/samples/hello-world.cc + + // Initialize V8. + std::unique_ptr platform = v8::platform::NewDefaultPlatform(); + v8::V8::InitializePlatform(platform.get()); + v8::V8::Initialize(); + + // Create a new Isolate and make it the current one. + v8::Isolate::CreateParams create_params; + create_params.array_buffer_allocator = + v8::ArrayBuffer::Allocator::NewDefaultAllocator(); + v8::Isolate *isolate = v8::Isolate::New(create_params); + { + v8::Isolate::Scope isolate_scope(isolate); + // Create a stack-allocated handle scope. + v8::HandleScope handle_scope(isolate); + // Create a new context. + v8::Local context = v8::Context::New(isolate); + // Enter the context for compiling and running the hello world script. + v8::Context::Scope context_scope(context); + { + // Create a string containing the JavaScript source code. + v8::Local source = + v8::String::NewFromUtf8Literal(isolate, "'Hello' + ', World!'"); + // Compile the source code. + v8::Local script = + v8::Script::Compile(context, source).ToLocalChecked(); + // Run the script to get the result. + v8::Local result = script->Run(context).ToLocalChecked(); + // Convert the result to an UTF8 string and print it. + v8::String::Utf8Value utf8(isolate, result); + printf("%s\n", *utf8); + } + { + // Use the JavaScript API to generate a WebAssembly module. + // + // |bytes| contains the binary format for the following module: + // + // (func (export "add") (param i32 i32) (result i32) + // get_local 0 + // get_local 1 + // i32.add) + // + const char csource[] = R"( + let bytes = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, + 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, + 0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01, + 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b + ]); + let module = new WebAssembly.Module(bytes); + let instance = new WebAssembly.Instance(module); + instance.exports.add(3, 4); + )"; + // Create a string containing the JavaScript source code. + v8::Local source = + v8::String::NewFromUtf8Literal(isolate, csource); + // Compile the source code. + v8::Local script = + v8::Script::Compile(context, source).ToLocalChecked(); + // Run the script to get the result. + v8::Local result = script->Run(context).ToLocalChecked(); + // Convert the result to a uint32 and print it. + uint32_t number = result->Uint32Value(context).ToChecked(); + printf("3 + 4 = %u\n", number); + } + } + // Dispose the isolate and tear down V8. + isolate->Dispose(); + v8::V8::Dispose(); + v8::V8::DisposePlatform(); + delete create_params.array_buffer_allocator; +} + +Engine::~Engine() {} + +} // namespace includejs diff --git a/test/engine/CMakeLists.txt b/test/engine/CMakeLists.txt index 7f621a10..5d5aaefc 100644 --- a/test/engine/CMakeLists.txt +++ b/test/engine/CMakeLists.txt @@ -1,15 +1,25 @@ -add_executable(sourcemeta_includejs_engine_unit +if(INCLUDEJS_BACKEND STREQUAL "V8") + # TODO(RaisinTen): Start testing the V8 backend. + return() +endif() + +add_executable(includejs_engine_unit engine_binding_test.cc engine_evaluate_test.cc engine_stacktraces_test.cc - engine_value_object_test.cc) + engine_value_object_test.cc + engine_value_array_test.cc + engine_value_function_test.cc + engine_value_undefined_test.cc + engine_value_type_test.cc + engine_value_null_test.cc) -sourcemeta_includejs_add_compile_options(sourcemeta_includejs_engine_unit) +includejs_add_compile_options(includejs_engine_unit) -target_link_libraries(sourcemeta_includejs_engine_unit +target_link_libraries(includejs_engine_unit PRIVATE GTest::gtest GTest::gtest_main) -target_link_libraries(sourcemeta_includejs_engine_unit - PRIVATE sourcemeta::includejs::engine) -gtest_discover_tests(sourcemeta_includejs_engine_unit) -set_target_properties(sourcemeta_includejs_engine_unit +target_link_libraries(includejs_engine_unit + PRIVATE includejs::engine) +gtest_discover_tests(includejs_engine_unit) +set_target_properties(includejs_engine_unit PROPERTIES FOLDER "IncludeJS/Engine") diff --git a/test/engine/engine_binding_test.cc b/test/engine/engine_binding_test.cc index 5cc7c688..3e20de74 100644 --- a/test/engine/engine_binding_test.cc +++ b/test/engine/engine_binding_test.cc @@ -1,135 +1,120 @@ #include -#include +#include -static auto is_string(const sourcemeta::includejs::Context &context, - SOURCEMETA_INCLUDEJS_ARGS arguments) - -> sourcemeta::includejs::Value { +static auto is_string(const includejs::Context &context, + INCLUDEJS_ARGS arguments) -> includejs::Value { return context.from(!arguments.empty() && arguments.front().is_string()); } // TODO: Add overload that doesn't take arguments -static auto get_details(const sourcemeta::includejs::Context &context, - SOURCEMETA_INCLUDEJS_ARGS) - -> sourcemeta::includejs::Value { - sourcemeta::includejs::Value result{context.make_object()}; +static auto get_details(const includejs::Context &context, INCLUDEJS_ARGS) + -> includejs::Value { + includejs::Value result{context.make_object()}; result.set("version", context.from("1.0.0")); return result; } -static auto promise_test(const sourcemeta::includejs::Context &context, - SOURCEMETA_INCLUDEJS_ARGS) - -> sourcemeta::includejs::Value { - sourcemeta::includejs::Promise promise{context.make_promise()}; +static auto promise_test(const includejs::Context &context, INCLUDEJS_ARGS) + -> includejs::Value { + includejs::Promise promise{context.make_promise()}; promise.resolve(context.from(true)); return promise.value(); } -SOURCEMETA_INCLUDEJS_EXPOSE_FUNCTION(is_string); -SOURCEMETA_INCLUDEJS_EXPOSE_FUNCTION(get_details); -SOURCEMETA_INCLUDEJS_EXPOSE_FUNCTION(promise_test); +INCLUDEJS_EXPOSE_FUNCTION(is_string); +INCLUDEJS_EXPOSE_FUNCTION(get_details); +INCLUDEJS_EXPOSE_FUNCTION(promise_test); TEST(IncludeJS_Engine, bind_global_root_integer) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_global({"my_global"}, 2); - sourcemeta::includejs::Value result{engine.evaluate("my_global", "index.js")}; + includejs::Value result{engine.evaluate("my_global", "index.js")}; EXPECT_TRUE(result.is_number()); EXPECT_EQ(result.to_number(), 2); } TEST(IncludeJS_Engine, bind_global_nested_integer) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_global({"foo", "bar", "baz"}, 8); - sourcemeta::includejs::Value result{ - engine.evaluate("foo.bar.baz", "index.js")}; + includejs::Value result{engine.evaluate("foo.bar.baz", "index.js")}; EXPECT_TRUE(result.is_number()); EXPECT_EQ(result.to_number(), 8); } TEST(IncludeJS_Engine, bind_global_nested_boolean_true) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_global({"foo", "bar", "baz"}, true); - sourcemeta::includejs::Value result{ - engine.evaluate("foo.bar.baz", "index.js")}; + includejs::Value result{engine.evaluate("foo.bar.baz", "index.js")}; EXPECT_TRUE(result.is_boolean()); EXPECT_EQ(result.to_boolean(), true); } TEST(IncludeJS_Engine, bind_global_nested_boolean_false) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_global({"foo", "bar", "baz"}, false); - sourcemeta::includejs::Value result{ - engine.evaluate("foo.bar.baz", "index.js")}; + includejs::Value result{engine.evaluate("foo.bar.baz", "index.js")}; EXPECT_TRUE(result.is_boolean()); EXPECT_EQ(result.to_boolean(), false); } TEST(IncludeJS_Engine, bind_global_array) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_global({"my", "array"}, {engine.context().from("foo"), engine.context().from("bar"), engine.context().from("baz")}); // Length - sourcemeta::includejs::Value length{ - engine.evaluate("my.array.length", "index.js")}; + includejs::Value length{engine.evaluate("my.array.length", "index.js")}; EXPECT_TRUE(length.is_number()); EXPECT_EQ(length.to_number(), 3); // Value at index 0 - sourcemeta::includejs::Value result1{ - engine.evaluate("my.array[0]", "index.js")}; + includejs::Value result1{engine.evaluate("my.array[0]", "index.js")}; EXPECT_TRUE(result1.is_string()); EXPECT_EQ(result1.to_string(), "foo"); // Value at index 1 - sourcemeta::includejs::Value result2{ - engine.evaluate("my.array[1]", "index.js")}; + includejs::Value result2{engine.evaluate("my.array[1]", "index.js")}; EXPECT_TRUE(result2.is_string()); EXPECT_EQ(result2.to_string(), "bar"); // Value at index 2 - sourcemeta::includejs::Value result3{ - engine.evaluate("my.array[2]", "index.js")}; + includejs::Value result3{engine.evaluate("my.array[2]", "index.js")}; EXPECT_TRUE(result3.is_string()); EXPECT_EQ(result3.to_string(), "baz"); } TEST(IncludeJS_Engine, bind_function_return_boolean_false) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_function({"isString"}, &is_string); - sourcemeta::includejs::Value result{ - engine.evaluate("isString(1)", "index.js")}; + includejs::Value result{engine.evaluate("isString(1)", "index.js")}; EXPECT_TRUE(result.is_boolean()); EXPECT_EQ(result.to_boolean(), false); } TEST(IncludeJS_Engine, bind_function_return_boolean_true) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_function({"isString"}, &is_string); - sourcemeta::includejs::Value result{ - engine.evaluate("isString('foo')", "index.js")}; + includejs::Value result{engine.evaluate("isString('foo')", "index.js")}; EXPECT_TRUE(result.is_boolean()); EXPECT_EQ(result.to_boolean(), true); } TEST(IncludeJS_Engine, bind_function_details_object) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_function({"getDetails"}, &get_details); - sourcemeta::includejs::Value result{ - engine.evaluate("getDetails()", "index.js")}; + includejs::Value result{engine.evaluate("getDetails()", "index.js")}; EXPECT_TRUE(result.is_object()); - const std::optional version{ - result.at("version")}; + const std::optional version{result.at("version")}; EXPECT_TRUE(version.has_value()); EXPECT_TRUE(version->is_string()); EXPECT_EQ(version->to_string(), "1.0.0"); } TEST(IncludeJS_Engine, bind_function_promise) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; engine.bind_function({"myPromise"}, &promise_test); - sourcemeta::includejs::Value result{ - engine.evaluate("myPromise()", "index.js")}; + includejs::Value result{engine.evaluate("myPromise()", "index.js")}; EXPECT_TRUE(result.is_object()); } diff --git a/test/engine/engine_evaluate_test.cc b/test/engine/engine_evaluate_test.cc index 734d9ec5..1ddf673c 100644 --- a/test/engine/engine_evaluate_test.cc +++ b/test/engine/engine_evaluate_test.cc @@ -1,29 +1,27 @@ #include -#include +#include TEST(IncludeJS_Engine, evaluate_result_integer) { - sourcemeta::includejs::Engine engine; - sourcemeta::includejs::Value result{engine.evaluate("1 + 3", "index.js")}; + includejs::Engine engine; + includejs::Value result{engine.evaluate("1 + 3", "index.js")}; EXPECT_TRUE(result.is_number()); EXPECT_EQ(result.to_number(), 4); } TEST(IncludeJS_Engine, evaluate_result_string) { - sourcemeta::includejs::Engine engine; - sourcemeta::includejs::Value result{engine.evaluate("\"foo\"", "index.js")}; + includejs::Engine engine; + includejs::Value result{engine.evaluate("\"foo\"", "index.js")}; EXPECT_TRUE(result.is_string()); EXPECT_EQ(result.to_string(), "foo"); } TEST(IncludeJS_Engine, invalid_function_call) { - sourcemeta::includejs::Engine engine; - EXPECT_THROW(engine.evaluate("xyz()", "index.js"), - sourcemeta::includejs::Error); + includejs::Engine engine; + EXPECT_THROW(engine.evaluate("xyz()", "index.js"), includejs::Error); } TEST(IncludeJS_Engine, evaluate_syntax_error) { - sourcemeta::includejs::Engine engine; - EXPECT_THROW(engine.evaluate("1 +", "index.js"), - sourcemeta::includejs::Error); + includejs::Engine engine; + EXPECT_THROW(engine.evaluate("1 +", "index.js"), includejs::Error); } diff --git a/test/engine/engine_stacktraces_test.cc b/test/engine/engine_stacktraces_test.cc index 597c3cb4..668e9a7f 100644 --- a/test/engine/engine_stacktraces_test.cc +++ b/test/engine/engine_stacktraces_test.cc @@ -1,20 +1,19 @@ #include -#include +#include #include #include TEST(IncludeJS_Engine, unknown_global_function) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; try { engine.evaluate("this_does_not_exist()", "index.js"); FAIL(); - } catch (const sourcemeta::includejs::Error &error) { + } catch (const includejs::Error &error) { EXPECT_EQ(std::string{error.what()}, "Can't find variable: this_does_not_exist"); - const std::vector stacktrace{error.begin(), - error.end()}; + const std::vector stacktrace{error.begin(), error.end()}; EXPECT_EQ(stacktrace.size(), 1); EXPECT_FALSE(stacktrace.at(0).scope.has_value()); EXPECT_TRUE(stacktrace.at(0).path.has_value()); diff --git a/test/engine/engine_value_array_test.cc b/test/engine/engine_value_array_test.cc new file mode 100644 index 00000000..e3dfeaa1 --- /dev/null +++ b/test/engine/engine_value_array_test.cc @@ -0,0 +1,44 @@ +#include + +#include + +TEST(IncludeJS_Engine, create_array) { + includejs::Engine engine; + + auto arr = engine.context().make_array(); + + EXPECT_TRUE(arr.is_array()); +} + +TEST(IncludeJS_Engine, transform_array_to_vector) { + includejs::Engine engine; + + auto arr = engine.context().make_array(); + arr.push(engine.context().from(42)); + + auto vec = arr.to_vector(); + EXPECT_EQ(vec.size(), 1); + EXPECT_EQ(vec.at(0).to_number(), 42); +} + +TEST(IncludeJS_Engine, evaluate_array) { + includejs::Engine engine; + includejs::Value result{engine.evaluate("([])", "index.js")}; + EXPECT_TRUE(result.is_array()); +} + +TEST(IncludeJS_Engine, push_and_get_array_element) { + includejs::Engine engine; + + auto arr = engine.context().make_array(); + arr.push(engine.context().from(42)); + arr.push(engine.context().from("baz")); + + includejs::Value result = arr.at(0).value(); + EXPECT_TRUE(result.is_number()); + EXPECT_EQ(result.to_number(), 42); + + includejs::Value result2 = arr.at(1).value(); + EXPECT_TRUE(result2.is_string()); + EXPECT_EQ(result2.to_string(), "baz"); +} diff --git a/test/engine/engine_value_function_test.cc b/test/engine/engine_value_function_test.cc new file mode 100644 index 00000000..6aba1cd0 --- /dev/null +++ b/test/engine/engine_value_function_test.cc @@ -0,0 +1,23 @@ +#include + +#include + +TEST(IncludeJS_Engine, is_function) { + includejs::Engine engine; + + auto result = engine.evaluate("(function a() {})", "index.js"); + + EXPECT_TRUE(result.is_function()); +} + +TEST(IncludeJS_Engine, to_function) { + includejs::Engine engine; + + auto result = engine.evaluate("(function a() { return 42; })", "index.js"); + + EXPECT_TRUE(result.is_function()); + + auto value = result.to_function()({}); + EXPECT_TRUE(value.is_number()); + EXPECT_EQ(value.to_number(), 42); +} diff --git a/test/engine/engine_value_null_test.cc b/test/engine/engine_value_null_test.cc new file mode 100644 index 00000000..4697afe7 --- /dev/null +++ b/test/engine/engine_value_null_test.cc @@ -0,0 +1,16 @@ +#include + +#include + +TEST(IncludeJS_Engine, create_null) { + includejs::Engine engine; + + auto null = engine.context().from(nullptr); + EXPECT_TRUE(null.is_null()); +} + +TEST(IncludeJS_Engine, evaluate_null) { + includejs::Engine engine; + includejs::Value result{engine.evaluate("null", "index.js")}; + EXPECT_TRUE(result.is_null()); +} diff --git a/test/engine/engine_value_object_test.cc b/test/engine/engine_value_object_test.cc index 3582f28c..6c4da93b 100644 --- a/test/engine/engine_value_object_test.cc +++ b/test/engine/engine_value_object_test.cc @@ -1,37 +1,74 @@ #include -#include +#include -// TODO(tonygo): Add tests for each kind of type -TEST(IncludeJS_Engine, set_object_property) { - sourcemeta::includejs::Engine engine; +TEST(IncludeJS_Engine, create_object) { + includejs::Engine engine; + + auto object = engine.context().make_object(); + + EXPECT_TRUE(object.is_object()); +} + +TEST(IncludeJS_Engine, transform_object_to_map) { + includejs::Engine engine; auto object = engine.context().make_object(); object.set("foo", engine.context().from(42)); - engine.bind_global({"obj"}, std::move(object)); - sourcemeta::includejs::Value result{engine.evaluate("obj.foo", "index.js")}; + auto map = object.to_map(); + EXPECT_EQ(map.size(), 1); + EXPECT_EQ(map.at("foo").to_number(), 42); +} + +TEST(IncludeJS_Engine, evaluate_object) { + includejs::Engine engine; + includejs::Value result{engine.evaluate("({})", "index.js")}; + EXPECT_TRUE(result.is_object()); +} + +TEST(IncludeJS_Engine, set_and_get_object_property) { + includejs::Engine engine; + + auto object = engine.context().make_object(); + object.set("foo", engine.context().from(42)); + object.set("bar", engine.context().from("baz")); + + includejs::Value result = object.at("foo").value(); EXPECT_TRUE(result.is_number()); EXPECT_EQ(result.to_number(), 42); + + includejs::Value result2 = object.at("bar").value(); + EXPECT_TRUE(result2.is_string()); + EXPECT_EQ(result2.to_string(), "baz"); } TEST(IncludeJS_Engine, set_object_function) { - sourcemeta::includejs::Engine engine; + includejs::Engine engine; auto object = engine.context().make_object(); - object.set( - "foo", - [](std::vector arguments) - -> sourcemeta::includejs::Value { return std::move(arguments[0]); }); + object.set("foo", + [](std::vector arguments) -> includejs::Value { + return std::move(arguments[0]); + }); engine.bind_global({"obj"}, std::move(object)); - sourcemeta::includejs::Value result{ - engine.evaluate("obj.foo(42)", "index.js")}; + includejs::Value result{engine.evaluate("obj.foo(42)", "index.js")}; EXPECT_TRUE(result.is_number()); EXPECT_EQ(result.to_number(), 42); - sourcemeta::includejs::Value result2{ - engine.evaluate("obj.foo('bar')", "index.js")}; + includejs::Value result2{engine.evaluate("obj.foo('bar')", "index.js")}; EXPECT_TRUE(result2.is_string()); EXPECT_EQ(result2.to_string(), "bar"); } + +TEST(IncludeJS_Engine, at_throw_for_functions) { + includejs::Engine engine; + includejs::Context &context = engine.context(); + + auto obj = context.make_object(); + obj.set("woo", [](std::vector args) -> includejs::Value { + return std::move(args[0]); + }); + EXPECT_THROW(obj.at("woo"), std::runtime_error); +} diff --git a/test/engine/engine_value_type_test.cc b/test/engine/engine_value_type_test.cc new file mode 100644 index 00000000..b8ed02ac --- /dev/null +++ b/test/engine/engine_value_type_test.cc @@ -0,0 +1,66 @@ +#include + +#include + +TEST(IncludeJS_Engine, null_type) { + includejs::Engine engine; + + auto null = engine.context().from(nullptr); + EXPECT_TRUE(null.type() == includejs::ValueType::Null); +} + +TEST(IncludeJS_Engine, undefined_type) { + includejs::Engine engine; + + auto undefined = engine.context().make_undefined(); + EXPECT_TRUE(undefined.type() == includejs::ValueType::Undefined); +} + +TEST(IncludeJS_Engine, error_type) { + includejs::Engine engine; + + auto error = engine.context().make_error("error"); + EXPECT_TRUE(error.type() == includejs::ValueType::Error); +} + +TEST(IncludeJS_Engine, object_type) { + includejs::Engine engine; + + auto object = engine.context().make_object(); + EXPECT_TRUE(object.type() == includejs::ValueType::Object); +} + +TEST(IncludeJS_Engine, array_type) { + includejs::Engine engine; + + auto array = engine.context().make_array(); + EXPECT_TRUE(array.type() == includejs::ValueType::Array); +} + +TEST(IncludeJS_Engine, number_type) { + includejs::Engine engine; + + auto number = engine.context().from(42); + EXPECT_TRUE(number.type() == includejs::ValueType::Number); +} + +TEST(IncludeJS_Engine, string_type) { + includejs::Engine engine; + + auto string = engine.context().from("foo"); + EXPECT_TRUE(string.type() == includejs::ValueType::String); +} + +TEST(IncludeJS_Engine, boolean_type) { + includejs::Engine engine; + + auto boolean = engine.context().from(true); + EXPECT_TRUE(boolean.type() == includejs::ValueType::Boolean); +} + +TEST(IncludeJS_Engine, function_type) { + includejs::Engine engine; + + auto result = engine.evaluate("(function foo() {})", "index.js"); + EXPECT_TRUE(result.type() == includejs::ValueType::Function); +} diff --git a/test/engine/engine_value_undefined_test.cc b/test/engine/engine_value_undefined_test.cc new file mode 100644 index 00000000..3b885eca --- /dev/null +++ b/test/engine/engine_value_undefined_test.cc @@ -0,0 +1,16 @@ +#include + +#include + +TEST(IncludeJS_Engine, create_undefined) { + includejs::Engine engine; + + auto object = engine.context().make_undefined(); + EXPECT_TRUE(object.is_undefined()); +} + +TEST(IncludeJS_Engine, evaluate_undefined) { + includejs::Engine engine; + includejs::Value result{engine.evaluate("undefined", "index.js")}; + EXPECT_TRUE(result.is_undefined()); +} diff --git a/test/packaging/find_package/CMakeLists.txt b/test/packaging/find_package/CMakeLists.txt index 8ceef97d..a80d027e 100644 --- a/test/packaging/find_package/CMakeLists.txt +++ b/test/packaging/find_package/CMakeLists.txt @@ -3,7 +3,6 @@ project(includejs_hello VERSION 0.0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../../cmake") find_package(IncludeJS REQUIRED) add_executable(includejs_hello hello.cc) -target_link_libraries(includejs_hello PRIVATE sourcemeta::includejs::engine) +target_link_libraries(includejs_hello PRIVATE includejs::engine) diff --git a/test/packaging/find_package/hello.cc b/test/packaging/find_package/hello.cc index d6c0807b..b218544e 100644 --- a/test/packaging/find_package/hello.cc +++ b/test/packaging/find_package/hello.cc @@ -1,13 +1,16 @@ -#include +#include #include // EXIT_SUCCESS #include // EXIT_SUCCESS #include // std::cout auto main() -> int { - sourcemeta::includejs::Engine engine; - sourcemeta::includejs::Value result{engine.evaluate("1 + 3", "index.js")}; + includejs::Engine engine; + // TODO(RaisinTen): Remove this once V8 supports this. +#if !defined(INCLUDEJS_ENGINE_V8) + includejs::Value result{engine.evaluate("1 + 3", "index.js")}; assert(result.is_number()); std::cout << result.to_number() << "\n"; +#endif return EXIT_SUCCESS; } diff --git a/vendor/noa/cmake/noa/library.cmake b/vendor/noa/cmake/noa/library.cmake index 527bbfe5..dd7019a5 100644 --- a/vendor/noa/cmake/noa/library.cmake +++ b/vendor/noa/cmake/noa/library.cmake @@ -2,9 +2,6 @@ function(noa_library) cmake_parse_arguments(NOA_LIBRARY "" "NAMESPACE;PROJECT;NAME;FOLDER" "PRIVATE_HEADERS;SOURCES" ${ARGN}) - if(NOT NOA_LIBRARY_NAMESPACE) - message(FATAL_ERROR "You must pass the namespace name using the NAMESPACE option") - endif() if(NOT NOA_LIBRARY_PROJECT) message(FATAL_ERROR "You must pass the project name using the PROJECT option") endif() @@ -15,7 +12,12 @@ function(noa_library) message(FATAL_ERROR "You must pass the folder name using the FOLDER option") endif() - set(INCLUDE_PREFIX "include/${NOA_LIBRARY_NAMESPACE}/${NOA_LIBRARY_PROJECT}") + if(NOA_LIBRARY_NAMESPACE) + set(INCLUDE_PREFIX "include/${NOA_LIBRARY_NAMESPACE}/${NOA_LIBRARY_PROJECT}") + else() + set(INCLUDE_PREFIX "include/${NOA_LIBRARY_PROJECT}") + endif() + set(PUBLIC_HEADER "${INCLUDE_PREFIX}/${NOA_LIBRARY_NAME}.h") if(NOA_LIBRARY_SOURCES) @@ -27,41 +29,46 @@ function(noa_library) set(ABSOLUTE_PRIVATE_HEADERS) endif() + if(NOA_LIBRARY_NAMESPACE) + set(TARGET_NAME "${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME}") + set(ALIAS_NAME "${NOA_LIBRARY_NAMESPACE}::${NOA_LIBRARY_PROJECT}::${NOA_LIBRARY_NAME}") + else() + set(TARGET_NAME "${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME}") + set(ALIAS_NAME "${NOA_LIBRARY_PROJECT}::${NOA_LIBRARY_NAME}") + endif() + if(NOA_LIBRARY_SOURCES) - add_library(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + add_library(${TARGET_NAME} ${PUBLIC_HEADER} ${ABSOLUTE_PRIVATE_HEADERS} ${NOA_LIBRARY_SOURCES}) else() - add_library(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} INTERFACE + add_library(${TARGET_NAME} INTERFACE ${PUBLIC_HEADER} ${ABSOLUTE_PRIVATE_HEADERS}) endif() - add_library(${NOA_LIBRARY_NAMESPACE}::${NOA_LIBRARY_PROJECT}::${NOA_LIBRARY_NAME} - ALIAS ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME}) + add_library(${ALIAS_NAME} ALIAS ${TARGET_NAME}) if(NOA_LIBRARY_SOURCES) - target_include_directories(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} - PUBLIC - "$" - "$") + target_include_directories(${TARGET_NAME} PUBLIC + "$" + "$") else() - target_include_directories(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} - INTERFACE - "$" - "$") + target_include_directories(${TARGET_NAME} INTERFACE + "$" + "$") endif() if(NOA_LIBRARY_SOURCES) - set_target_properties(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + set_target_properties(${TARGET_NAME} PROPERTIES - OUTPUT_NAME ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + OUTPUT_NAME ${TARGET_NAME} PUBLIC_HEADER "${PUBLIC_HEADER}" PRIVATE_HEADER "${ABSOLUTE_PRIVATE_HEADERS}" EXPORT_NAME "${NOA_LIBRARY_PROJECT}::${NOA_LIBRARY_NAME}" FOLDER "${NOA_LIBRARY_FOLDER}") else() - set_target_properties(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + set_target_properties(${TARGET_NAME} PROPERTIES - OUTPUT_NAME ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + OUTPUT_NAME ${TARGET_NAME} PUBLIC_HEADER "${PUBLIC_HEADER}" PRIVATE_HEADER "${ABSOLUTE_PRIVATE_HEADERS}" FOLDER "${NOA_LIBRARY_FOLDER}") @@ -69,15 +76,15 @@ function(noa_library) if(NOA_LIBRARY_SOURCES) include(GenerateExportHeader) - generate_export_header(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + generate_export_header(${TARGET_NAME} EXPORT_FILE_NAME ${NOA_LIBRARY_NAME}_export.h) - set_target_properties(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + set_target_properties(${TARGET_NAME} PROPERTIES SOVERSION "${PROJECT_VERSION_MAJOR}" VERSION "${PROJECT_VERSION}") # To find the generated files - target_include_directories(${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + target_include_directories(${TARGET_NAME} PUBLIC "$") endif() endfunction() @@ -85,9 +92,6 @@ endfunction() function(noa_library_install) cmake_parse_arguments(NOA_LIBRARY "" "NAMESPACE;PROJECT;NAME" "" ${ARGN}) - if(NOT NOA_LIBRARY_NAMESPACE) - message(FATAL_ERROR "You must pass the namespace name using the NAMESPACE option") - endif() if(NOT NOA_LIBRARY_PROJECT) message(FATAL_ERROR "You must pass the project name using the PROJECT option") endif() @@ -95,22 +99,34 @@ function(noa_library_install) message(FATAL_ERROR "You must pass the library name using the NAME option") endif() + if(NOA_LIBRARY_NAMESPACE) + set(COMPONENT_NAME "${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}") + set(TARGET_NAME "${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME}") + set(INCLUDE_PATH "${CMAKE_INSTALL_INCLUDEDIR}/${NOA_LIBRARY_NAMESPACE}/${NOA_LIBRARY_PROJECT}") + set(NAMESPACE_PREFIX "${NOA_LIBRARY_NAMESPACE}::") + else() + set(COMPONENT_NAME "${NOA_LIBRARY_PROJECT}") + set(TARGET_NAME "${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME}") + set(INCLUDE_PATH "${CMAKE_INSTALL_INCLUDEDIR}/${NOA_LIBRARY_PROJECT}") + set(NAMESPACE_PREFIX "") + endif() + include(GNUInstallDirs) - install(TARGETS ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} - EXPORT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} - PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${NOA_LIBRARY_NAMESPACE}/${NOA_LIBRARY_PROJECT}" - COMPONENT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_dev - PRIVATE_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${NOA_LIBRARY_NAMESPACE}/${NOA_LIBRARY_PROJECT}" - COMPONENT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_dev + install(TARGETS ${TARGET_NAME} + EXPORT ${TARGET_NAME} + PUBLIC_HEADER DESTINATION "${INCLUDE_PATH}" + COMPONENT ${COMPONENT_NAME}_dev + PRIVATE_HEADER DESTINATION "${INCLUDE_PATH}" + COMPONENT ${COMPONENT_NAME}_dev RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - COMPONENT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT} + COMPONENT ${COMPONENT_NAME} LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - COMPONENT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT} - NAMELINK_COMPONENT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_dev + COMPONENT ${COMPONENT_NAME} + NAMELINK_COMPONENT ${COMPONENT_NAME}_dev ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - COMPONENT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_dev) - install(EXPORT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_${NOA_LIBRARY_NAME} + COMPONENT ${COMPONENT_NAME}_dev) + install(EXPORT ${TARGET_NAME} DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${NOA_LIBRARY_PROJECT}" - NAMESPACE ${NOA_LIBRARY_NAMESPACE}:: - COMPONENT ${NOA_LIBRARY_NAMESPACE}_${NOA_LIBRARY_PROJECT}_dev) + NAMESPACE ${NAMESPACE_PREFIX} + COMPONENT ${COMPONENT_NAME}_dev) endfunction()