mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-07 04:40:43 +08:00
android: experimental ninja build
This commit is contained in:
committed by
a1batross
parent
bb811310d2
commit
ca6de641a1
1
.gitignore
vendored
1
.gitignore
vendored
@@ -353,3 +353,4 @@ cmake-build-*
|
||||
# some Android-specific build stuff
|
||||
3rdparty/SDL
|
||||
3rdparty/hlsdk-portable
|
||||
!scripts/build-ninja.py
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
|
||||
# Only used to build Android project
|
||||
|
||||
project(xash3d-fwgs-android)
|
||||
|
||||
# armeabi-v7a requires cpufeatures library
|
||||
if (ANDROID)
|
||||
include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
|
||||
add_library(cpufeatures ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
|
||||
target_link_libraries(cpufeatures dl)
|
||||
endif ()
|
||||
|
||||
include(FindPython)
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
set(BUILD_TYPE "debug")
|
||||
else()
|
||||
set(BUILD_TYPE "release")
|
||||
list(APPEND WAF_EXTRA_ARGS --enable-poly-opt --enable-lto --enable-limited-debuginfo)
|
||||
endif()
|
||||
|
||||
if (ANDROID_ABI STREQUAL "x86")
|
||||
# HACKHACK: I don't know why but engine gets built as 64-bit binary here
|
||||
list(APPEND WAF_EXTRA_ARGS -4)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
|
||||
|
||||
# not cleanest way to get upper directory
|
||||
set(ENGINE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../")
|
||||
|
||||
set(WAF_CC "${CMAKE_C_COMPILER} --target=${CMAKE_C_COMPILER_TARGET}")
|
||||
set(WAF_CXX "${CMAKE_CXX_COMPILER} --target=${CMAKE_CXX_COMPILER_TARGET}")
|
||||
set(WAF ${Python_EXECUTABLE} ${ENGINE_SOURCE_DIR}waf -t ${ENGINE_SOURCE_DIR} -o ${CMAKE_CURRENT_BINARY_DIR}/xash3d-fwgs)
|
||||
|
||||
# try to build minimal SDL. Enable features as we're gonna use them
|
||||
foreach (X IN ITEMS SDL_RENDER SDL_POWER SDL_VULKAN SDL_DISKAUDIO SDL_DUMMYAUDIO SDL_DUMMYVIDEO SDL_VULKAN SDL_OFFSCREEN SDL_STATIC)
|
||||
set(${X} OFF CACHE BOOL "" FORCE)
|
||||
endforeach ()
|
||||
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/SDL" SDL)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
# gradle passes backslashes to cmake, how does this even work for everybody else?
|
||||
string(REPLACE "\\" "/" CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
||||
|
||||
ExternalProject_Add(
|
||||
Xash3DFWGS
|
||||
SOURCE_DIR ${ENGINE_SOURCE_DIR}
|
||||
INSTALL_DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
BUILD_IN_SOURCE TRUE
|
||||
DEPENDS SDL2
|
||||
BUILD_ALWAYS TRUE
|
||||
LOG_CONFIGURE TRUE
|
||||
LOG_BUILD TRUE
|
||||
LOG_INSTALL TRUE
|
||||
LOG_OUTPUT_ON_FAILURE TRUE
|
||||
LOG_MERGED_STDOUTERR TRUE
|
||||
|
||||
# USES_TERMINAL_CONFIGURE TRUE
|
||||
# USES_TERMINAL_BUILD TRUE
|
||||
# USES_TERMINAL_INSTALL TRUE
|
||||
|
||||
# NOTE: setting up WAFLOCK is important to avoid possible race conditions
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env
|
||||
ANDROID_NDK=${ANDROID_NDK}
|
||||
BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
||||
${WAF} configure -T ${BUILD_TYPE} --android=${ANDROID_ABI},,${ANDROID_PLATFORM_LEVEL}
|
||||
-s "${ENGINE_SOURCE_DIR}/3rdparty/SDL" --enable-bundled-deps ${WAF_EXTRA_ARGS}
|
||||
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} -E env
|
||||
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
||||
${WAF} build -v
|
||||
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E env
|
||||
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
||||
${WAF} install --destdir=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
)
|
||||
|
||||
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/hlsdk-portable" hlsdk-portable)
|
||||
|
||||
# a1ba: without this, xash3d target will be ignored as nothing depends on it
|
||||
add_dependencies(client Xash3DFWGS)
|
||||
add_dependencies(server Xash3DFWGS)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import java.time.LocalDateTime
|
||||
import java.time.Month
|
||||
import java.time.temporal.ChronoUnit
|
||||
@@ -9,18 +10,35 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "su.xash.engine"
|
||||
ndkVersion = "28.0.13004108"
|
||||
ndkVersion = "28.2.13676358"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "su.xash.engine"
|
||||
versionName = "0.21"
|
||||
versionName = "0.21-" + getGitHash()
|
||||
versionCode = getBuildNum()
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
|
||||
ndk {
|
||||
abiFilters.addAll(setOf("armeabi-v7a", "arm64-v8a"))
|
||||
externalNativeBuild {
|
||||
val engineRoot = projectDir.parentFile.parent
|
||||
|
||||
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a")
|
||||
experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path
|
||||
experimentalProperties["ninja.configure"] = "run-python"
|
||||
experimentalProperties["ninja.arguments"] = setOf(
|
||||
File(engineRoot, "scripts/configure-ninja.py").path,
|
||||
engineRoot,
|
||||
"--variant=\${ndk.variantName}",
|
||||
"--abi=Android-\${ndk.abi}",
|
||||
"--configuration-dir=\${ndk.buildRoot}",
|
||||
"--ndk-version=\${ndk.moduleNdkVersion}",
|
||||
"--min-sdk-version=\${ndk.minPlatform}",
|
||||
"--ndk-root=${android.ndkDirectory}",
|
||||
// shut up, fake options
|
||||
"-p:Configuration=\${ndk.variantName}",
|
||||
"-p:Platform=\${ndk.abi}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +47,9 @@ android {
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("CMakeLists.txt")
|
||||
version = "3.22.1"
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,3 +129,9 @@ fun getBuildNum(): Int {
|
||||
val minuteOfDay = now.hour * 60 + now.minute
|
||||
return (qBuildNum * 10000 + minuteOfDay).toInt()
|
||||
}
|
||||
|
||||
fun getGitHash(): String {
|
||||
val process = ProcessBuilder("git", "rev-parse", "--short", "HEAD").directory(project.rootDir)
|
||||
.redirectErrorStream(true).start()
|
||||
return process.inputStream.bufferedReader().readText().trim()
|
||||
}
|
||||
|
||||
3
android/app/run-python
Executable file
3
android/app/run-python
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
exec python $@
|
||||
1
android/app/run-python.bat
Normal file
1
android/app/run-python.bat
Normal file
@@ -0,0 +1 @@
|
||||
python %*
|
||||
@@ -3,7 +3,8 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[versions]
|
||||
acraHttp = "5.12.0"
|
||||
agp = "8.9.1"
|
||||
appcompat = "1.7.0"
|
||||
kotlin = "2.0.0"
|
||||
agp = "8.11.1"
|
||||
appcompat = "1.7.1"
|
||||
kotlin = "2.2.0"
|
||||
material = "1.12.0"
|
||||
navigationRuntimeKtx = "2.8.9"
|
||||
navigationRuntimeKtx = "2.9.1"
|
||||
preferenceKtx = "1.2.1"
|
||||
swiperefreshlayout = "1.1.0"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Tue Dec 31 17:33:17 EET 2024
|
||||
#Tue Jul 01 19:51:06 EEST 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
79
scripts/build-ninja.py
Normal file
79
scripts/build-ninja.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
# Copyright (C) 2025 Velaron
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def run_cmake(path, libs, out):
|
||||
cmake_exec = ["cmake", "--build", path]
|
||||
|
||||
cmake_process = subprocess.Popen(cmake_exec)
|
||||
cmake_process.communicate()
|
||||
|
||||
for lib in libs:
|
||||
src = os.path.join(path, *lib.split("/"))
|
||||
dest = os.path.join(out, lib.split("/")[-1])
|
||||
|
||||
dest_dir = os.path.dirname(dest)
|
||||
|
||||
if not os.path.exists(dest_dir):
|
||||
os.makedirs(dest_dir)
|
||||
|
||||
shutil.copyfile(src, dest)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("cmd")
|
||||
parser.add_argument("top_dir")
|
||||
parser.add_argument("out_dir")
|
||||
parser.add_argument("waflock")
|
||||
parser.add_argument("--targets", type=str, default="")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
waf_path = os.path.join(args.top_dir, "waf")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["WAFLOCK"] = args.waflock
|
||||
|
||||
waf_exec = [sys.executable, waf_path, args.cmd, "-t", args.top_dir]
|
||||
|
||||
if args.targets:
|
||||
waf_exec += ["--targets={}".format(args.targets)]
|
||||
else:
|
||||
# build SDL2 and hlsdk-portable with cmake
|
||||
sdl_out_path = os.path.join(args.out_dir, "SDL")
|
||||
hlsdk_out_path = os.path.join(args.out_dir, "hlsdk-portable")
|
||||
|
||||
abi = args.waflock.replace(".lock-waf_android_", "").replace("_build", "")
|
||||
dest_dir = os.path.join(args.top_dir, "android", "app", "src", "main", "jniLibs", abi)
|
||||
|
||||
if not os.path.exists(dest_dir):
|
||||
os.makedirs(dest_dir)
|
||||
|
||||
run_cmake(sdl_out_path, ["libSDL2.so"], dest_dir)
|
||||
run_cmake(hlsdk_out_path, ["cl_dll/libclient.so", "dlls/libserver.so"], dest_dir)
|
||||
|
||||
process = subprocess.Popen(waf_exec, env=env)
|
||||
process.communicate()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
108
scripts/configure-ninja.py
Executable file
108
scripts/configure-ninja.py
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
# Copyright (C) 2025 Velaron
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import io
|
||||
|
||||
|
||||
def check_repo(name, branch, url, path):
|
||||
if not os.path.exists(path):
|
||||
print("{} not found. Cloning...".format(name))
|
||||
git_exec = ["git", "clone", "--branch", branch, url, path]
|
||||
git_process = subprocess.Popen(git_exec)
|
||||
git_process.communicate()
|
||||
|
||||
|
||||
def run_cmake(root, out, toolchain, abi, build_type, ndk_root, min_sdk, *args):
|
||||
cmake_exec = ["cmake", "-H{}".format(root), "-DCMAKE_BUILD_TYPE={}".format(build_type),
|
||||
"-DCMAKE_TOOLCHAIN_FILE={}".format(toolchain), "-DANDROID_ABI={}".format(abi),
|
||||
"-DANDROID_NDK={}".format(ndk_root),
|
||||
"-DANDROID_PLATFORM=android-{}".format(min_sdk),
|
||||
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
|
||||
"-DCMAKE_SYSTEM_NAME=Android", "-DCMAKE_SYSTEM_VERSION={}".format(min_sdk),
|
||||
"-B{}".format(out), "-GNinja"]
|
||||
|
||||
cmake_exec.extend(args)
|
||||
cmake_process = subprocess.Popen(cmake_exec)
|
||||
cmake_process.communicate()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("wscript_path")
|
||||
parser.add_argument("--variant")
|
||||
parser.add_argument("--abi")
|
||||
parser.add_argument("--configuration-dir")
|
||||
parser.add_argument("--ndk-version")
|
||||
parser.add_argument("--min-sdk-version")
|
||||
parser.add_argument("--ndk-root")
|
||||
|
||||
args, unknown = parser.parse_known_args()
|
||||
|
||||
abi = args.abi[8:]
|
||||
|
||||
cmake_build_type = "Debug" if args.variant in ["debug", "asan"] else "Release"
|
||||
cmake_toolchain_path = os.path.join(args.ndk_root, "build", "cmake", "android.toolchain.cmake")
|
||||
|
||||
# configure SDL2
|
||||
sdl_path = os.path.join(args.wscript_path, "3rdparty", "SDL")
|
||||
check_repo("SDL", "release-2.32.8", "https://github.com/libsdl-org/SDL", sdl_path)
|
||||
|
||||
sdl_out_path = os.path.join(args.configuration_dir, "SDL")
|
||||
|
||||
run_cmake(sdl_path, sdl_out_path, cmake_toolchain_path, abi, cmake_build_type, args.ndk_root, args.min_sdk_version,
|
||||
"-DSDL_RENDER=OFF", "-DSDL_POWER=OFF", "-DSDL_VULKAN=OFF", "-DSDL_DISKAUDIO=OFF",
|
||||
"-DSDL_DUMMYAUDIO=OFF", "-DSDL_DUMMYVIDEO=OFF",
|
||||
"-DSDL_VULKAN=OFF", "-DSDL_OFFSCREEN=OFF", "-DSDL_STATIC=OFF")
|
||||
|
||||
# configure hlsdk-portable
|
||||
hlsdk_path = os.path.join(args.wscript_path, "3rdparty", "hlsdk-portable")
|
||||
check_repo("hlsdk-portable", "mobile_hacks", "https://github.com/FWGS/hlsdk-portable", hlsdk_path)
|
||||
|
||||
hlsdk_out_path = os.path.join(args.configuration_dir, "hlsdk-portable")
|
||||
|
||||
run_cmake(hlsdk_path, hlsdk_out_path, cmake_toolchain_path, abi, cmake_build_type, args.ndk_root,
|
||||
args.min_sdk_version)
|
||||
|
||||
# waf configure
|
||||
waf_path = os.path.join(args.wscript_path, "waf")
|
||||
out_path = os.path.join(args.configuration_dir, "xash3d-fwgs")
|
||||
|
||||
waf_build_type = "debug" if args.variant in ["debug", "asan"] else "release"
|
||||
|
||||
env = os.environ.copy()
|
||||
env["WAFLOCK"] = ".lock-waf_android_{}_build".format(abi)
|
||||
env["ANDROID_NDK"] = args.ndk_root
|
||||
env["BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY"] = sdl_out_path
|
||||
|
||||
waf_exec = [sys.executable, waf_path, "configure", "-t", args.wscript_path, "-o", out_path,
|
||||
"-T", waf_build_type, "--android={},,{}".format(abi, args.min_sdk_version), "-s",
|
||||
sdl_path, "--skip-sdl2-sanity-check", "--enable-bundled-deps", "ninja"]
|
||||
|
||||
process = subprocess.Popen(waf_exec, env=env)
|
||||
process.communicate()
|
||||
|
||||
with io.open(os.path.join(args.configuration_dir, "build.ninja.txt"), "w", encoding="utf-8") as f:
|
||||
f.write(os.path.join(out_path, "build.ninja"))
|
||||
|
||||
# required for Android Studio
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
340
scripts/waifulib/ninja.py
Normal file
340
scripts/waifulib/ninja.py
Normal file
@@ -0,0 +1,340 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
# Copyright (C) 2025 Velaron
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
import io
|
||||
import os
|
||||
import abc
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
from abc import abstractmethod
|
||||
|
||||
try:
|
||||
from typing import List, Type, Union
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from waflib import Logs, Task, Build, Options, Node
|
||||
from ninja_syntax import Writer, escape_path
|
||||
|
||||
try:
|
||||
ABC = abc.ABC
|
||||
except AttributeError:
|
||||
class ABC(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
Task.Task.keep_last_cmd = True
|
||||
|
||||
|
||||
def get_node_path(node): # type: (Union[Node, str]) -> str
|
||||
if isinstance(node, str):
|
||||
return escape_path(node)
|
||||
else:
|
||||
return escape_path(node.abspath())
|
||||
|
||||
|
||||
class TaskAdapter(ABC):
|
||||
task_type = None # type: str
|
||||
|
||||
def __init__(self, task): # type: (Task) -> None
|
||||
if not task.inputs:
|
||||
raise ValueError()
|
||||
|
||||
if not task.outputs:
|
||||
raise ValueError()
|
||||
|
||||
self.task = task # type: Task
|
||||
|
||||
@classmethod
|
||||
def write_rule(cls, writer): # type: (Writer) -> None
|
||||
writer.rule(
|
||||
name=cls.task_type,
|
||||
command="$cmd",
|
||||
description="Building {} object $out".format(cls.task_type)
|
||||
)
|
||||
writer.newline()
|
||||
|
||||
@abstractmethod
|
||||
def write_build(self, writer): # type: (Writer) -> None
|
||||
pass
|
||||
|
||||
def write_target(self, writer): # type: (Writer) -> None
|
||||
pass
|
||||
|
||||
def get_inputs(self): # type: () -> List[str]
|
||||
return []
|
||||
|
||||
def get_outputs(self): # type: () -> List[str]
|
||||
return []
|
||||
|
||||
|
||||
class CAdapter(TaskAdapter):
|
||||
task_type = "c" # type: str
|
||||
|
||||
def write_build(self, writer): # type: (Writer) -> None
|
||||
output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)
|
||||
input_files = []
|
||||
|
||||
cmd = " ".join(self.task.last_cmd)
|
||||
|
||||
for node in self.task.inputs:
|
||||
cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())
|
||||
input_files.append(node.abspath())
|
||||
|
||||
cmd = cmd.replace(self.task.outputs[0].abspath(), output_file)
|
||||
|
||||
for inc in self.task.env.INCPATHS:
|
||||
cwd = self.task.get_cwd().abspath()
|
||||
path = os.path.normpath(os.path.join(cwd, inc))
|
||||
cmd = cmd.replace(self.task.env.CPPPATH_ST % inc,
|
||||
self.task.env.CPPPATH_ST % path)
|
||||
|
||||
writer.build(
|
||||
rule=self.task_type,
|
||||
outputs=output_file,
|
||||
inputs=input_files,
|
||||
variables={
|
||||
"cmd": cmd,
|
||||
})
|
||||
writer.newline()
|
||||
|
||||
|
||||
class CxxAdapter(CAdapter):
|
||||
task_type = "cxx" # type: str
|
||||
|
||||
|
||||
class CStLibAdapter(TaskAdapter):
|
||||
task_type = "cstlib" # type: str
|
||||
|
||||
def write_build(self, writer): # type: (Writer) -> None
|
||||
output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)
|
||||
input_files = []
|
||||
|
||||
cmd = " ".join(self.task.last_cmd)
|
||||
|
||||
for node in self.task.inputs:
|
||||
cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())
|
||||
input_files.append(node.abspath())
|
||||
|
||||
cmd = cmd.replace(self.task.outputs[0].abspath(), output_file)
|
||||
|
||||
writer.build(
|
||||
rule=self.task_type,
|
||||
outputs=output_file,
|
||||
inputs=input_files,
|
||||
variables={
|
||||
"cmd": cmd,
|
||||
})
|
||||
writer.newline()
|
||||
|
||||
def write_target(self, writer): # type: (Writer) -> None
|
||||
writer.build(
|
||||
rule="waf_build",
|
||||
outputs="{}.passthrough".format(self.get_outputs()[0]),
|
||||
inputs=self.get_inputs(),
|
||||
variables={
|
||||
"tgt": self.task.generator.name
|
||||
})
|
||||
writer.newline()
|
||||
|
||||
def get_inputs(self): # type: () -> List[str]
|
||||
return [node.abspath() for node in self.task.generator.source]
|
||||
|
||||
def get_outputs(self): # type: () -> List[str]
|
||||
return [self.task.outputs[0].path_from(self.task.generator.bld.bldnode)]
|
||||
|
||||
|
||||
class CShLibAdapter(CStLibAdapter):
|
||||
task_type = "cshlib" # type: str
|
||||
|
||||
def write_build(self, writer): # type: (Writer) -> None
|
||||
output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)
|
||||
input_files = []
|
||||
|
||||
cmd = " ".join(self.task.last_cmd)
|
||||
|
||||
for node in self.task.inputs:
|
||||
cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())
|
||||
input_files.append(node.abspath())
|
||||
|
||||
cmd = cmd.replace(self.task.outputs[0].abspath(), output_file)
|
||||
|
||||
for lib in self.task.env.STLIBPATH:
|
||||
cwd = self.task.get_cwd().abspath()
|
||||
path = os.path.normpath(os.path.join(cwd, lib))
|
||||
cmd = cmd.replace(self.task.env.STLIBPATH_ST % lib,
|
||||
self.task.env.STLIBPATH_ST % path)
|
||||
|
||||
for lib in self.task.env.LIBPATH:
|
||||
cwd = self.task.get_cwd().abspath()
|
||||
path = os.path.normpath(os.path.join(cwd, lib))
|
||||
cmd = cmd.replace(self.task.env.LIBPATH_ST % lib,
|
||||
self.task.env.LIBPATH_ST % path)
|
||||
|
||||
writer.build(
|
||||
rule=self.task_type,
|
||||
outputs=output_file,
|
||||
inputs=input_files,
|
||||
variables={
|
||||
"cmd": cmd,
|
||||
})
|
||||
writer.newline()
|
||||
|
||||
|
||||
class CxxStLibAdapter(CStLibAdapter):
|
||||
task_type = "cxxstlib" # type: str
|
||||
|
||||
|
||||
class CxxShLibAdapter(CShLibAdapter):
|
||||
task_type = "cxxshlib" # type: str
|
||||
|
||||
|
||||
def get_subclasses(cls):
|
||||
subs = set()
|
||||
for sub in cls.__subclasses__():
|
||||
subs.add(sub)
|
||||
subs.update(get_subclasses(sub))
|
||||
return list(subs)
|
||||
|
||||
|
||||
Adapters = get_subclasses(TaskAdapter) # type: List[Type[TaskAdapter]]
|
||||
|
||||
|
||||
class NinjaContext(Build.BuildContext):
|
||||
cmd = "ninja"
|
||||
|
||||
def execute(self):
|
||||
self.restore()
|
||||
|
||||
tasks = [] # type: List[TaskAdapter]
|
||||
|
||||
if not self.all_envs:
|
||||
self.load_envs()
|
||||
|
||||
self.recurse([self.run_dir])
|
||||
self.pre_build()
|
||||
|
||||
def exec_command(self, *k, **kw):
|
||||
return 0
|
||||
|
||||
for group in self.groups:
|
||||
for task_gen in group:
|
||||
try:
|
||||
if hasattr(task_gen, "post"):
|
||||
task_gen.post()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(task_gen, Task.Task):
|
||||
current_tasks = [task_gen]
|
||||
else:
|
||||
current_tasks = task_gen.tasks
|
||||
|
||||
for task in current_tasks:
|
||||
try:
|
||||
adapter = next(a for a in Adapters if a.task_type == task.__class__.__name__)
|
||||
except StopIteration:
|
||||
continue
|
||||
|
||||
if adapter:
|
||||
try:
|
||||
tasks.append(adapter(task))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
task.nocache = True
|
||||
|
||||
old_exec = task.exec_command
|
||||
task.exec_command = exec_command
|
||||
try:
|
||||
task.run()
|
||||
except Exception as e:
|
||||
Logs.error("Error running task {}: {}".format(task, e))
|
||||
finally:
|
||||
task.exec_command = old_exec
|
||||
|
||||
ninja_file_node = self.bldnode.make_node("build.ninja")
|
||||
|
||||
Logs.info("Ninja build commands will be stored in %s", ninja_file_node.abspath())
|
||||
|
||||
string_buffer = StringIO()
|
||||
writer = Writer(string_buffer)
|
||||
|
||||
writer.variable(key="ninja_required_version", value="1.5")
|
||||
writer.newline()
|
||||
|
||||
for a in Adapters:
|
||||
a.write_rule(writer)
|
||||
|
||||
writer.rule(
|
||||
"waf_build",
|
||||
command="python {} build {} {} {} --targets=$tgt".format(
|
||||
os.path.join(self.top_dir, "scripts", "build-ninja.py"),
|
||||
self.top_dir, os.path.dirname(self.out_dir), Options.lockfile)
|
||||
)
|
||||
writer.newline()
|
||||
|
||||
writer.rule(
|
||||
"waf_build_all",
|
||||
command="python {} build {} {} {}".format(os.path.join(self.top_dir, "scripts", "build-ninja.py"),
|
||||
self.top_dir, os.path.dirname(self.out_dir), Options.lockfile)
|
||||
)
|
||||
writer.newline()
|
||||
|
||||
writer.rule(
|
||||
"waf_clean",
|
||||
command="python {} clean {} {} {}".format(os.path.join(self.top_dir, "scripts", "build-ninja.py"),
|
||||
self.top_dir, os.path.dirname(self.out_dir), Options.lockfile)
|
||||
)
|
||||
writer.newline()
|
||||
|
||||
for task in tasks:
|
||||
task.write_build(writer)
|
||||
|
||||
for task in tasks:
|
||||
task.write_target(writer)
|
||||
|
||||
outputs = [] # type: List[str]
|
||||
for task in tasks:
|
||||
outputs += task.get_outputs()
|
||||
|
||||
writer.build(
|
||||
outputs="all",
|
||||
rule="phony",
|
||||
inputs=outputs
|
||||
)
|
||||
writer.newline()
|
||||
|
||||
inputs = [] # type: List[str]
|
||||
for task in tasks:
|
||||
inputs += task.get_inputs()
|
||||
|
||||
writer.build(
|
||||
outputs="all.passthrough",
|
||||
rule="waf_build_all",
|
||||
inputs=inputs
|
||||
)
|
||||
writer.newline()
|
||||
|
||||
writer.build(outputs="clean", rule="waf_clean")
|
||||
writer.newline()
|
||||
|
||||
writer.default("all")
|
||||
|
||||
file_content = string_buffer.getvalue()
|
||||
|
||||
with io.open(ninja_file_node.abspath(), "w", encoding="utf-8") as f:
|
||||
f.write(file_content)
|
||||
235
scripts/waifulib/ninja_syntax.py
Normal file
235
scripts/waifulib/ninja_syntax.py
Normal file
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright 2011 Google Inc. All Rights Reserved.
|
||||
# Copyright 2025 Velaron (edited for Python 2.7 compatibility)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Python module for generating .ninja files.
|
||||
|
||||
Note that this is emphatically not a required piece of Ninja; it's
|
||||
just a helpful utility for build-file-generation systems that already
|
||||
use Python.
|
||||
"""
|
||||
|
||||
import re
|
||||
import textwrap
|
||||
from io import TextIOWrapper
|
||||
|
||||
try:
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def escape_path(word): # type: (str) -> str
|
||||
return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
|
||||
|
||||
|
||||
class Writer(object):
|
||||
def __init__(self, output, width=78): # type: (TextIOWrapper, int) -> None
|
||||
self.output = output
|
||||
self.width = width
|
||||
|
||||
def newline(self):
|
||||
self.output.write('\n')
|
||||
|
||||
def comment(self, text): # type: (str) -> None
|
||||
for line in textwrap.wrap(text, self.width - 2, break_long_words=False,
|
||||
break_on_hyphens=False):
|
||||
self.output.write('# ' + line + '\n')
|
||||
|
||||
def variable(
|
||||
self,
|
||||
key,
|
||||
value,
|
||||
indent=0,
|
||||
): # type: (str, Optional[Union[bool, int, float, str, List[str]]], int) -> None
|
||||
if value is None:
|
||||
return
|
||||
if isinstance(value, list):
|
||||
value = ' '.join(filter(None, value)) # Filter out empty strings.
|
||||
self._line('%s = %s' % (key, value), indent)
|
||||
|
||||
def pool(self, name, depth): # type: (str, int) -> None
|
||||
self._line('pool %s' % name)
|
||||
self.variable('depth', depth, indent=1)
|
||||
|
||||
def rule(
|
||||
self,
|
||||
name,
|
||||
command,
|
||||
description=None,
|
||||
depfile=None,
|
||||
generator=False,
|
||||
pool=None,
|
||||
restat=False,
|
||||
rspfile=None,
|
||||
rspfile_content=None,
|
||||
deps=None,
|
||||
): # type: (str, str, Optional[str], Optional[str], bool, Optional[str], bool, Optional[str], Optional[str], Optional[Union[str, List[str]]]) -> None
|
||||
self._line('rule %s' % name)
|
||||
self.variable('command', command, indent=1)
|
||||
if description:
|
||||
self.variable('description', description, indent=1)
|
||||
if depfile:
|
||||
self.variable('depfile', depfile, indent=1)
|
||||
if generator:
|
||||
self.variable('generator', '1', indent=1)
|
||||
if pool:
|
||||
self.variable('pool', pool, indent=1)
|
||||
if restat:
|
||||
self.variable('restat', '1', indent=1)
|
||||
if rspfile:
|
||||
self.variable('rspfile', rspfile, indent=1)
|
||||
if rspfile_content:
|
||||
self.variable('rspfile_content', rspfile_content, indent=1)
|
||||
if deps:
|
||||
self.variable('deps', deps, indent=1)
|
||||
|
||||
def build(
|
||||
self,
|
||||
outputs,
|
||||
rule,
|
||||
inputs=None,
|
||||
implicit=None,
|
||||
order_only=None,
|
||||
variables=None,
|
||||
implicit_outputs=None,
|
||||
pool=None,
|
||||
dyndep=None,
|
||||
): # type: (Union[str, List[str]], str, Optional[Union[str, List[str]]], Optional[Union[str, List[str]]], Optional[Union[str, List[str]]], Optional[Union[List[Tuple[str, Optional[Union[str, List[str]]]]],Dict[str, Optional[Union[str, List[str]]]],]], Optional[Union[str, List[str]]], Optional[str], Optional[str]) -> List[str]
|
||||
outputs = as_list(outputs)
|
||||
out_outputs = [escape_path(x) for x in outputs]
|
||||
all_inputs = [escape_path(x) for x in as_list(inputs)]
|
||||
|
||||
if implicit:
|
||||
implicit = [escape_path(x) for x in as_list(implicit)]
|
||||
all_inputs.append('|')
|
||||
all_inputs.extend(implicit)
|
||||
if order_only:
|
||||
order_only = [escape_path(x) for x in as_list(order_only)]
|
||||
all_inputs.append('||')
|
||||
all_inputs.extend(order_only)
|
||||
if implicit_outputs:
|
||||
implicit_outputs = [escape_path(x)
|
||||
for x in as_list(implicit_outputs)]
|
||||
out_outputs.append('|')
|
||||
out_outputs.extend(implicit_outputs)
|
||||
|
||||
self._line('build %s: %s' % (' '.join(out_outputs),
|
||||
' '.join([rule] + all_inputs)))
|
||||
if pool is not None:
|
||||
self._line(' pool = %s' % pool)
|
||||
if dyndep is not None:
|
||||
self._line(' dyndep = %s' % dyndep)
|
||||
|
||||
if variables:
|
||||
if isinstance(variables, dict):
|
||||
iterator = iter(variables.items())
|
||||
else:
|
||||
iterator = iter(variables)
|
||||
|
||||
for key, val in iterator:
|
||||
self.variable(key, val, indent=1)
|
||||
|
||||
return outputs
|
||||
|
||||
def include(self, path): # type: (str) -> None
|
||||
self._line('include %s' % path)
|
||||
|
||||
def subninja(self, path): # type: (str) -> None
|
||||
self._line('subninja %s' % path)
|
||||
|
||||
def default(self, paths): # type: (Union[str, List[str]]) -> None
|
||||
self._line('default %s' % ' '.join(as_list(paths)))
|
||||
|
||||
def _count_dollars_before_index(self, s, i): # type: (str, int) -> int
|
||||
"""Returns the number of '$' characters right in front of s[i]."""
|
||||
dollar_count = 0
|
||||
dollar_index = i - 1
|
||||
while dollar_index > 0 and s[dollar_index] == '$':
|
||||
dollar_count += 1
|
||||
dollar_index -= 1
|
||||
return dollar_count
|
||||
|
||||
def _line(self, text, indent=0): # type: (str, int) -> None
|
||||
"""Write 'text' word-wrapped at self.width characters."""
|
||||
leading_space = ' ' * indent
|
||||
while len(leading_space) + len(text) > self.width:
|
||||
# The text is too wide; wrap if possible.
|
||||
|
||||
# Find the rightmost space that would obey our width constraint and
|
||||
# that's not an escaped space.
|
||||
available_space = self.width - len(leading_space) - len(' $')
|
||||
space = available_space
|
||||
while True:
|
||||
space = text.rfind(' ', 0, space)
|
||||
if (space < 0 or
|
||||
self._count_dollars_before_index(text, space) % 2 == 0):
|
||||
break
|
||||
|
||||
if space < 0:
|
||||
# No such space; just use the first unescaped space we can find.
|
||||
space = available_space - 1
|
||||
while True:
|
||||
space = text.find(' ', space + 1)
|
||||
if (space < 0 or
|
||||
self._count_dollars_before_index(text, space) % 2 == 0):
|
||||
break
|
||||
if space < 0:
|
||||
# Give up on breaking.
|
||||
break
|
||||
|
||||
self.output.write(leading_space + text[0:space] + ' $\n')
|
||||
text = text[space + 1:]
|
||||
|
||||
# Subsequent lines are continuations, so indent them.
|
||||
leading_space = ' ' * (indent + 2)
|
||||
|
||||
self.output.write(leading_space + text + '\n')
|
||||
|
||||
def close(self):
|
||||
self.output.close()
|
||||
|
||||
|
||||
def as_list(input): # type: (Optional[Union[str, List[str]]]) -> List[str]
|
||||
if input is None:
|
||||
return []
|
||||
if isinstance(input, list):
|
||||
return input
|
||||
return [input]
|
||||
|
||||
|
||||
def escape(string): # type: (str) -> str
|
||||
"""Escape a string such that it can be embedded into a Ninja file without
|
||||
further interpretation."""
|
||||
assert '\n' not in string, 'Ninja syntax does not allow newlines'
|
||||
# We only have one special metacharacter: '$'.
|
||||
return string.replace('$', '$$')
|
||||
|
||||
|
||||
def expand(string, vars, local_vars={}): # type: (str, Dict[str, str], Dict[str, str]) -> str
|
||||
"""Expand a string containing $vars as Ninja would.
|
||||
|
||||
Note: doesn't handle the full Ninja variable syntax, but it's enough
|
||||
to make configure.py's use of it work.
|
||||
"""
|
||||
|
||||
def exp(m): # type (Match[str]) -> str:
|
||||
var = m.group(1)
|
||||
if var == '$':
|
||||
return '$'
|
||||
return local_vars.get(var, vars.get(var, ''))
|
||||
|
||||
return re.sub(r'\$(\$|\w*)', exp, string)
|
||||
@@ -25,6 +25,7 @@ ANDROID_NDK_HARDFP_MAX = 11 # latest version that supports hardfp
|
||||
ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC
|
||||
ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15
|
||||
ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag
|
||||
ANDROID_NDK_BUGGED_LINKER_MAX = 22
|
||||
ANDROID_NDK_API_MIN = {
|
||||
10: 3,
|
||||
19: 16,
|
||||
@@ -354,6 +355,12 @@ class Android:
|
||||
else: linkflags += ['-no-canonical-prefixes']
|
||||
|
||||
linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined']
|
||||
|
||||
linkflags += ["-Wl,-z,max-page-size=16384"]
|
||||
|
||||
if self.ndk_rev <= ANDROID_NDK_BUGGED_LINKER_MAX:
|
||||
linkflags += ["-Wl,-z,common-page-size=16384"]
|
||||
|
||||
return linkflags
|
||||
|
||||
def ldflags(self):
|
||||
|
||||
4
wscript
4
wscript
@@ -128,7 +128,7 @@ REFDLLS = [
|
||||
]
|
||||
|
||||
def options(opt):
|
||||
opt.load('reconfigure compiler_optimizations xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msvs subproject')
|
||||
opt.load('reconfigure compiler_optimizations xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msvs subproject ninja')
|
||||
|
||||
grp = opt.add_option_group('Common options')
|
||||
|
||||
@@ -226,7 +226,7 @@ def configure(conf):
|
||||
if conf.env.COMPILER_CC == 'msvc':
|
||||
conf.load('msvc_pdb')
|
||||
|
||||
conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit')
|
||||
conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit ninja')
|
||||
|
||||
conf.env.MSVC_SUBSYSTEM = 'WINDOWS'
|
||||
conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE'
|
||||
|
||||
Reference in New Issue
Block a user