载入中...
搜索中...
未找到
Build.cpp
浏览该文件的文档.
1#include "cmdline/cmdline.h"
2#include "cmdline/sdk_tools.h"
3
4#include <CLI11.hpp>
5#include <rang.hpp>
6
7#include <filesystem>
8#include <fstream>
9#include <iostream>
10
11using std::string;
12using std::cerr;
13using std::cout;
14using std::endl;
15
16namespace eve::cmd {
17
18namespace {
19
20// Reads the positional arguments of the build subcommand: the first positional
21// is treated as the platform name when it matches a known platform and no
22// -p/--platform was given; otherwise it is the game path.
23bool splitBuildArgs(const std::vector<std::string>& remaining, std::string& platform,
24 std::string& game) {
25 game = ".";
26 if (remaining.empty()) return true;
27 if (platform.empty() && sdk::parsePlatform(remaining[0]) != sdk::Platform::Unknown) {
28 platform = remaining[0];
29 if (remaining.size() > 1) game = remaining[1];
30 return remaining.size() <= 2;
31 }
32 game = remaining[0];
33 return remaining.size() == 1;
34}
35
36std::string gradleLauncher(const std::string& gradleHome) {
37#if defined(_WIN32)
38 return (std::filesystem::path(gradleHome) / "bin" / "gradle.bat").string();
39#else
40 return (std::filesystem::path(gradleHome) / "bin" / "gradle").string();
41#endif
42}
43
44// 组装命令:切到 <apk>/ 目录后调用 gradle 的 assembleRelease/assembleDebug。
45std::string gradleCommand(const std::string& apkDir, const std::string& gradleHome,
46 bool debug) {
47 const std::string launcher = gradleLauncher(gradleHome);
48#if defined(_WIN32)
49 // `call` 前缀避免 cmd /c 剥掉以引号开头的命令的首尾引号。
50 return "cd /d \"" + apkDir + "\" && call \"" + launcher + "\" assemble" +
51 std::string(debug ? "Debug" : "Release");
52#else
53 return "cd '" + apkDir + "' && '" + launcher + "' assemble" +
54 std::string(debug ? "Debug" : "Release");
55#endif
56}
57
58} // namespace
59
62 bool release = false, debug = false;
63
64 void setup(CLI::App& app, std::shared_ptr<CLI::Formatter> formatter) override {
65 auto subcmd = app.add_subcommand(
66 "build", "Build the game for a platform (android) using the packaged SDK");
67 subcmd->allow_extras()->formatter(formatter);
68 subcmd->add_option("-p,--platform", platform, "Build platform (android)");
69 subcmd->add_flag("-r,--release", release, "release build (default)");
70 subcmd->add_flag("-d,--debug", debug, "debug build");
71 subcmd->add_option("-l,--log", log_path, "log messages into a file");
72 subcmd->add_option("-o,--output", output_path, "output folder for the assembled APK project");
73 subcmd->add_option("--sdk", sdk_path, "EVEngine SDK root (default: $EVENGINE_SDK or next to the eve binary)");
74 }
75
76 int parse(CLI::App& app, Cmdline& cmd) override {
77 auto subcmd = app.get_subcommand("build");
78 if (subcmd->parsed()) {
79 if (debug && release) {
80 cerr << rang::fg::red << "Build type can not be both debug and release"
81 << rang::fg::reset << endl;
82 return 1;
83 }
84
85 std::string game;
86 if (!splitBuildArgs(subcmd->remaining(), platform, game)) {
87 cerr << rang::fg::red << "Unknown remaining arguments" << rang::fg::reset << endl;
88 return 1;
89 }
90 return cmd.Build(game, output_path, platform, sdk_path, debug);
91 }
92 return -1; // not handle
93 }
94};
95
97
98int Cmdline::Build(std::string path, std::string output, std::string platform,
99 std::string sdkRoot, bool debug) {
100 using namespace sdk;
101 namespace fs = std::filesystem;
102
103 const Platform p = parsePlatform(platform);
104 if (platform.empty()) {
105 cerr << rang::fg::red << "eve build: missing platform. Pass a platform name, "
106 << "e.g. `eve build android`." << rang::fg::reset << endl;
107 return 2;
108 }
109 if (p == Platform::Unknown) {
110 cerr << rang::fg::red << "eve build: unknown platform '" << platform
111 << "' (supported: android)" << rang::fg::reset << endl;
112 return 2;
113 }
114 if (p != Platform::Android) {
115 cerr << rang::fg::red << "eve build: SDK mode currently supports 'android' only; "
116 << "desktop targets are packaged with `eve package`."
117 << rang::fg::reset << endl;
118 return 2;
119 }
120
121 // 1. Android SDK 工具链目录(`eve get android` 安装)。先读它写的
122 // eve-android.env(ANDROID_HOME / JAVA_HOME / GRADLE_HOME / EVENGINE_SDK),
123 // 再据此定位 EVEngine SDK。
124 const std::string asdk = androidSdkRoot();
125 std::error_code ec;
126 if (!fs::is_directory(asdk, ec)) {
127 cerr << rang::fg::red << "Android SDK not found at " << asdk << ". Run `eve get android` "
128 << "first, or set ANDROID_HOME / EVENGINE_ANDROID_SDK."
129 << rang::fg::reset << endl;
130 return 2;
131 }
132 applyEnvFile((fs::path(asdk) / "eve-android.env").string());
133
134 // 2. 定位 EVEngine SDK(无需源码/Makefile/cmake):--sdk > $EVENGINE_SDK
135 // (含 env 文件注入)> eve 可执行文件位置 > `eve get` 安装目录。
136 std::string root = findSdkRoot(sdkRoot);
137 if (root.empty()) {
138 cerr << "eve build: cannot locate the EVEngine SDK. Pass --sdk <dir>, set "
139 "$EVENGINE_SDK, or run `eve get android` first."
140 << endl;
141 return 2;
142 }
143 const std::string sdkPlat = sdkTargetPlatform(root);
144 if (sdkPlat != "android") {
145 cerr << rang::fg::red << "eve build: this SDK targets '" << sdkPlat
146 << "', not android. Get the android SDK." << rang::fg::reset << endl;
147 return 2;
148 }
149
150 // 3. JDK / Gradle 由 `eve get android` 安装(env 文件已注入)。
151 const std::string javaHome = getEnv("JAVA_HOME");
152 const std::string gradleHome = getEnv("GRADLE_HOME");
153 if (javaHome.empty() || gradleHome.empty() ||
154 !fs::is_regular_file(gradleLauncher(gradleHome), ec)) {
155 cerr << rang::fg::red << "Missing JDK/Gradle for Android builds. Run `eve get android` "
156 << "to install them (JAVA_HOME / GRADLE_HOME)." << rang::fg::reset << endl;
157 return 2;
158 }
159 setEnv("ANDROID_HOME", asdk);
160 setEnv("ANDROID_SDK_ROOT", asdk);
161
162 // 4. 游戏目录:默认当前目录;位于 SDK 根时用自带的 demo 壳。
163 std::string game = path;
164 if (game.empty() || game == ".") game = fs::absolute(".").string();
165 else game = fs::absolute(game, ec).string();
166 if (ec || !fs::is_directory(game, ec)) {
167 cerr << rang::fg::red << "eve build: bad game path '" << path << "'"
168 << rang::fg::reset << endl;
169 return 2;
170 }
171 if (fs::equivalent(game, root, ec) || fs::path(game) == fs::path(root)) {
172 const auto demo = fs::path(root) / "platform" / "game-shell";
173 if (fs::is_directory(demo, ec)) game = demo.string();
174 }
175
176 // 5. 组装 APK 工程到输出目录(模板 + 游戏资源 + 预编译 .so,不动 SDK)。
177 const std::string outDir = output.empty()
178 ? (fs::current_path() / "build" / "eve-android").string()
179 : fs::absolute(output, ec).string();
180 fs::remove_all(outDir, ec);
181 fs::create_directories(outDir, ec);
182 const std::string apkDir = (fs::path(outDir) / "apk").string();
183 // 跳过模板自带的构建产物:官方 SDK 的 platform/apk 在发布流水线里被
184 // test-sdk.sh 编译过一次,app/build 下会残留一个 CI 测试 APK(不是用户的
185 // 游戏)。copyTree 只按相对路径跳过顶层目录,所以 app/build 要显式列出。
186 if (!copyTree((fs::path(root) / "platform" / "apk").string(), apkDir,
187 {"build", ".gradle", "local.properties", "app/build"})) {
188 cerr << rang::fg::red << "SDK is missing the android APK template (platform/apk)."
189 << rang::fg::reset << endl;
190 return 3;
191 }
192 const std::string assetsGame = (fs::path(apkDir) / "app" / "src" / "main" / "assets" / "game").string();
193 if (!copyTreeContents(game, assetsGame)) {
194 cerr << rang::fg::red << "Failed to copy game assets." << rang::fg::reset << endl;
195 return 3;
196 }
197 const std::string jniDir = (fs::path(apkDir) / "app" / "src" / "main" / "jniLibs" / "arm64-v8a").string();
198 fs::create_directories(jniDir, ec);
199 int libs = 0;
200 for (const auto& entry : fs::directory_iterator(fs::path(root) / "lib", ec)) {
201 if (entry.is_regular_file() && entry.path().extension() == ".so") {
202 fs::copy_file(entry.path(), fs::path(jniDir) / entry.path().filename(),
203 fs::copy_options::overwrite_existing, ec);
204 ++libs;
205 }
206 }
207 if (libs == 0) {
208 cerr << rang::fg::yellow
209 << "note: no prebuilt .so found in the SDK lib/ dir; the APK will be "
210 "missing native code."
211 << rang::fg::reset << endl;
212 }
213 {
214 // 用正斜杠避免 Java properties 的转义问题。
215 std::string sdkProp = asdk;
216 for (char& c : sdkProp)
217 if (c == '\\') c = '/';
218 std::ofstream f(fs::path(apkDir) / "local.properties", std::ios::binary | std::ios::trunc);
219 f << "sdk.dir=" << sdkProp << "\n";
220 }
221
222 // 6. Gradle 组装 APK。
223 const std::string cmd = gradleCommand(apkDir, gradleHome, debug);
224 cout << "eve build: " << cmd << endl;
225 const int rc = runShell(cmd);
226 if (rc != 0) {
227 cerr << rang::fg::red << "eve build: gradle failed (exit " << rc << ")."
228 << rang::fg::reset << endl;
229 return 4;
230 }
231
232 // 7. 输出 APK 路径(release 未配置签名时 AGP 产出 app-release-unsigned.apk)。
233 const std::string variant = debug ? "debug" : "release";
234 fs::path apk;
235 {
236 std::vector<std::string> names = {"app-" + variant + ".apk"};
237 if (!debug) names.push_back("app-release-unsigned.apk");
238 for (const auto& n : names) {
239 const fs::path candidate = fs::path(apkDir) / "app" / "build" / "outputs" / "apk" /
240 variant / n;
241 if (fs::is_regular_file(candidate, ec)) {
242 apk = candidate;
243 break;
244 }
245 }
246 }
247 if (apk.empty()) {
248 cerr << rang::fg::yellow
249 << "note: gradle finished but no APK found under "
250 << (fs::path(apkDir) / "app" / "build" / "outputs" / "apk").string()
251 << rang::fg::reset << endl;
252 return 0;
253 }
254 cout << rang::fg::green << "Built APK -> " << rang::fg::reset << apk.string();
255 if (apk.filename() == "app-release-unsigned.apk")
256 cout << " (unsigned; add a signing config to release properly)";
257 cout << endl;
258 return 0;
259}
260
261} // namespace eve::cmd
int root
Definition AnimSmr.cpp:119
std::string output
std::string variant
glm::vec4 p[6]
glm::vec3 n
Definition Grass.cpp:63
std::int32_t c
float f
std::string path
Definition PlayHost.cpp:110
命令行模块(eve.cmd):run / build / package / test / zip / dev-server 等子命令入口。
Definition cmdline.h:33
int Build(std::string path, std::string output, std::string platform, std::string sdkRoot="", bool debug=false)
构建项目:委托仓库 Makefile 的 build/<platform>[-debug] 目标。
Definition Build.cpp:98
#define CMD_REG(name)
Definition cmdline.h:101
Platform parsePlatform(const std::string &name)
解析平台名(大小写不敏感,含常用别名:windows/win、mac/macos/osx 等)。
void setup(CLI::App &app, std::shared_ptr< CLI::Formatter > formatter) override
Registers options on the CLI11 sub-app.
Definition Build.cpp:64
int parse(CLI::App &app, Cmdline &cmd) override
Parses arguments and runs the command; returns the exit code.
Definition Build.cpp:76
One eve <subcommand> handler: CLI setup + argument parsing.
Definition cmdline.h:23