diff --git a/CMakeLists.txt b/CMakeLists.txt index ff77368ba..bf7128b9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,3 +22,5 @@ include( ${PROJECT_SOURCE_DIR}/cmake/godotcpp.cmake ) godotcpp_options() godotcpp_generate() + +godotcpp_installable() diff --git a/SConstruct b/SConstruct index 8acea2624..96018406e 100644 --- a/SConstruct +++ b/SConstruct @@ -48,4 +48,7 @@ if scons_cache_path is not None: cpp_tool.generate(env) library = env.GodotCPP() +if library: + env.InstallableGodotCPP(library) + Return("env") diff --git a/cmake/godot-cpp.pc.in b/cmake/godot-cpp.pc.in new file mode 100644 index 000000000..1813706b0 --- /dev/null +++ b/cmake/godot-cpp.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ + +Name: godot-cpp +Description: C++ bindings for the Godot script API +Version: @GODOT_API_VERSION@ +Libs: -L${libdir} -l@GODOTCPP_OUTPUT_NAME@ +Cflags: -I${includedir} diff --git a/cmake/godotcpp.cmake b/cmake/godotcpp.cmake index a5c667796..a622c68d0 100644 --- a/cmake/godotcpp.cmake +++ b/cmake/godotcpp.cmake @@ -1,10 +1,13 @@ +include("CMakePackageConfigHelpers") +include("GNUInstallDirs") + function( godotcpp_options ) #TODO platform #TODO target # Input from user for GDExtension interface header and the API JSON file - set(GODOT_GDEXTENSION_DIR "gdextension" CACHE PATH + set(GODOT_GDEXTENSION_DIR "${CMAKE_CURRENT_SOURCE_DIR}/gdextension" CACHE PATH "Path to a custom directory containing GDExtension interface header and API JSON file ( /path/to/gdextension_dir )" ) set(GODOT_CUSTOM_API_FILE "" CACHE FILEPATH "Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`) ( /path/to/custom_api_file )") @@ -204,9 +207,10 @@ function( godotcpp_generate ) endif () target_include_directories(${PROJECT_NAME} ${GODOT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC - include - ${CMAKE_CURRENT_BINARY_DIR}/gen/include - ${GODOT_GDEXTENSION_DIR} + "$" + "$" + "$" + "$" ) # Add the compile flags @@ -235,6 +239,66 @@ function( godotcpp_generate ) LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin" OUTPUT_NAME "${OUTPUT_NAME}" + EXPORT_NAME "cpp" # This ensures that the exported target is godot::cpp when installed ) endfunction() + +function( godotcpp_installable ) + # Install the library + install(TARGETS "godot-cpp" + EXPORT "godot-config" + ARCHIVE + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT "godot-cpp" + ) + #Install the headers + install( + DIRECTORY + "${PROJECT_SOURCE_DIR}/include/" + "${PROJECT_BINARY_DIR}/gen/include/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + COMPONENT "godot-cpp" + ) + install(FILES "${GODOT_GDEXTENSION_DIR}/gdextension_interface.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + COMPONENT "godot-cpp" + ) + # Install the export config file, so the library can be found via find_package + install(EXPORT "godot-config" + NAMESPACE "godot::" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/godot" + COMPONENT "godot-cpp" + ) + + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.19") # string(JSON...) only available in cmake v3.19+ + # Use the JSON api file to get the version + file(READ "${GODOT_GDEXTENSION_DIR}/extension_api.json" GODOT_GDEXTENSION_API_JSON) + string(JSON GODOT_API_VERSION_MAJOR GET "${GODOT_GDEXTENSION_API_JSON}" "header" "version_major") # GODOT_API_VERSION_MAJOR = GODOT_GDEXTENSION_API_JSON["header"]["version_major"] + string(JSON GODOT_API_VERSION_MINOR GET "${GODOT_GDEXTENSION_API_JSON}" "header" "version_minor") + string(JSON GODOT_API_VERSION_PATCH GET "${GODOT_GDEXTENSION_API_JSON}" "header" "version_patch") + set(GODOT_API_VERSION "${GODOT_API_VERSION_MAJOR}.${GODOT_API_VERSION_MINOR}.${GODOT_API_VERSION_PATCH}") + # Install the config version file so that the gdextension version can be specified in find_package + write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/godot-config-version.cmake" + VERSION "${GODOT_API_VERSION}" + COMPATIBILITY SameMinorVersion # https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/what_is_gdextension.html#version-compatibility + ) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/godot-config-version.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/godot" + COMPONENT "godot-cpp" + ) + + # Install the pkg-config file + get_target_property(GODOTCPP_OUTPUT_NAME "${PROJECT_NAME}" OUTPUT_NAME) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/godot-cpp.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/godot-cpp.pc" + @ONLY + ) + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/godot-cpp.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" + COMPONENT "godot-cpp" + ) + endif() +endfunction() diff --git a/tools/godotcpp.py b/tools/godotcpp.py index 77a0740fc..ffc6cbbb4 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -338,6 +338,33 @@ def options(opts, env): opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) + # Scons lack a default install prefix, so use CMake's as a default for feature parity + # https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html + opts.Add( + PathVariable( + "install_prefix_dir", + "The directory to install to", + "C:/Program Files/godot-cpp" if default_platform == "windows" else "/usr/local", + PathVariable.PathAccept, + ) + ) + opts.Add( + PathVariable( + "install_lib_dir", + "The directory to install the library (relative from the prefix)", + "lib", + PathVariable.PathAccept, + ) + ) + opts.Add( + PathVariable( + "install_include_dir", + "The directory to install the headers (relative from the prefix)", + "include", + PathVariable.PathAccept, + ) + ) + # Add platform options (custom tools can override platforms) for pl in sorted(set(platforms + custom_platforms)): tool = Tool(pl, toolpath=get_platform_tools_paths(env)) @@ -526,6 +553,7 @@ def generate(env): } ) env.AddMethod(_godot_cpp, "GodotCPP") + env.AddMethod(_installable, "InstallableGodotCPP") def _godot_cpp(env): @@ -571,3 +599,58 @@ def _godot_cpp(env): env.AppendUnique(LIBS=[env.File("bin/%s" % library_name)]) return library + + +def _installable(env, library): + import itertools + import json + + install_prefix_dir = env["install_prefix_dir"] + full_install_lib_dir = os.path.join(install_prefix_dir, env["install_lib_dir"]) + full_install_include_dir = os.path.join(install_prefix_dir, env["install_include_dir"]) + + # Obtain the gdextension version + extension_dir = normalize_path(env.get("gdextension_dir", env.Dir("gdextension").abspath), env) + api_filename = normalize_path( + env.get("custom_api_file", env.File(extension_dir + "/extension_api.json").abspath), + env, + ) + with open(api_filename, "r") as api_json_file: + api_data = json.load(api_json_file) + version_major = api_data["header"]["version_major"] + version_minor = api_data["header"]["version_minor"] + version_patch = api_data["header"]["version_patch"] + gd_version = f"{version_major}.{version_minor}.{version_patch}" + + # Configure and install the pkgconfig file + libname = str(library[0]) + pkgconfig = env.Substfile( + target="gen/godot-cpp.pc", + source="cmake/godot-cpp.pc.in", + SUBST_DICT={ + "@CMAKE_INSTALL_PREFIX@": install_prefix_dir, + "@CMAKE_INSTALL_FULL_INCLUDEDIR@": full_install_include_dir, + "@CMAKE_INSTALL_FULL_LIBDIR@": full_install_lib_dir, + "@GODOT_API_VERSION@": gd_version, + "@GODOTCPP_OUTPUT_NAME@": libname, + }, + ) + full_install_pkgconfig_dir = os.path.join(full_install_lib_dir, "pkgconfig") + env.Install(full_install_pkgconfig_dir, pkgconfig) + + # Install the headers + headers_from = [] + headers_to = [] + for dir_from, _, file_lst in itertools.chain(os.walk("include/godot_cpp"), os.walk("gen/include/godot_cpp")): + dir_to = os.path.join(full_install_include_dir, dir_from[dir_from.find("/godot_cpp") + 1 :]) + headers_from += [os.path.join(dir_from, filename) for filename in file_lst] + headers_to += [os.path.join(dir_to, filename) for filename in file_lst] + env.InstallAs(headers_to, headers_from) + # Install the gdextension files + interface_header = os.path.join(extension_dir, "gdextension_interface.h") + env.Install(full_install_include_dir, interface_header) + + # Install the static library + env.Install(full_install_lib_dir, library) + + env.Alias("install", env["install_prefix_dir"])