summaryrefslogtreecommitdiff
path: root/xmake/rules/qt/deploy/android.lua
blob: 8a620683e70c361907caaf8e2ef493198a4f34b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
--!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      ruki
-- @file        android.lua
--

-- imports
import("core.theme.theme")
import("core.base.option")
import("core.base.semver")
import("core.project.config")
import("core.project.depend")
import("core.tool.toolchain")
import("lib.detect.find_file")
import("utils.progress")

-- escape path
function _escape_path(p)
    return os.args(p, {escape = true})
end

-- deploy application package for android
function main(target, opt)

    -- get ndk toolchain
    local toolchain_ndk = toolchain.load("ndk", {plat = target:plat(), arch = target:arch()})

    -- get target apk path
    local target_apk = path.join(target:targetdir(), target:basename() .. ".apk")

    -- need re-generate this apk?
    local targetfile = target:targetfile()
    local dependfile = target:dependfile(target_apk)
    local dependinfo = target:is_rebuilt() and {} or (depend.load(dependfile) or {})
    if not depend.is_changed(dependinfo, {lastmtime = os.mtime(dependfile)}) then
        return
    end

    -- trace progress info
    progress.show(opt.progress, "${color.build.target}generating.qt.app %s.apk", target:basename())

    -- get qt sdk
    local qt = target:data("qt")

    -- get ndk
    local ndk = path.translate(assert(toolchain_ndk:config("ndk"), "cannot get NDK!"))
    local ndk_sdkver = assert(toolchain_ndk:config("ndk_sdkver"), "cannot get the sdk version of NDK!")

    -- get ndk host
    local ndk_host = os.host() .. "-" .. os.arch()
    if is_host("windows") then
        ndk_host = os.arch() == "x64" and "windows-x86_64" or "windows-x86"
    elseif is_host("macosx") then
        ndk_host = "darwin-x86_64"
    elseif is_host("linux") then
        ndk_host = "linux-x86_64"
    end

    -- get androiddeployqt
    local search_dirs = {}
    if qt.bindir_host then table.insert(search_dirs, qt.bindir_host) end
    if qt.bindir then table.insert(search_dirs, qt.bindir) end
    local androiddeployqt = find_file("androiddeployqt" .. (is_host("windows") and ".exe" or ""), search_dirs)
    assert(os.isexec(androiddeployqt), "androiddeployqt not found!")

    -- get working directory
    local workdir = path.join(config.builddir(), ".qt", "app", "android", target:name())

    -- get android-build directory
    local android_buildir = path.join(workdir, "android-build")

    -- get android platform
    local android_platform = "android-" .. tostring(ndk_sdkver)

    -- get java home
    local java_home = assert(os.getenv("JAVA_HOME"), "please set $JAVA_HOME environment variable first!")

    -- 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!")

    -- get qt sdk version
    local qt_sdkver = config.get("qt_sdkver")
    if qt_sdkver then
        qt_sdkver = try { function () return semver.new(qt_sdkver) end}
    end

    -- get the target architecture
    local target_archs =
    {
        ["armv5te"]     = "armeabi"       -- deprecated
    ,   ["armv7-a"]     = "armeabi-v7a"   -- deprecated
    ,   ["armeabi"]     = "armeabi"
    ,   ["armeabi-v7a"] = "armeabi-v7a"
    ,   ["arm64-v8a"]   = "arm64-v8a"
    ,   i386            = "x86"
    ,   x86_64          = "x86_64"
    ,   mips            = "mips"          -- removed in ndk r71
    ,   mips64          = "mips64"        -- removed in ndk r71
    }
    local target_arch = assert(target_archs[config.arch()], "unsupport target arch(%s)!", config.arch())

    -- install target to android-build/libs first
    if qt_sdkver and qt_sdkver:ge("5.14") then
        -- we need to copy target to android-build/libs/armeabi/libxxx_armeabi.so after Qt 5.14.0
        os.cp(target:targetfile(), path.join(android_buildir, "libs", target_arch, "lib" .. target:basename() .. "_" .. target_arch .. ".so"))
    else
        os.cp(target:targetfile(), path.join(android_buildir, "libs", target_arch, path.filename(target:targetfile())))
    end

    -- get stdcpp path
    local stdcpp_path = path.join(ndk, "sources/cxx-stl/llvm-libc++/libs", target_arch, "libc++_shared.so")
    if qt_sdkver and qt_sdkver:ge("5.14") then
        local ndk_sysroot = assert(toolchain_ndk:config("ndk_sysroot"), "NDK sysroot directory not found!")
        stdcpp_path = path.join(ndk_sysroot, "usr", "lib")
    end

    -- get toolchain version
    local ndk_toolchains_ver = toolchain_ndk:config("ndk_toolchains_ver") or "4.9"

    -- generate android-deployment-settings.json file
    local android_deployment_settings = path.join(workdir, "android-deployment-settings.json")
    local settings_file = io.open(android_deployment_settings, "w")
    if settings_file then
        settings_file:print('{')
        settings_file:print('   "description": "This file is generated by qmake to be read by androiddeployqt and should not be modified by hand.",')
        settings_file:print('   "qt": "%s",', _escape_path(qt.sdkdir))
        settings_file:print('   "sdk": "%s",', _escape_path(android_sdkdir))
        settings_file:print('   "ndk": "%s",', _escape_path(ndk))
        settings_file:print('   "sdkBuildToolsRevision": "%s",', android_build_toolver)
        settings_file:print('   "toolchain-prefix": "llvm",')
        settings_file:print('   "tool-prefix": "llvm",')
        settings_file:print('   "toolchain-version": "%s",', ndk_toolchains_ver)
        settings_file:print('   "stdcpp-path": "%s",', _escape_path(stdcpp_path))
        settings_file:print('   "ndk-host": "%s",', ndk_host)
        settings_file:print('   "target-architecture": "%s",', target_arch)
        settings_file:print('   "qml-root-path": "%s",', _escape_path(os.projectdir()))
        -- for 6.2.x
        local search_dirs = {}
        if qt.libexecdir_host then table.insert(search_dirs, qt.libexecdir_host) end
        if qt.libexecdir then table.insert(search_dirs, qt.libexecdir) end
        local qmlimportscanner = find_file("qmlimportscanner" .. (is_host("windows") and ".exe" or ""), search_dirs)
        if os.isexec(qmlimportscanner) then
            settings_file:print('   "qml-importscanner-binary": "%s",', _escape_path(qmlimportscanner))
        end
        -- for 6.3.x
        local search_dirs = {}
        if qt.bindir_host then table.insert(search_dirs, qt.bindir_host) end
        if qt.bindir then table.insert(search_dirs, qt.bindir) end
        if qt.libexecdir_host then table.insert(search_dirs, qt.libexecdir_host) end
        if qt.libexecdir then table.insert(search_dirs, qt.libexecdir) end
        local rcc = find_file("rcc" .. (is_host("windows") and ".exe" or ""), search_dirs)
        if os.isexec(rcc) then
            settings_file:print('   "rcc-binary": "%s",', _escape_path(rcc))
        end
        local platformplugin = find_file("libplugins_platforms_qtforandroid_" .. target_arch .. "*", path.join(qt.sdkdir, "plugins", "platforms"))
        if platformplugin then
            settings_file:print('   "deployment-dependencies": {"%s":"%s"},', target_arch, _escape_path(platformplugin))
        end

        local minsdkversion = target:values("qt.android.minsdkversion")
        if minsdkversion then
            settings_file:print('    "android-min-sdk-version": "%s",', tostring(minsdkversion))
        end
        local targetsdkversion = target:values("qt.android.targetsdkversion")
        if targetsdkversion then
            settings_file:print('    "android-target-sdk-version": "%s",', tostring(targetsdkversion))
        end
        settings_file:print('   "useLLVM": true,')
        if qt_sdkver and qt_sdkver:ge("5.14") then
            -- @see https://codereview.qt-project.org/c/qt-creator/qt-creator/+/287145
            local triples =
            {
                ["armv5te"]     = "arm-linux-androideabi"   -- deprecated
            ,   ["armv7-a"]     = "arm-linux-androideabi"   -- deprecated
            ,   ["armeabi"]     = "arm-linux-androideabi"   -- removed in ndk r17
            ,   ["armeabi-v7a"] = "arm-linux-androideabi"
            ,   ["arm64-v8a"]   = "aarch64-linux-android"
            ,   i386            = "i686-linux-android"      -- deprecated
            ,   x86             = "i686-linux-android"
            ,   x86_64          = "x86_64-linux-android"
            ,   mips            = "mips-linux-android"      -- removed in ndk r17
            ,   mips64          = "mips64-linux-android"    -- removed in ndk r17
            }
            settings_file:print('   "architectures": {"%s":"%s"},', target_arch, triples[target_arch])
            settings_file:print('   "application-binary": "%s"', target:basename())
        else
            settings_file:print('   "application-binary": "%s"', _escape_path(target:targetfile()))
        end
        settings_file:print('}')
        settings_file:close()
    end
    if option.get("verbose") and option.get("diagnosis") then
        io.cat(android_deployment_settings)
    end

    -- do deploy
    local argv = {"--input", android_deployment_settings,
                  "--output", android_buildir,
                  "--jdk", java_home,
                  "--gradle", "--no-gdbserver"}
    if option.get("verbose") and option.get("diagnosis") then
        table.insert(argv, "--verbose")
    end

    -- add user flags
    local user_flags = target:values("qt.deploy.flags") or {}
    if user_flags then
        argv = table.join(argv, user_flags)
    end

    os.vrunv(androiddeployqt, argv)

    -- output apk
    os.cp(path.join(android_buildir, "build", "outputs", "apk", "debug", "android-build-debug.apk"), target_apk)

    -- show apk output path
    vprint("the apk output path: %s", target_apk)

    -- update files and values to the dependent file
    dependinfo.files = {targetfile}
    depend.save(dependinfo, dependfile)
end