Skip to content

Instantly share code, notes, and snippets.

@devappd
Created January 28, 2021 01:16
Show Gist options
  • Save devappd/039722ab383a88212c1c7648c04dc84e to your computer and use it in GitHub Desktop.
Save devappd/039722ab383a88212c1c7648c04dc84e to your computer and use it in GitHub Desktop.
CMake Emscripten sample
# CMakeLists.txt
# Entry-point for build configuration.
#
# Copyright (c) 2021 David Apollo (77db70f775fa0b590889c45371a70a1d23e99869d4565976a5207c11606fb6aa)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
########################################################################
# Config
########################################################################
set(PUBLISHER_NAME "MyCompany")
set(PUBLISHER_LONG_NAME "My Company Inc.")
set(MAIN_NAME "MyProject")
set(MAIN_LONG_NAME "My Project")
set(MAIN_VERSION "1.0.0")
set(MAIN_DESCRIPTION "My description.")
set(TARGET_NAME "${MAIN_NAME}")
set(PROJECT_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
########################################################################
# Setup
########################################################################
project("${MAIN_NAME}"
VERSION "${MAIN_VERSION}"
LANGUAGES C CXX
)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -frtti -Wall")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
add_executable("${TARGET_NAME}")
set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME "${TARGET_NAME}")
target_sources("${TARGET_NAME}"
PRIVATE "${PROJECT_PATH}/main.cpp"
# Other sources...
)
if(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
include(SetupEmscripten)
endif()
# SetupEmscripten.cmake
# Build configuration specific to Emscripten.
#
# Copyright (c) 2021 David Apollo (77db70f775fa0b590889c45371a70a1d23e99869d4565976a5207c11606fb6aa)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
########################################################################
# Config
########################################################################
IF(NOT DEFINED EMCC_OUTPUT_TYPE)
set(EMCC_OUTPUT_TYPE ".js" CACHE STRING "Output file type for target: .mjs, .js, .html, .a")
endif()
IF(NOT DEFINED EMCC_MODULARIZE)
set(EMCC_MODULARIZE OFF CACHE BOOL "Output JS as an object module.")
endif()
IF(NOT DEFINED EMCC_USE_PTHREADS)
set(EMCC_USE_PTHREADS OFF CACHE BOOL "Enable multithreading.")
endif()
if(NOT DEFINED EMCC_ASYNCIFY)
set(EMCC_ASYNCIFY OFF CACHE BOOL "Enable compatibility with browser async behavior.")
endif()
if(NOT DEFINED EMCC_WEBGL2)
set(EMCC_WEBGL2 ON CACHE BOOL "Enable support for WebGL 2.")
endif()
# This should be set by the CMake project, not the user.
# if(NOT DEFINED EMCC_EXPORTS)
# set(EMCC_EXPORTS "" CACHE STRING
# "Comma-separated list of C/++ function names to expose to JS. Here, all names are prefixed by underscore. E.g., '_function_name','_function_name2','_function_name3'")
# endif()
if(NOT DEFINED EMCC_EXTRA_EXPORTS)
set(EMCC_EXTRA_EXPORTS "" CACHE STRING
"Comma-separated list of Emscripten function names to expose to JS. Do not prefix names. E.g., 'function_name','function_name2','function_name3'")
endif()
if(NOT DEFINED EMCC_PRELOAD_PATH)
set(EMCC_PRELOAD_PATH "" CACHE STRING
"Path to directory or file to preload on Emscripten startup. If path is a directory, then all files in the directory will be preloaded.")
endif()
if(NOT DEFINED EMCC_SHELL_FILE)
set(EMCC_SHELL_FILE "" CACHE STRING
"Path to shell HTML file. Optional.")
endif()
if(NOT DEFINED EMCC_SINGLE_FILE)
set(EMCC_SINGLE_FILE OFF CACHE BOOL "Compile one output file that combines JS, WASM, and HTML.")
endif()
########################################################################
# Parameter Setup
########################################################################
if(NOT DEFINED EMCC_INITIAL_MEMORY)
set(EMCC_INITIAL_MEMORY "8192000")
# CACHE STRING "Memory size to initialize Emscripten. Multiple of 65536.")
endif()
if(CMAKE_BUILD_TYPE MATCHES "Debug")
add_definitions(-DDEBUG)
set(OPTIMIZATION_LEVEL "0")
set(DEBUG_FLAG "-g")
# set(WEBGL_DEBUG "-s GL_ASSERTIONS=1 ")
else()
set(OPTIMIZATION_LEVEL "3")
set(DEBUG_FLAG "")
endif()
if (EMCC_MODULARIZE)
set(MODULARIZE "-s MODULARIZE=1 ")
endif()
if (EMCC_USE_PTHREADS)
set(USE_PTHREADS "-s USE_PTHREADS=1 ")
set(ENVIRONMENT "-s ENVIRONMENT='web,worker' -s EXPORT_NAME=Mod ")
if (EMCC_MODULARIZE)
set(MODULARIZE "${MODULARIZE} -s EXPORT_ES6=1 ")
endif()
else()
set(ENVIRONMENT "-s ENVIRONMENT='web' ")
endif()
if (EMCC_ASYNCIFY)
# Add trailing space for compiler parsing
set(ASYNCIFY "-s ASYNCIFY=1 ")
add_definitions(-DASYNCIFY)
else()
set(ASYNCIFY "")
endif()
if(EMCC_WEBGL2)
set(WEBGL2 "-s USE_WEBGL2=1 ")
add_definitions(-DUSE_ES3)
else()
set(WEBGL2 "")
endif()
# Set preload path
if(EXISTS "${EMCC_PRELOAD_PATH}")
# Dodge error about path below current directory
file(COPY "${EMCC_PRELOAD_PATH}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
# Get base name
get_filename_component(EMCC_PRELOAD_NAME "${EMCC_PRELOAD_PATH}" NAME)
# Prepare linker flag
if(IS_DIRECTORY "${EMCC_PRELOAD_PATH}")
set(EMCC_PRELOAD "--preload-file \"${CMAKE_CURRENT_BINARY_DIR}/${EMCC_PRELOAD_NAME}\"@\"/\"")
else()
set(EMCC_PRELOAD "--preload-file \"${CMAKE_CURRENT_BINARY_DIR}/${EMCC_PRELOAD_NAME}\"@\"/${EMCC_PRELOAD_NAME}\"")
endif()
elseif(EMCC_PRELOAD_PATH) # Is non-empty
message(SEND_ERROR "Preload path not found:\n"
" ${EMCC_PRELOAD_PATH}")
endif()
if(EXISTS "${EMCC_SHELL_FILE}")
# Retrieve latest copy into build cache
file(COPY "${EMCC_SHELL_FILE}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
# Get base name
get_filename_component(EMCC_SHELL_NAME "${EMCC_SHELL_FILE}" NAME)
# Build parameter
set(EMCC_SHELL "--shell-file \"${CMAKE_CURRENT_BINARY_DIR}/${EMCC_SHELL_NAME}\"")
elseif(EMCC_SHELL_FILE) # Is non-empty
message(SEND_ERROR "Shell HTML file not found:\n"
" ${EMCC_SHELL_FILE}")
endif()
if(EMCC_SINGLE_FILE)
set(SINGLE_FILE "-s SINGLE_FILE=1")
endif()
########################################################################
# Compiler Setup
########################################################################
add_definitions(-DEMSCRIPTEN)
# Export functions to JS.
# First set standard list, not closed by bracket so we can append
set(EMCC_EXPORT_LIST "\"['_main',")
# Now append custom exports
if(EMCC_EXPORTS)
set(EMCC_EXPORT_LIST "${EMCC_EXPORT_LIST},${EMCC_EXPORTS}]\"")
else()
set(EMCC_EXPORT_LIST "${EMCC_EXPORT_LIST}]\"")
endif()
set(EMCC_EXTRA_EXPORT_LIST "\"['ccall'")
if(EMCC_EXTRA_EXPORTS)
set(EMCC_EXTRA_EXPORT_LIST "${EMCC_EXTRA_EXPORT_LIST},${EMCC_EXTRA_EXPORTS}]\"")
else()
set(EMCC_EXTRA_EXPORT_LIST "${EMCC_EXTRA_EXPORT_LIST}]\"")
endif()
# Set compiler args
# We use semicolons so that CMake does not pass the entire string as one quoted flag
set(EMCC_ARGS "SHELL: \
${ASYNCIFY} \
-O${OPTIMIZATION_LEVEL} \
--llvm-opts ${OPTIMIZATION_LEVEL} \
${DEBUG_FLAG} \
-s FULL_ES2=1 \
-s FULL_ES3=1 \
-s DEMANGLE_SUPPORT=1 \
${USE_PTHREADS}"
)
# Set linker args
set(EMCC_LINK_ARGS "SHELL: \
${SINGLE_FILE} \
-s TOTAL_MEMORY=${EMCC_INITIAL_MEMORY} \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXPORTED_FUNCTIONS=${EMCC_EXPORT_LIST} \
-s EXTRA_EXPORTED_RUNTIME_METHODS=${EMCC_EXTRA_EXPORT_LIST} \
${ENVIRONMENT} \
${USE_PTHREADS} \
${WEBGL2} \
${WEBGL_DEBUG} \
${EMCC_PRELOAD} ${EMCC_SHELL} \
${MODULARIZE} \
-lidbfs.js"
)
########################################################################
# Libraries
########################################################################
target_compile_options(${TARGET_NAME} PRIVATE "SHELL:-s USE_SDL=2 -s USE_SDL_MIXER=2")
target_link_options(${TARGET_NAME} PRIVATE "SHELL:-s USE_SDL=2 -s USE_SDL_MIXER=2")
########################################################################
# Install target
########################################################################
if(NOT INSTALL_SUBPREFIX)
set(INSTALL_SUBPREFIX ".")
endif()
if(NOT DEFINED OUTPUT_NAME)
set(OUTPUT_NAME "${TARGET_NAME}")
endif()
# Set install path to project dir by default
get_filename_component(ABS_PROJECT_PATH "${PROJECT_PATH}" ABSOLUTE)
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set (CMAKE_INSTALL_PREFIX "${PROJECT_PATH}/emscripten/data" CACHE PATH "Install location for the program." FORCE)
endif()
# Print info message
get_filename_component(ABS_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}" ABSOLUTE)
message(STATUS "Project install directory is set to:\n"
"\n"
" ${ABS_INSTALL_PATH}/${INSTALL_SUBPREFIX}\n"
)
# Install program binaries and preloaded data
install(DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/"
DESTINATION "${INSTALL_SUBPREFIX}"
FILES_MATCHING
PATTERN "${OUTPUT_NAME}.data"
PATTERN "${OUTPUT_NAME}.html"
PATTERN "${OUTPUT_NAME}.js"
PATTERN "${OUTPUT_NAME}.worker.js"
PATTERN "${OUTPUT_NAME}.mjs"
PATTERN "${OUTPUT_NAME}.a"
PATTERN "${OUTPUT_NAME}.wasm"
PATTERN "${OUTPUT_NAME}.wasm.map"
)
########################################################################
# Wire it all together
########################################################################
set(CMAKE_EXECUTABLE_SUFFIX ${EMCC_OUTPUT_TYPE})
target_compile_options(${TARGET_NAME} PRIVATE ${EMCC_ARGS})
target_link_options(${TARGET_NAME} PRIVATE ${EMCC_ARGS})
target_link_options(${TARGET_NAME} PRIVATE ${EMCC_LINK_ARGS})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment