diff --git a/.github/workflows/testing-macos.yml b/.github/workflows/testing-macos.yml index f95a506db760..a67ef4dc77eb 100644 --- a/.github/workflows/testing-macos.yml +++ b/.github/workflows/testing-macos.yml @@ -232,3 +232,16 @@ jobs: run: | cmake -S . -B build -DHalide_TARGET=wasm-32-wasmrt-wasm_simd128-wasm_threads cmake --build build --target build_generator + + # HelloWasm is a super-build (not add_subdirectory()'d from apps/), so + # it needs its own configure/build/test rather than riding along with + # the "Configure/Build/Test apps" steps above. + - name: Configure HelloWasm + run: cmake -S . -B build -DCMAKE_PREFIX_PATH="${{ github.workspace }}/install" + working-directory: apps/HelloWasm + + - name: Build and test HelloWasm + run: | + cmake --build build + ctest --test-dir build --output-on-failure + working-directory: apps/HelloWasm diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index b75ba3246334..5365d7b1110a 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -60,6 +60,7 @@ add_app(harris) add_app(HelloBaremetal) # add_app(HelloiOS) # don't build HelloiOS here because it isn't universal. add_app(HelloPyTorch) +# add_app(HelloWasm) # driven by its own super-build; see apps/HelloWasm/README.md add_app(hexagon_benchmarks) add_app(hexagon_dma) add_app(hist) diff --git a/apps/HelloWasm/.gitignore b/apps/HelloWasm/.gitignore deleted file mode 100644 index 5a54ff823fc8..000000000000 --- a/apps/HelloWasm/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -js/* -build/ -bin/ diff --git a/apps/HelloWasm/CMakeLists.txt b/apps/HelloWasm/CMakeLists.txt index da9d7edff4ed..e6d9722450bb 100644 --- a/apps/HelloWasm/CMakeLists.txt +++ b/apps/HelloWasm/CMakeLists.txt @@ -1,102 +1,67 @@ cmake_minimum_required(VERSION 3.28) -project(HelloWasm) +project(HelloWasm-Super) -enable_testing() - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED YES) -set(CMAKE_CXX_EXTENSIONS NO) - -find_package(Halide REQUIRED) -set(halide_includes "$") - -find_program(EMCC emcc REQUIRED HINTS "$ENV{EMSDK}/upstream/emscripten") - -configure_file(index.html index.html COPYONLY) - -set(EMCC_FLAGS -s WASM=1 -s USE_SDL=2 -s TOTAL_MEMORY=512MB -O3) -set(EMCC_FLAGS_threads -pthread -matomics) - -add_halide_generator(reaction_diffusion_generator SOURCES reaction_diffusion_generator.cpp) - -set(features_simd -wasm_simd128) -set(features_threads -wasm_threads) -set(params_threads threads=true) -set(params threads=false) -foreach (simd IN ITEMS "" "_simd") - foreach (threads IN ITEMS "" "_threads") - set(config "wasm${simd}${threads}") - set(js_file "js/index${simd}${threads}.js") +include(ExternalProject) - set(target "wasm-32-wasmrt${features${simd}}${features${threads}}") +# Halide generators always run on the host, regardless of the target they +# emit code for -- only the final app (core.cpp, linked with em++) actually +# needs the Emscripten toolchain. This follows the "Use a super-build" +# pattern in doc/HalideCMakePackage.md (see also +# apps/HelloBaremetal/cmake-super_build/ for the same pattern applied to a +# baremetal cross-compile). - foreach (gen IN ITEMS init update render) - add_halide_library( - ${config}_${gen} - FROM reaction_diffusion_generator - GENERATOR reaction_diffusion_${gen} - FUNCTION_NAME reaction_diffusion_${gen} - PARAMS ${params${threads}} - TARGETS ${target} - ) - endforeach () - - set(pipeline_includes "$") - add_custom_command( - OUTPUT ${js_file} - COMMAND - "${EMCC}" - "$" - "$" - "$" - "$" - "$" - ${EMCC_FLAGS} - ${EMCC_FLAGS${threads}} - "-I$" - "-I$" - -o "${js_file}" - VERBATIM - DEPENDS ${config}_init ${config}_update ${config}_render - COMMAND_EXPAND_LISTS - ) +# Prefer $EMSDK (set by sourcing emsdk_env.sh); PATH_SUFFIXES is what lets this +# find em-config without the PATH. +find_program(EM_CONFIG REQUIRED NAMES em-config HINTS ENV EMSDK PATH_SUFFIXES "upstream/emscripten") +execute_process( + COMMAND "${EM_CONFIG}" EMSCRIPTEN_ROOT + OUTPUT_VARIABLE EMSCRIPTEN_ROOT + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY +) +find_file( + EMSCRIPTEN_TOOLCHAIN_FILE REQUIRED + NAMES Emscripten.cmake + PATHS "${EMSCRIPTEN_ROOT}/cmake/Modules/Platform" + NO_DEFAULT_PATH +) - add_custom_target(${config} ALL DEPENDS ${js_file}) - endforeach () -endforeach () +set(GEN_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generator") -foreach (gen IN ITEMS init update render) - add_halide_library( - reaction_diffusion_${gen} - FROM reaction_diffusion_generator - REGISTRATION ${gen}_cpp - PARAMS threads=true - ) -endforeach () +# Default to Release (both sub-projects pick up -O3 this way, standard CMake +# practice, rather than hardcoding optimization flags). +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif () -add_executable(run ${init_cpp} ${update_cpp} ${render_cpp}) -target_link_libraries( - run - PRIVATE - Halide::RunGenMain reaction_diffusion_init reaction_diffusion_update reaction_diffusion_render +# Step 1: build the generator (and the native benchmark) with the host compiler. +ExternalProject_Add( + generator_project + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/generator + BINARY_DIR ${GEN_BINARY_DIR} + INSTALL_COMMAND "" + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} + CONFIGURE_HANDLED_BY_BUILD TRUE ) -add_test( - NAME init_bench - COMMAND - run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_init - --output_extents=[1024,1024] --parsable_output -) -add_test( - NAME update_bench - COMMAND - run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_update - --output_extents=[1024,1024] frame=0 mouse_x=0 mouse_y=0 state=random:0:[1024,1024,3] - --parsable_output +# Step 2: cross-compile the app with Emscripten, importing the generator +# built in step 1. +ExternalProject_Add( + app_project + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/app + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/app + INSTALL_COMMAND "" + DEPENDS generator_project + CMAKE_ARGS + -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN_TOOLCHAIN_FILE} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} + -DHelloWasm-halide_generators_ROOT:FILEPATH=${GEN_BINARY_DIR} + CONFIGURE_HANDLED_BY_BUILD TRUE ) + +enable_testing() add_test( - NAME render_bench - COMMAND - run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_render - --output_extents=[1024,1024] state=random:0:[1024,1024,3] --parsable_output + NAME benchmarks + COMMAND ${CMAKE_CTEST_COMMAND} --test-dir ${GEN_BINARY_DIR} --output-on-failure ) diff --git a/apps/HelloWasm/Makefile b/apps/HelloWasm/Makefile deleted file mode 100644 index d8e89769f3bd..000000000000 --- a/apps/HelloWasm/Makefile +++ /dev/null @@ -1,83 +0,0 @@ -include ../support/Makefile.inc - -# The emscripten compiler -EMCC ?= emcc -EMCC_FLAGS ?= -std=c++17 -s WASM=1 -s USE_SDL=2 -s TOTAL_MEMORY=512MB -O3 -I $(HALIDE_DISTRIB_PATH)/include -EMCC_THREADS_FLAGS ?= $(EMCC_FLAGS) -pthread -matomics - -# the output dir for the .js products must be fixed, because that's what index.html looks for - -all: js/index.js js/index_simd.js js/index_threads.js js/index_simd_threads.js - -$(GENERATOR_BIN)/reaction_diffusion_generator: reaction_diffusion_generator.cpp $(GENERATOR_DEPS) - @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) $(filter %.cpp,$^) -o $@ $(LIBHALIDE_LDFLAGS) - -$(BIN)/wasm/reaction_diffusion_%.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -g reaction_diffusion_$* -e $(GENERATOR_OUTPUTS) -o $(@D) target=wasm-32-wasmrt-no_runtime threads=false - -$(BIN)/wasm/runtime.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -r runtime -o $(@D) target=wasm-32-wasmrt - -$(BIN)/wasm_simd/reaction_diffusion_%.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -g reaction_diffusion_$* -e $(GENERATOR_OUTPUTS) -o $(@D) target=wasm-32-wasmrt-wasm_simd128-no_runtime threads=false - -$(BIN)/wasm_simd/runtime.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -r runtime -o $(@D) target=wasm-32-wasmrt-wasm_simd128 - -$(BIN)/wasm_threads/reaction_diffusion_%.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -g reaction_diffusion_$* -o $(@D) target=wasm-32-wasmrt-wasm_threads-no_runtime threads=true - -$(BIN)/wasm_threads/runtime.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -r runtime -o $(@D) target=wasm-32-wasmrt-wasm_threads - -$(BIN)/wasm_simd_threads/reaction_diffusion_%.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -g reaction_diffusion_$* -o $(@D) target=wasm-32-wasmrt-wasm_simd128-wasm_threads-no_runtime threads=true - -$(BIN)/wasm_simd_threads/runtime.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -r runtime -o $(@D) target=wasm-32-wasmrt-wasm_simd128-wasm_threads - -$(BIN)/$(HL_TARGET)/reaction_diffusion_%.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -g reaction_diffusion_$* -o $(@D) -e registration,static_library target=$(HL_TARGET)-no_runtime threads=true - -$(BIN)/$(HL_TARGET)/runtime.a: $(GENERATOR_BIN)/reaction_diffusion_generator - @mkdir -p $(@D) - $< -r runtime -o $(@D) target=$(HL_TARGET) - -js/index.js: core.cpp $(BIN)/wasm/reaction_diffusion_init.a $(BIN)/wasm/reaction_diffusion_update.a $(BIN)/wasm/reaction_diffusion_render.a $(BIN)/wasm/runtime.a - @mkdir -p $(@D) - $(EMCC) $^ $(EMCC_FLAGS) -I $(BIN)/wasm -o $@ - -js/index_simd.js: core.cpp $(BIN)/wasm_simd/reaction_diffusion_init.a $(BIN)/wasm_simd/reaction_diffusion_update.a $(BIN)/wasm_simd/reaction_diffusion_render.a $(BIN)/wasm_simd/runtime.a - @mkdir -p $(@D) - $(EMCC) $^ $(EMCC_FLAGS) -I $(BIN)/wasm_simd -o $@ - -js/index_threads.js: core.cpp $(BIN)/wasm_threads/reaction_diffusion_init.a $(BIN)/wasm_threads/reaction_diffusion_update.a $(BIN)/wasm_threads/reaction_diffusion_render.a $(BIN)/wasm_threads/runtime.a - @mkdir -p $(@D) - $(EMCC) $^ $(EMCC_THREADS_FLAGS) -I $(BIN)/wasm_threads -o $@ - -js/index_simd_threads.js: core.cpp $(BIN)/wasm_simd_threads/reaction_diffusion_init.a $(BIN)/wasm_simd_threads/reaction_diffusion_update.a $(BIN)/wasm_simd_threads/reaction_diffusion_render.a $(BIN)/wasm_simd_threads/runtime.a - @mkdir -p $(@D) - $(EMCC) $^ $(EMCC_THREADS_FLAGS) -I $(BIN)/wasm_simd_threads -o $@ - -clean: - rm -rf js - rm -rf $(BIN) - -$(BIN)/$(HL_TARGET)/run: $(BIN)/$(HL_TARGET)/reaction_diffusion_init.a $(BIN)/$(HL_TARGET)/reaction_diffusion_update.a $(BIN)/$(HL_TARGET)/reaction_diffusion_render.a $(BIN)/$(HL_TARGET)/runtime.a - mkdir -p $(@D) - $(CXX) $(CXXFLAGS) $(HALIDE_DISTRIB_PATH)/tools/RunGenMain.cpp $(BIN)/$(HL_TARGET)/reaction_diffusion_*.registration.cpp $^ -o $@ -I $(HALIDE_DISTRIB_PATH)/include $(IMAGE_IO_FLAGS) $(LDFLAGS) - -benchmark_native: $(BIN)/$(HL_TARGET)/run - $(BIN)/$(HL_TARGET)/run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_init --output_extents=[1024,1024] --parsable_output - $(BIN)/$(HL_TARGET)/run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_update --output_extents=[1024,1024] frame=0 mouse_x=0 mouse_y=0 state=random:0:[1024,1024,3] --parsable_output - $(BIN)/$(HL_TARGET)/run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_render --output_extents=[1024,1024] state=random:0:[1024,1024,3] --parsable_output diff --git a/apps/HelloWasm/README.md b/apps/HelloWasm/README.md index 810423300dfa..707a9cdcd49a 100644 --- a/apps/HelloWasm/README.md +++ b/apps/HelloWasm/README.md @@ -1,19 +1,58 @@ -This app is a demo of using the Halide webassembly backend +# HelloWasm -To try it out, +A demonstration of integrating a Halide pipeline into a WebAssembly application: +a reaction-diffusion simulation, AOT-compiled by Halide to four WASM variants +(scalar/SIMD x single/multi-threaded), linked into a browser demo with +Emscripten, and switched between at runtime based on what the browser actually +supports. -- install emscripten (see emscripten.org), and have emcc in your path. +## How the pieces fit together -- Build with `make all` +- `generator/reaction_diffusion_generator.cpp` defines the pipeline (three + Halide generators: `init`, `update`, `render`). Generators always run on the + *host*, regardless of what target they emit code for -- this is true even when + that target is `wasm-32-wasmrt`. +- `app/core.cpp` is the actual WASM application: an SDL2 program (compiled by + Emscripten) that calls into the AOT-compiled pipeline each frame and blits the + result to a ``. +- Because the generator needs a host compiler and `core.cpp` needs the + Emscripten compiler, this is a CMake ["super-build"][super-build]: the + top-level `CMakeLists.txt` builds `generator/` with the host compiler first, + then configures `app/` under Emscripten's own CMake toolchain file, importing + the host-built generator to emit the pipeline for four `wasm-32-wasmrt` + variants (`FEATURES wasm_simd128`/`wasm_threads`, or neither). +- `index.html` picks one of the four compiled variants at load time based on + `?simd`/`?threads` URL parameters, falling back to the single-threaded scalar + build by default. -- Run a local webserver using, e.g.: python3 -m http.server 8080 & +## Building -- Load Google chrome (at least version 84), go to chrome://flags, and turn on - all the experimental webassembly stuff (e.g. threads, simd). If you don't do - this, only the single-threaded scalar variant will work (at the time of - writing). +Prerequisites: a Halide install (built via CMake -- see +`doc/BuildingHalideWithCMake.md`) and the [Emscripten SDK][emsdk]. The build +finds `emcc` via `$EMSDK` if you've sourced `emsdk_env.sh`, or otherwise via +`PATH`. -- Visit http://127.0.0.1:8080/index.html +``` +cmake -S apps/HelloWasm -B build -DCMAKE_PREFIX_PATH= +cmake --build build +ctest --test-dir build --output-on-failure +``` -- Finally, run `make benchmark_native` and compare the performance you get with - native code +All build output (the four `index*.js`/`.wasm` pairs, plus copies of +`index.html`/`main.js`/`sw.js`) lands under `build/app/` -- nothing is ever +written back into the source tree. + +## Running the demo + +``` +cd build/app +python3 -m http.server 8080 +``` + +Then visit . The single-threaded scalar +variant should work in any modern browser; the SIMD and multi-threaded variants +may require enabling experimental WebAssembly features (e.g. in Chrome, +`chrome://flags`). + +[emsdk]: https://emscripten.org/docs/getting_started/downloads.html +[super-build]: ../../doc/HalideCMakePackage.md#use-a-super-build diff --git a/apps/HelloWasm/app/CMakeLists.txt b/apps/HelloWasm/app/CMakeLists.txt new file mode 100644 index 000000000000..e62a1d182187 --- /dev/null +++ b/apps/HelloWasm/app/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required(VERSION 3.28) +project(HelloWasm) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) + +# WASM-native dependencies +find_package(Threads REQUIRED) +find_package(SDL2 REQUIRED) + +# Halide and the generator package are both host-built config packages, not +# part of the Emscripten sysroot the toolchain file restricts find_package() +# to by default. +find_package(Halide REQUIRED CMAKE_FIND_ROOT_PATH_BOTH) + +# Import the generator built by the host-side sub-project. +find_package(HelloWasm-halide_generators REQUIRED CMAKE_FIND_ROOT_PATH_BOTH) +set(GEN_TARGET HelloWasm::halide_generators::reaction_diffusion_generator) + +# Keep the static assets next to the build outputs they reference (CMake +# practice: nothing gets written back into the source tree). +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../index.html index.html COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../main.js main.js COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../sw.js sw.js COPYONLY) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/js") + +# No explicit TARGETS on any add_halide_library call below: auto-resolved +# from the ambient CMAKE_SYSTEM_NAME=Emscripten/CMAKE_SYSTEM_PROCESSOR=x86 the +# Emscripten toolchain file sets, to wasm-32-wasmrt. +foreach (simd IN ITEMS "" "_simd") + foreach (threads IN ITEMS "" "_threads") + set(config "index${simd}${threads}") + + set(features "") + if (simd) + list(APPEND features wasm_simd128) + endif () + set(is_threads false) + if (threads) + list(APPEND features wasm_threads) + set(is_threads true) + endif () + + # FILE_BASE_NAME/OUTPUT_DIR: core.cpp #includes the fixed name + # "reaction_diffusion_.h" regardless of variant, so each variant + # needs its own output directory to avoid collisions (FILE_BASE_NAME + # would otherwise default to the per-variant TARGET name instead). + foreach (gen IN ITEMS init update render) + add_halide_library( + ${config}_${gen} + FROM ${GEN_TARGET} + GENERATOR reaction_diffusion_${gen} + FUNCTION_NAME reaction_diffusion_${gen} + FILE_BASE_NAME reaction_diffusion_${gen} + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/${config}" + FEATURES ${features} + PARAMS threads=${is_threads} + ) + endforeach () + + add_executable(${config} core.cpp) + target_link_libraries( + ${config} + PRIVATE ${config}_init ${config}_update ${config}_render SDL2::SDL2 + ) + # -O3 here (not just relying on CMAKE_BUILD_TYPE=Release's compile + # flags) because Emscripten's link step does its own codegen/Binaryen + # optimization passes that CMake's default per-config linker flags + # don't cover. + target_link_options(${config} PRIVATE -sWASM=1 -sTOTAL_MEMORY=512MB -O3) + if (is_threads) + target_link_libraries(${config} PRIVATE Threads::Threads) + target_link_options(${config} PRIVATE -matomics) + endif () + endforeach () +endforeach () diff --git a/apps/HelloWasm/core.cpp b/apps/HelloWasm/app/core.cpp similarity index 100% rename from apps/HelloWasm/core.cpp rename to apps/HelloWasm/app/core.cpp diff --git a/apps/HelloWasm/generator/CMakeLists.txt b/apps/HelloWasm/generator/CMakeLists.txt new file mode 100644 index 000000000000..3a062c376641 --- /dev/null +++ b/apps/HelloWasm/generator/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.28) +project(HelloWasm) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) + +find_package(Halide REQUIRED) + +add_halide_generator(reaction_diffusion_generator SOURCES reaction_diffusion_generator.cpp) + +foreach (gen IN ITEMS init update render) + add_halide_library( + reaction_diffusion_${gen} + FROM reaction_diffusion_generator + REGISTRATION ${gen}_cpp + PARAMS threads=true + ) +endforeach () + +add_executable(run ${init_cpp} ${update_cpp} ${render_cpp}) +target_link_libraries( + run + PRIVATE + Halide::RunGenMain reaction_diffusion_init reaction_diffusion_update reaction_diffusion_render +) + +enable_testing() + +add_test( + NAME init_bench + COMMAND + run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_init + --output_extents=[1024,1024] --parsable_output +) +add_test( + NAME update_bench + COMMAND + run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_update + --output_extents=[1024,1024] frame=0 mouse_x=0 mouse_y=0 state=random:0:[1024,1024,3] + --parsable_output +) +add_test( + NAME render_bench + COMMAND + run --benchmarks=all --benchmark_min_time=1 --name=reaction_diffusion_render + --output_extents=[1024,1024] state=random:0:[1024,1024,3] --parsable_output +) diff --git a/apps/HelloWasm/reaction_diffusion_generator.cpp b/apps/HelloWasm/generator/reaction_diffusion_generator.cpp similarity index 100% rename from apps/HelloWasm/reaction_diffusion_generator.cpp rename to apps/HelloWasm/generator/reaction_diffusion_generator.cpp diff --git a/cmake/HalideGeneratorHelpers.cmake b/cmake/HalideGeneratorHelpers.cmake index e5573f8d7605..ac4e90ea4f11 100644 --- a/cmake/HalideGeneratorHelpers.cmake +++ b/cmake/HalideGeneratorHelpers.cmake @@ -700,6 +700,7 @@ function(add_halide_library TARGET) cmake_path(SET ARG_OUTPUT_DIR "${ARG_OUTPUT_DIR}") cmake_path(ABSOLUTE_PATH ARG_OUTPUT_DIR BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" NORMALIZE) + file(MAKE_DIRECTORY "${ARG_OUTPUT_DIR}") if (ARG_NAMESPACE) set(ARG_FUNCTION_NAME "${ARG_NAMESPACE}::${ARG_FUNCTION_NAME}")