Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry-picks for the godot-cpp 4.1 branch - 2nd batch #1227

Merged
merged 10 commits into from
Sep 2, 2023
43 changes: 26 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

> **Warning**
>
> This repository's `master` branch is only usable with the latest version of
> Godot's ([GDExtension](https://godotengine.org/article/introducing-gd-extensions))
> API (Godot 4.1 and later).
> This repository's `master` branch is only usable with
> [GDExtension](https://godotengine.org/article/introducing-gd-extensions)
> from Godot's `master` branch.
>
> For users of Godot 4.0.x, switch to the [`4.0`](https://github.com/godotengine/godot-cpp/tree/4.0) branch.
> For users of stable branches, switch to the branch matching your target Godot version:
> - [`4.0`](https://github.com/godotengine/godot-cpp/tree/4.0)
> - [`4.1`](https://github.com/godotengine/godot-cpp/tree/4.1)
>
> Or check out the Git tag matching your Godot version (e.g. `godot-4.1.1-stable`).
>
> For GDNative users (Godot 3.x), switch to the [`3.x`](https://github.com/godotengine/godot-cpp/tree/3.x)
> or the [`3.5`](https://github.com/godotengine/godot-cpp/tree/3.5) branch.
Expand Down Expand Up @@ -52,9 +56,10 @@ first-party `godot-cpp` extension.

Some compatibility breakage is to be expected as GDExtension and `godot-cpp`
get more used, documented, and critical issues get resolved. See the
[issue tracker](https://github.com/godotengine/godot/issues) for a list of known
issues, and be sure to provide feedback on issues and PRs which affect your use
of this extension.
[Godot issue tracker](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aopen+label%3Atopic%3Agdextension)
and the [godot-cpp issue tracker](https://github.com/godotengine/godot/issues)
for a list of known issues, and be sure to provide feedback on issues and PRs
which affect your use of this extension.

## Contributing

Expand All @@ -76,22 +81,22 @@ just like before.

To use the shared lib in your Godot project you'll need a `.gdextension`
file, which replaces what was the `.gdnlib` before.
Follow [the example](test/demo/example.gdextension):
See [example.gdextension](test/project/example.gdextension) used in the test project:

```ini
[configuration]

entry_symbol = "example_library_init"
compatibility_minimum = 4.1
compatibility_minimum = "4.1"

[libraries]

macos.debug = "bin/libgdexample.macos.debug.framework"
macos.release = "bin/libgdexample.macos.release.framework"
windows.debug.x86_64 = "bin/libgdexample.windows.debug.x86_64.dll"
windows.release.x86_64 = "bin/libgdexample.windows.release.x86_64.dll"
linux.debug.x86_64 = "bin/libgdexample.linux.debug.x86_64.so"
linux.release.x86_64 = "bin/libgdexample.linux.release.x86_64.so"
macos.debug = "res://bin/libgdexample.macos.debug.framework"
macos.release = "res://bin/libgdexample.macos.release.framework"
windows.debug.x86_64 = "res://bin/libgdexample.windows.debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libgdexample.windows.release.x86_64.dll"
linux.debug.x86_64 = "res://bin/libgdexample.linux.debug.x86_64.so"
linux.release.x86_64 = "res://bin/libgdexample.linux.release.x86_64.so"
# Repeat for other architectures to support arm64, rv64, etc.
```

Expand Down Expand Up @@ -129,6 +134,10 @@ void initialize_example_module(ModuleInitializationLevel p_level) {

Any node and resource you register will be available in the corresponding `Create...` dialog. Any class will be available to scripting as well.

## Included example
## Examples and templates

See the [godot-cpp-template](https://github.com/godotengine/godot-cpp-template) project for a
generic reusable template.

Check the project in the `test` folder for an example on how to use and register different things.
Or checkout the code for the [Summator example](https://github.com/paddy-exe/GDExtensionSummator)
as shown in the [official documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/gdextension/gdextension_cpp_example.html).
282 changes: 6 additions & 276 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,9 @@ import subprocess
from binding_generator import scons_generate_bindings, scons_emit_files
from SCons.Errors import UserError

EnsureSConsVersion(4, 0)


def add_sources(sources, dir, extension):
for f in os.listdir(dir):
if f.endswith("." + extension):
sources.append(dir + "/" + f)


def normalize_path(val):
return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val)


def validate_file(key, val, env):
if not os.path.isfile(normalize_path(val)):
raise UserError("'%s' is not a file: %s" % (key, val))


def validate_dir(key, val, env):
if not os.path.isdir(normalize_path(val)):
raise UserError("'%s' is not a directory: %s" % (key, val))


def validate_parent_dir(key, val, env):
if not os.path.isdir(normalize_path(os.path.dirname(val))):
raise UserError("'%s' is not a directory: %s" % (key, os.path.dirname(val)))


def get_gdextension_dir(env):
return normalize_path(env.get("gdextension_dir", env.Dir("gdextension").abspath))


def get_api_file(env):
return normalize_path(env.get("custom_api_file", os.path.join(get_gdextension_dir(env), "extension_api.json")))

EnsureSConsVersion(4, 0)

# Try to detect the host platform automatically.
# This is used if no `platform` argument is passed
if sys.platform.startswith("linux"):
default_platform = "linux"
elif sys.platform == "darwin":
default_platform = "macos"
elif sys.platform == "win32" or sys.platform == "msys":
default_platform = "windows"
elif ARGUMENTS.get("platform", ""):
default_platform = ARGUMENTS.get("platform")
else:
raise ValueError("Could not detect platform automatically, please specify with platform=<platform>")

try:
Import("env")
Expand All @@ -65,24 +20,6 @@ except:

env.PrependENVPath("PATH", os.getenv("PATH"))

# Default num_jobs to local cpu count if not user specified.
# SCons has a peculiarity where user-specified options won't be overridden
# by SetOption, so we can rely on this to know if we should use our default.
initial_num_jobs = env.GetOption("num_jobs")
altered_num_jobs = initial_num_jobs + 1
env.SetOption("num_jobs", altered_num_jobs)
if env.GetOption("num_jobs") == altered_num_jobs:
cpu_count = os.cpu_count()
if cpu_count is None:
print("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.")
else:
safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1
print(
"Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument."
% (cpu_count, safer_cpu_count)
)
env.SetOption("num_jobs", safer_cpu_count)

# Custom options and profile flags.
customs = ["custom.py"]
profile = ARGUMENTS.get("profile", "")
Expand All @@ -92,159 +29,11 @@ if profile:
elif os.path.isfile(profile + ".py"):
customs.append(profile + ".py")
opts = Variables(customs, ARGUMENTS)

platforms = ("linux", "macos", "windows", "android", "ios", "javascript")
opts.Add(
EnumVariable(
key="platform",
help="Target platform",
default=env.get("platform", default_platform),
allowed_values=platforms,
ignorecase=2,
)
)

# Editor and template_debug are compatible (i.e. you can use the same binary for Godot editor builds and Godot debug templates).
# Godot release templates are only compatible with "template_release" builds.
# For this reason, we default to template_debug builds, unlike Godot which defaults to editor builds.
opts.Add(
EnumVariable(
key="target",
help="Compilation target",
default=env.get("target", "template_debug"),
allowed_values=("editor", "template_release", "template_debug"),
)
)
opts.Add(
PathVariable(
key="gdextension_dir",
help="Path to a custom directory containing GDExtension interface header and API JSON file",
default=env.get("gdextension_dir", None),
validator=validate_dir,
)
)
opts.Add(
PathVariable(
key="custom_api_file",
help="Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`)",
default=env.get("custom_api_file", None),
validator=validate_file,
)
)
opts.Add(
BoolVariable(
key="generate_bindings",
help="Force GDExtension API bindings generation. Auto-detected by default.",
default=env.get("generate_bindings", False),
)
)
opts.Add(
BoolVariable(
key="generate_template_get_node",
help="Generate a template version of the Node class's get_node.",
default=env.get("generate_template_get_node", True),
)
)

opts.Add(BoolVariable(key="build_library", help="Build the godot-cpp library.", default=env.get("build_library", True)))
opts.Add(
EnumVariable(
key="precision",
help="Set the floating-point precision level",
default=env.get("precision", "single"),
allowed_values=("single", "double"),
)
)

# compiledb
opts.Add(
BoolVariable(
key="compiledb",
help="Generate compilation DB (`compile_commands.json`) for external tools",
default=env.get("compiledb", False),
)
)
opts.Add(
PathVariable(
key="compiledb_file",
help="Path to a custom `compile_commands.json` file",
default=env.get("compiledb_file", "compile_commands.json"),
validator=validate_parent_dir,
)
)

# Add platform options
tools = {}
for pl in platforms:
tool = Tool(pl, toolpath=["tools"])
if hasattr(tool, "options"):
tool.options(opts)
tools[pl] = tool

# CPU architecture options.
architecture_array = ["", "universal", "x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32"]
architecture_aliases = {
"x64": "x86_64",
"amd64": "x86_64",
"armv7": "arm32",
"armv8": "arm64",
"arm64v8": "arm64",
"aarch64": "arm64",
"rv": "rv64",
"riscv": "rv64",
"riscv64": "rv64",
"ppcle": "ppc32",
"ppc": "ppc32",
"ppc64le": "ppc64",
}
opts.Add(
EnumVariable(
key="arch",
help="CPU architecture",
default=env.get("arch", ""),
allowed_values=architecture_array,
map=architecture_aliases,
)
)

# Targets flags tool (optimizations, debug symbols)
target_tool = Tool("targets", toolpath=["tools"])
target_tool.options(opts)

cpp_tool = Tool("godotcpp", toolpath=["tools"])
cpp_tool.options(opts, env)
opts.Update(env)
Help(opts.GenerateHelpText(env))

# Process CPU architecture argument.
if env["arch"] == "":
# No architecture specified. Default to arm64 if building for Android,
# universal if building for macOS or iOS, wasm32 if building for web,
# otherwise default to the host architecture.
if env["platform"] in ["macos", "ios"]:
env["arch"] = "universal"
elif env["platform"] == "android":
env["arch"] = "arm64"
elif env["platform"] == "javascript":
env["arch"] = "wasm32"
else:
host_machine = platform.machine().lower()
if host_machine in architecture_array:
env["arch"] = host_machine
elif host_machine in architecture_aliases.keys():
env["arch"] = architecture_aliases[host_machine]
elif "86" in host_machine:
# Catches x86, i386, i486, i586, i686, etc.
env["arch"] = "x86_32"
else:
print("Unsupported CPU architecture: " + host_machine)
Exit()

tool = Tool(env["platform"], toolpath=["tools"])

if tool is None or not tool.exists(env):
raise ValueError("Required toolchain not found for platform " + env["platform"])

tool.generate(env)
target_tool.generate(env)
Help(opts.GenerateHelpText(env))

# Detect and print a warning listing unknown SCons variables to ease troubleshooting.
unknown = opts.UnknownVariables()
Expand All @@ -253,71 +42,12 @@ if unknown:
for item in unknown.items():
print(" " + item[0] + "=" + item[1])

print("Building for architecture " + env["arch"] + " on platform " + env["platform"])

# Require C++17
if env.get("is_msvc", False):
env.Append(CXXFLAGS=["/std:c++17"])
else:
env.Append(CXXFLAGS=["-std=c++17"])

if env["precision"] == "double":
env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"])

# compile_commands.json
if env.get("compiledb", False):
env.Tool("compilation_db")
env.Alias("compiledb", env.CompilationDatabase(normalize_path(env["compiledb_file"])))

# Generate bindings
env.Append(BUILDERS={"GenerateBindings": Builder(action=scons_generate_bindings, emitter=scons_emit_files)})

bindings = env.GenerateBindings(
env.Dir("."),
[get_api_file(env), os.path.join(get_gdextension_dir(env), "gdextension_interface.h"), "binding_generator.py"],
)

scons_cache_path = os.environ.get("SCONS_CACHE")
if scons_cache_path is not None:
CacheDir(scons_cache_path)
Decider("MD5")

# Forces bindings regeneration.
if env["generate_bindings"]:
AlwaysBuild(bindings)
NoCache(bindings)

# Includes
env.Append(CPPPATH=[[env.Dir(d) for d in [get_gdextension_dir(env), "include", os.path.join("gen", "include")]]])

# Sources to compile
sources = []
add_sources(sources, "src", "cpp")
add_sources(sources, "src/classes", "cpp")
add_sources(sources, "src/core", "cpp")
add_sources(sources, "src/variant", "cpp")
sources.extend([f for f in bindings if str(f).endswith(".cpp")])

suffix = ".{}.{}".format(env["platform"], env["target"])
if env.dev_build:
suffix += ".dev"
if env["precision"] == "double":
suffix += ".double"
suffix += "." + env["arch"]
if env["ios_simulator"]:
suffix += ".simulator"

# Expose it when included from another project
env["suffix"] = suffix

library = None
env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"]
library_name = "libgodot-cpp{}{}".format(suffix, env["LIBSUFFIX"])

if env["build_library"]:
library = env.StaticLibrary(target=env.File("bin/%s" % library_name), source=sources)
Default(library)
cpp_tool.generate(env)
library = env.GodotCPP()

env.Append(LIBPATH=[env.Dir("bin")])
env.Append(LIBS=library_name)
Return("env")
Loading