summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYi Jianlong <[email protected]>2025-12-12 15:00:44 +0800
committerYi Jianlong <[email protected]>2025-12-13 00:45:05 +0800
commit7298699afb4e2baf50978cda9b2cc8822170301b (patch)
tree0d467fb4fa2b17d2b4c956ec2b4144c544b0d0e1
parent711feadb87c1224de4fa81745d7a13565aeda10d (diff)
refactor the android native app rule and add a simple test project
-rw-r--r--tests/projects/c++/ndk_raylib/android/AndroidManifest.xml31
-rw-r--r--tests/projects/c++/ndk_raylib/android/debug.jksbin0 -> 2487 bytes
-rw-r--r--tests/projects/c++/ndk_raylib/android/res/mipmap-mdpi/icon.pngbin0 -> 2643 bytes
-rw-r--r--tests/projects/c++/ndk_raylib/android/res/mipmap/icon.pngbin0 -> 2643 bytes
-rw-r--r--tests/projects/c++/ndk_raylib/android/res/values/strings.xml4
-rw-r--r--tests/projects/c++/ndk_raylib/android/res/values/styles.xml9
-rw-r--r--tests/projects/c++/ndk_raylib/src/main.cpp21
-rw-r--r--tests/projects/c++/ndk_raylib/xmake.lua22
-rw-r--r--xmake/rules/android/android_install.lua91
-rw-r--r--xmake/rules/android/android_run.lua31
-rw-r--r--xmake/rules/android/xmake.lua53
-rw-r--r--xmake/rules/ndk/install.lua34
-rw-r--r--xmake/rules/ndk/load.lua42
-rw-r--r--xmake/rules/ndk/package.lua97
-rw-r--r--xmake/rules/ndk/run.lua35
-rw-r--r--xmake/rules/ndk/xmake.lua33
16 files changed, 328 insertions, 175 deletions
diff --git a/tests/projects/c++/ndk_raylib/android/AndroidManifest.xml b/tests/projects/c++/ndk_raylib/android/AndroidManifest.xml
new file mode 100644
index 000000000..40cfd7066
--- /dev/null
+++ b/tests/projects/c++/ndk_raylib/android/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.raylib.demo"
+
+ android:versionCode="15"
+ android:versionName="1.5">
+
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
+
+ <uses-permission android:name="android.permission.SET_RELEASE_APP"/>
+
+ <application
+ android:label="Fighter" android:debuggable="true" android:hasCode="false"
+ tools:replace="android:icon,android:theme,android:allowBackup,label"
+ android:icon="@mipmap/icon">
+
+ <activity android:name="android.app.NativeActivity"
+ android:label="Fighter"
+ android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
+ android:screenOrientation="portrait"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+ <meta-data android:name="android.app.lib_name"
+ android:value="main" />
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest> \ No newline at end of file
diff --git a/tests/projects/c++/ndk_raylib/android/debug.jks b/tests/projects/c++/ndk_raylib/android/debug.jks
new file mode 100644
index 000000000..b4eb4f9f2
--- /dev/null
+++ b/tests/projects/c++/ndk_raylib/android/debug.jks
Binary files differ
diff --git a/tests/projects/c++/ndk_raylib/android/res/mipmap-mdpi/icon.png b/tests/projects/c++/ndk_raylib/android/res/mipmap-mdpi/icon.png
new file mode 100644
index 000000000..743413831
--- /dev/null
+++ b/tests/projects/c++/ndk_raylib/android/res/mipmap-mdpi/icon.png
Binary files differ
diff --git a/tests/projects/c++/ndk_raylib/android/res/mipmap/icon.png b/tests/projects/c++/ndk_raylib/android/res/mipmap/icon.png
new file mode 100644
index 000000000..743413831
--- /dev/null
+++ b/tests/projects/c++/ndk_raylib/android/res/mipmap/icon.png
Binary files differ
diff --git a/tests/projects/c++/ndk_raylib/android/res/values/strings.xml b/tests/projects/c++/ndk_raylib/android/res/values/strings.xml
new file mode 100644
index 000000000..e98a18284
--- /dev/null
+++ b/tests/projects/c++/ndk_raylib/android/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Space Defender</string>
+</resources> \ No newline at end of file
diff --git a/tests/projects/c++/ndk_raylib/android/res/values/styles.xml b/tests/projects/c++/ndk_raylib/android/res/values/styles.xml
new file mode 100644
index 000000000..621547380
--- /dev/null
+++ b/tests/projects/c++/ndk_raylib/android/res/values/styles.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="AppTheme" parent="android:Theme.NoTitleBar.Fullscreen">
+ <item name="android:windowBackground">@android:color/black</item>
+ <item name="android:colorPrimary">#FF6200EE</item>
+ <item name="android:colorPrimaryDark">#FF3700B3</item>
+ <item name="android:colorAccent">#FF03DAC5</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/tests/projects/c++/ndk_raylib/src/main.cpp b/tests/projects/c++/ndk_raylib/src/main.cpp
new file mode 100644
index 000000000..4c8253ead
--- /dev/null
+++ b/tests/projects/c++/ndk_raylib/src/main.cpp
@@ -0,0 +1,21 @@
+#include <raylib.h>
+
+int main() {
+ InitWindow(0, 0, "Hello, xmake for raylib android!");
+
+ SetTargetFPS(60);
+
+ while (!WindowShouldClose()) { // Main game loop
+ BeginDrawing();
+
+ ClearBackground(BLACK);
+
+ DrawText("Hello, xmake for raylib android!", 250, 250, 25, BLUE);
+
+ EndDrawing();
+ }
+
+ CloseWindow();
+
+ return 0;
+} \ No newline at end of file
diff --git a/tests/projects/c++/ndk_raylib/xmake.lua b/tests/projects/c++/ndk_raylib/xmake.lua
new file mode 100644
index 000000000..08a102d9d
--- /dev/null
+++ b/tests/projects/c++/ndk_raylib/xmake.lua
@@ -0,0 +1,22 @@
+add_rules("mode.debug", "mode.release")
+
+
+add_requires("raylib 5.5.0")
+
+target("raydemo_android")
+ set_kind("binary")
+ set_languages("c++17")
+ add_files("src/main.cpp")
+ add_packages("raylib")
+
+ if is_plat("android") then
+
+ add_rules("android.native_app", {
+ android_sdk_version = "35",
+ android_manifest = "android/AndroidManifest.xml",
+ android_res = "android/res",
+ keystore = "android/debug.jks",
+ keystore_pass = "123456",
+ package_name = "com.raylib.demo"
+ })
+ end
diff --git a/xmake/rules/android/android_install.lua b/xmake/rules/android/android_install.lua
deleted file mode 100644
index 97b78609b..000000000
--- a/xmake/rules/android/android_install.lua
+++ /dev/null
@@ -1,91 +0,0 @@
-function main(target, android_sdk_version, android_manifest, android_res, android_assets, attachedjar, keystore,
- keystore_pass, apk_output_path)
- local outputpath = path.join("build", "android", "output")
- if os.exists(outputpath) then
- os.rmdir(outputpath)
- end
- local outputtemppath = path.join(outputpath, "temp")
- os.mkdir(outputtemppath)
- local libpath = path.join(outputpath, "lib")
- os.mkdir(libpath)
- local libarchpath = path.join(libpath, target:arch())
- os.mkdir(libarchpath)
- os.cp(path.join(target:installdir(), "lib", "*.so"), libarchpath)
-
- -- rename to libmain.so
- local target_filename = target:filename()
- local source_lib = path.join(libarchpath, target_filename)
- local dest_lib = path.join(libarchpath, "libmain.so")
- if os.exists(source_lib) then
- os.mv(source_lib, dest_lib)
- else
- os.raise("can't find source lib: " .. source_lib)
- end
-
- import("core.tool.toolchain")
-
- local toolchain_ndk = toolchain.load("ndk", {
- plat = target:plat(),
- arch = target:arch()
- })
-
- -- get ndk
- local ndk = path.translate(assert(toolchain_ndk:config("ndk"), "cannot get NDK!"))
-
- -- get android sdk directory
- local android_sdkdir = path.translate(assert(toolchain_ndk:config("android_sdk"),
- "please run `xmake f --android_sdk=xxx` to set the android sdk directory!"))
-
- -- get android build-tools version
- local android_build_toolver = assert(toolchain_ndk:config("build_toolver"),
- "please run `xmake f --build_toolver=xxx` to set the android build-tools version!")
-
- local androidjar = path.join(android_sdkdir, "platforms", string.format("android-%s", android_sdk_version),
- "android.jar")
-
- local aapt = path.join(android_sdkdir, "build-tools", android_build_toolver,
- "aapt" .. (is_host("windows") and ".exe" or ""))
-
- --------------------------------------- pack resources
- local resonly_apk = path.join(outputtemppath, "res_only.apk")
- local aapt_argv = {"package", "-f", "-M", android_manifest, "-I", androidjar, "-F", resonly_apk}
-
- if android_res ~= nil and os.exists(android_res) then
- table.insert(aapt_argv, "-S")
- table.insert(aapt_argv, android_res)
- end
-
- if android_assets ~= nil and os.exists(android_assets) and os.emptydir(android_assets) == false then
- table.insert(aapt_argv, "-A")
- table.insert(aapt_argv, android_assets)
- end
-
- cprint("${green}[Android][Packing resources]${white} Save to " .. resonly_apk .. "...")
- os.vrunv(aapt, aapt_argv)
-
- --------------------------------------- pack libs
- local curdir = os.cd(outputpath)
- os.vrunv(aapt, {"add", "temp/res_only.apk", path.join("lib", target:arch(), "libmain.so")})
- cprint("${green}[Android][Packing library]${white} Adding lib/" .. target:arch() .. "/libmain.so to res_only.apk...")
- os.cd(curdir)
-
- --------------------------------------- align apk
- local aligned_apk = path.join(outputtemppath, "unsigned.apk")
- local zipalign = path.join(android_sdkdir, "build-tools", android_build_toolver,
- "zipalign" .. (is_host("windows") and ".exe" or ""))
- local zipalign_argv = {"-f", "4", resonly_apk, aligned_apk}
-
- cprint("${green}[Android][Align apk]${white} Save to " .. aligned_apk .. "...")
- os.vrunv(zipalign, zipalign_argv)
-
- --------------------------------------- sign apk
- local final_apk = path.join(apk_output_path, target:basename() .. ".apk")
- local apksigner = path.join(android_sdkdir, "build-tools", android_build_toolver,
- "apksigner" .. (is_host("windows") and ".bat" or ""))
- local apksigner_argv = {"sign", "--ks", keystore, "--ks-pass", string.format("pass:%s", keystore_pass), "--out",
- final_apk, "--in", aligned_apk}
-
- cprint("${green}[Android][Signing apk]${white} Save to " .. final_apk .. "...")
- os.vrunv(apksigner, apksigner_argv)
-
-end
diff --git a/xmake/rules/android/android_run.lua b/xmake/rules/android/android_run.lua
deleted file mode 100644
index c3c3cbaf8..000000000
--- a/xmake/rules/android/android_run.lua
+++ /dev/null
@@ -1,31 +0,0 @@
-function main(target, apk_output_path, package_name, activity_name)
- import("core.tool.toolchain")
- local toolchain_ndk = toolchain.load("ndk", {plat = target:plat(), arch = target:arch()})
- local android_sdkdir = path.translate(assert(toolchain_ndk:config("android_sdk"), "please run `xmake f --android_sdk=xxx` to set the android sdk directory!"))
- local adb = path.join(android_sdkdir, "platform-tools", "adb" .. (is_host("windows") and ".exe" or ""))
-
- local outputpath = path.join("build", "android", "output")
- local outputtemppath = path.join(outputpath, "temp")
- assert(os.exists(outputtemppath))
-
- local final_output_path = apk_output_path or outputtemppath
- local final_apk = path.join(final_output_path, target:basename() .. ".apk")
- assert(os.exists(final_apk))
-
- local install_argv = {
- "install", final_apk,
- }
-
- cprint("{green}[Installing apk] ...")
- os.vrunv(adb, install_argv)
-
- local run_argv = {
- "shell",
- "am",
- "start", "-n",
- package_name .. "/" .. activity_name
- }
-
- cprint("{green}[Run apk] ...")
- os.vrunv(adb, run_argv)
-end
diff --git a/xmake/rules/android/xmake.lua b/xmake/rules/android/xmake.lua
deleted file mode 100644
index f46facde4..000000000
--- a/xmake/rules/android/xmake.lua
+++ /dev/null
@@ -1,53 +0,0 @@
-rule("android.cpp")
- if is_plat("android") then
- on_load(function (target)
- import("core.tool.toolchain")
- local toolchain_ndk = toolchain.load("ndk", {plat = target:plat(), arch = target:arch()})
- if not toolchain_ndk then
- raise("NDK toolchain not found! Please configure NDK properly.")
- end
-
- local ndk_root = toolchain_ndk:config("ndk")
- if not ndk_root then
- raise("NDK path not set! Please set NDK path properly.")
- end
-
- local native_app_glue_path = path.join(ndk_root, "sources", "android", "native_app_glue")
-
- -- Add glue file and jni interface file to target
- local conf = target:extraconf("rules", "android.cpp")
- target:add("files", conf.jni_interface)
- target:add("files", path.join(native_app_glue_path, "android_native_app_glue.c"))
- target:add("includedirs", native_app_glue_path)
- end)
-
- after_install(function (target)
- local conf = target:extraconf("rules", "android.cpp")
- local android_sdk_version = conf.android_sdk_version
- local android_manifest = conf.android_manifest
- local android_res = conf.android_res
- local android_assets = conf.android_assets
- local keystore = assert(conf.keystore, "android.cpp rule requires `keystore` to be set")
- local keystore_pass = assert(conf.keystore_pass, "android.cpp rule requires `keystore_pass` to be set")
- local apk_output_path = conf.apk_output_path or "build"
- local attachedjar = conf.attachedjar
-
- assert(android_sdk_version, "android sdk version not set")
- assert(android_manifest, "android manifest not set")
-
- import("android_install")(target, android_sdk_version, android_manifest, android_res,
- android_assets, attachedjar, keystore, keystore_pass, apk_output_path)
- end)
-
- on_run(function (target)
- local conf = target:extraconf("rules", "android.cpp")
- local apk_output_path = conf.apk_output_path or "."
- local package_name = conf.package_name
- local activity_name = conf.activity_name
-
- assert(package_name, "package name not set")
- assert(activity_name, "activity name not set")
-
- import("android_run")(target, apk_output_path, package_name, activity_name)
- end)
- end
diff --git a/xmake/rules/ndk/install.lua b/xmake/rules/ndk/install.lua
new file mode 100644
index 000000000..3a1b04342
--- /dev/null
+++ b/xmake/rules/ndk/install.lua
@@ -0,0 +1,34 @@
+-- !A cross-platform build utility based on Lua
+--
+-- 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.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author Xmake Open Source Community
+-- @file xmake.lua
+--
+
+function main(target)
+
+ local android_sdkdir = target:toolchain("ndk"):config("android_sdk")
+ local adb = path.join(android_sdkdir, "platform-tools", "adb")
+
+ local final_apk = path.join(target:targetdir(), target:basename() .. ".apk")
+ assert(os.exists(final_apk))
+
+ cprint("${green}[Android][Install] try to install apk ...")
+ os.vrunv(adb, {"install", final_apk})
+
+ cprint("${green}[Android][Install] done.")
+
+end
diff --git a/xmake/rules/ndk/load.lua b/xmake/rules/ndk/load.lua
new file mode 100644
index 000000000..5eaee58aa
--- /dev/null
+++ b/xmake/rules/ndk/load.lua
@@ -0,0 +1,42 @@
+-- !A cross-platform build utility based on Lua
+--
+-- 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.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author Xmake Open Source Community
+-- @file xmake.lua
+--
+
+import("core.tool.toolchain")
+
+-- main entry
+function main (target)
+
+ local toolchain_ndk = target:toolchain("ndk")
+
+ local ndk_root = toolchain_ndk:config("ndk")
+ if not ndk_root then
+ raise("NDK path not set! Please set NDK path properly.")
+ end
+
+ local native_app_glue_file = path.join(ndk_root, "sources", "android", "native_app_glue", "android_native_app_glue.c")
+
+ -- Add glue file to target
+ local conf = target:extraconf("rules", "android.native_app")
+ target:add("files", native_app_glue_file)
+ target:add("includedirs", native_app_glue_path)
+
+ target:set("kind", "shared")
+
+end
diff --git a/xmake/rules/ndk/package.lua b/xmake/rules/ndk/package.lua
new file mode 100644
index 000000000..227afaaa8
--- /dev/null
+++ b/xmake/rules/ndk/package.lua
@@ -0,0 +1,97 @@
+-- !A cross-platform build utility based on Lua
+--
+-- 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.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author Xmake Open Source Community
+-- @file xmake.lua
+--
+
+import("core.tool.toolchain")
+
+function main(target)
+ local conf = target:extraconf("rules", "android.native_app")
+ local android_sdk_version = conf.android_sdk_version
+ local android_manifest = conf.android_manifest
+ local android_res = conf.android_res
+ local android_assets = conf.android_assets
+ local keystore = conf.keystore
+ local keystore_pass = conf.keystore_pass
+
+ assert(android_sdk_version, "android sdk version not set")
+ assert(android_manifest, "android manifest not set")
+
+ cprint("${green}[Android][Package]${white} Starting...")
+
+ local tmp_path = path.join(target:targetdir(), "temp")
+ os.mkdir(tmp_path)
+ os.mkdir(path.join(tmp_path, "lib"))
+ os.mkdir(path.join(tmp_path, "lib", target:arch()))
+
+ -- copy the target library to temp folder with name libmain.so
+ local libfile = path.join(tmp_path, "lib", target:arch(), "libmain.so")
+ os.cp(target:targetfile(), libfile)
+
+
+ -- get android tool path
+ local android_sdkdir = target:toolchain("ndk"):config("android_sdk")
+ local android_build_toolver = target:toolchain("ndk"):config("build_toolver")
+ local sdk_tool_path = path.join(android_sdkdir, "build-tools", android_build_toolver)
+
+ local aapt = path.join(sdk_tool_path, "aapt" .. (is_host("windows") and ".exe" or ""))
+ local zipalign = path.join(sdk_tool_path, "zipalign" .. (is_host("windows") and ".exe" or ""))
+ local apksigner = path.join(sdk_tool_path, "apksigner" .. (is_host("windows") and ".bat" or ""))
+
+ -- pack resources
+ local resonly_apk = path.join(tmp_path, "res_only.apk")
+ local androidjar = path.join(android_sdkdir, "platforms", string.format("android-%s", android_sdk_version),
+ "android.jar")
+ assert(os.exists(androidjar), "%s not found", androidjar)
+ local aapt_argv = {"package", "-f", "-M", android_manifest, "-I", androidjar, "-F", resonly_apk}
+
+ if android_res ~= nil and os.exists(android_res) then
+ table.insert(aapt_argv, "-S")
+ table.insert(aapt_argv, android_res)
+ end
+
+ if android_assets ~= nil and os.exists(android_assets) and os.emptydir(android_assets) == false then
+ table.insert(aapt_argv, "-A")
+ table.insert(aapt_argv, android_assets)
+ end
+
+ cprint("${green}[Android][Packing resources]${white} Create a resource only apk...")
+ os.vrunv(aapt, aapt_argv)
+
+ -- pack libs
+ cprint("${green}[Android][Packing library]${white} Adding library to res_only.apk...")
+ os.vrunv(aapt, {"add", "res_only.apk", "lib/" .. target:arch() .."/libmain.so"}, {curdir = tmp_path})
+
+ -- align apk
+ local aligned_apk = path.join(tmp_path, "unsigned.apk")
+ local zipalign_argv = {"-f", "4", "res_only.apk", "unsigned.apk"}
+
+ cprint("${green}[Android][Align apk]${white} Save to " .. aligned_apk .. "...")
+ os.vrunv(zipalign, zipalign_argv, {curdir = tmp_path})
+
+ -- sign apk
+ local final_apk = path.join(target:targetdir(), target:basename() .. ".apk")
+
+ local apksigner_argv = {"sign", "--ks", keystore, "--ks-pass", string.format("pass:%s", keystore_pass), "--out",
+ final_apk, "--in", aligned_apk}
+ cprint("${green}[Android][Signing apk]${white} Save to " .. final_apk .. "...")
+ os.vrunv(apksigner, apksigner_argv)
+
+ cprint("${green}[Android][Package]${white} Done!")
+
+end
diff --git a/xmake/rules/ndk/run.lua b/xmake/rules/ndk/run.lua
new file mode 100644
index 000000000..08751021a
--- /dev/null
+++ b/xmake/rules/ndk/run.lua
@@ -0,0 +1,35 @@
+-- !A cross-platform build utility based on Lua
+--
+-- 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.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author Xmake Open Source Community
+-- @file xmake.lua
+--
+
+function main(target)
+
+ local conf = target:extraconf("rules", "android.native_app")
+ local package_name = conf.package_name
+ local activity_name = conf.activity_name or "android.app.NativeActivity"
+
+ local android_sdkdir = target:toolchain("ndk"):config("android_sdk")
+ local adb = path.join(android_sdkdir, "platform-tools", "adb")
+
+ local run_argv = {"shell", "am", "start", "-n", package_name .. "/" .. activity_name}
+
+ cprint("${green}[Android] Starting app ...")
+ os.vrunv(adb, run_argv)
+
+end
diff --git a/xmake/rules/ndk/xmake.lua b/xmake/rules/ndk/xmake.lua
new file mode 100644
index 000000000..2779a0665
--- /dev/null
+++ b/xmake/rules/ndk/xmake.lua
@@ -0,0 +1,33 @@
+--!A cross-platform build utility based on Lua
+--
+-- 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.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author Xmake Open Source Community
+-- @file xmake.lua
+--
+
+-- define rule: build android native app with NDK
+rule("android.native_app")
+ -- we must set_kind and add some glue files to target
+ on_load("load")
+
+ -- generate android apk package
+ on_package("package")
+
+ -- install android package with adb
+ on_install("install")
+
+ -- run android app through adb
+ after_install("run") \ No newline at end of file