2020-07-20 18:46:15 +08:00
|
|
|
#!/usr/bin/python
|
|
|
|
# build_native.py
|
|
|
|
# Build native codes
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os, os.path
|
|
|
|
import shutil
|
|
|
|
from optparse import OptionParser
|
|
|
|
import cocos
|
|
|
|
from MultiLanguage import MultiLanguage
|
|
|
|
import cocos_project
|
|
|
|
import json
|
|
|
|
import re
|
|
|
|
from xml.dom import minidom
|
|
|
|
|
2021-04-22 22:01:47 +08:00
|
|
|
if(sys.version_info.major < 3):
|
|
|
|
import project_compile
|
2020-07-20 18:46:15 +08:00
|
|
|
|
|
|
|
BUILD_CFIG_FILE="build-cfg.json"
|
|
|
|
|
|
|
|
class AndroidBuilder(object):
|
|
|
|
|
|
|
|
CFG_KEY_COPY_TO_ASSETS = "copy_to_assets"
|
|
|
|
CFG_KEY_MUST_COPY_TO_ASSERTS = "must_copy_to_assets"
|
|
|
|
CFG_KEY_STORE = "key_store"
|
|
|
|
CFG_KEY_STORE_PASS = "key_store_pass"
|
|
|
|
CFG_KEY_ALIAS = "alias"
|
|
|
|
CFG_KEY_ALIAS_PASS = "alias_pass"
|
|
|
|
|
|
|
|
GRADLE_KEY_STORE = "RELEASE_STORE_FILE"
|
|
|
|
GRADLE_KEY_ALIAS = "RELEASE_KEY_ALIAS"
|
|
|
|
GRADLE_KEY_STORE_PASS = "RELEASE_STORE_PASSWORD"
|
|
|
|
GRADLE_KEY_ALIAS_PASS = "RELEASE_KEY_PASSWORD"
|
|
|
|
|
|
|
|
GRADLE_PROP_TARGET_VERSION = 'PROP_TARGET_SDK_VERSION'
|
|
|
|
GRADLE_PROP_BUILD_TYPE = 'PROP_BUILD_TYPE'
|
|
|
|
GRADLE_PROP_APP_ABI = 'PROP_APP_ABI'
|
|
|
|
GRADLE_PROP_COMPILE_SCRIPT = 'PROP_COMPILE_SCRIPT'
|
|
|
|
GRADLE_PROP_LUA_ENCRYPT = 'PROP_LUA_ENCRYPT'
|
|
|
|
GRADLE_PROP_LUA_ENCRYPT_KEY = 'PROP_LUA_ENCRYPT_KEY'
|
|
|
|
GRADLE_PROP_LUA_ENCRYPT_SIGN = 'PROP_LUA_ENCRYPT_SIGN'
|
|
|
|
|
|
|
|
def __init__(self, verbose, app_android_root, no_res, proj_obj, mode, build_type, app_abi, gradle_support_ndk=False):
|
|
|
|
self._verbose = verbose
|
|
|
|
|
|
|
|
self.app_android_root = app_android_root
|
|
|
|
self._no_res = no_res
|
|
|
|
self._project = proj_obj
|
|
|
|
self.gradle_support_ndk = gradle_support_ndk
|
|
|
|
self.app_abi = app_abi
|
|
|
|
self.mode = mode
|
|
|
|
self.build_type = build_type
|
|
|
|
|
|
|
|
# check environment variable
|
2021-04-22 22:01:47 +08:00
|
|
|
self.sdk_root = cocos.check_environment_variable('ANDROID_SDK')
|
2020-07-20 18:46:15 +08:00
|
|
|
self.ant_root = None
|
|
|
|
if os.path.exists(os.path.join(self.app_android_root, "gradle.properties")):
|
|
|
|
self.sign_prop_file = os.path.join(self.app_android_root, "gradle.properties")
|
|
|
|
elif os.path.exists(os.path.join(self.app_android_root, 'app', "gradle.properties")):
|
|
|
|
self.sign_prop_file = os.path.join(self.app_android_root, 'app', "gradle.properties")
|
|
|
|
else:
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_GRADLE_PROPERTIES_NOT_FOUND'),
|
|
|
|
cocos.CCPluginError.ERROR_PATH_NOT_FOUND)
|
|
|
|
self._parse_cfg()
|
|
|
|
|
|
|
|
def _run_cmd(self, command, cwd=None):
|
|
|
|
cocos.CMDRunner.run_cmd(command, self._verbose, cwd=cwd)
|
|
|
|
|
|
|
|
def _parse_cfg(self):
|
|
|
|
# get the properties for sign release apk
|
|
|
|
self.key_store_str = AndroidBuilder.GRADLE_KEY_STORE
|
|
|
|
self.key_alias_str = AndroidBuilder.GRADLE_KEY_ALIAS
|
|
|
|
self.key_store_pass_str = AndroidBuilder.GRADLE_KEY_STORE_PASS
|
|
|
|
self.key_alias_pass_str = AndroidBuilder.GRADLE_KEY_ALIAS_PASS
|
|
|
|
|
|
|
|
if self.gradle_support_ndk:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.cfg_path = os.path.join(self.app_android_root, BUILD_CFIG_FILE)
|
|
|
|
try:
|
|
|
|
f = open(self.cfg_path)
|
|
|
|
cfg = json.load(f, encoding='utf8')
|
|
|
|
f.close()
|
|
|
|
except Exception:
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_ERROR_PARSE_CFG_FAILED_FMT', self.cfg_path),
|
|
|
|
cocos.CCPluginError.ERROR_PARSE_FILE)
|
|
|
|
|
2021-04-22 22:01:47 +08:00
|
|
|
if cocos.dict_contains(cfg, project_compile.CCPluginCompile.CFG_KEY_MUST_COPY_RESOURCES):
|
2020-07-20 18:46:15 +08:00
|
|
|
if self._no_res:
|
|
|
|
self.res_files = cfg[project_compile.CCPluginCompile.CFG_KEY_MUST_COPY_RESOURCES]
|
|
|
|
else:
|
|
|
|
self.res_files = cfg[project_compile.CCPluginCompile.CFG_KEY_MUST_COPY_RESOURCES] + cfg[project_compile.CCPluginCompile.CFG_KEY_COPY_RESOURCES]
|
|
|
|
else:
|
|
|
|
self.res_files = cfg[project_compile.CCPluginCompile.CFG_KEY_COPY_RESOURCES]
|
|
|
|
|
|
|
|
move_cfg = {}
|
|
|
|
self.key_store = None
|
2021-04-22 22:01:47 +08:00
|
|
|
if cocos.dict_contains(cfg, AndroidBuilder.CFG_KEY_STORE):
|
2020-07-20 18:46:15 +08:00
|
|
|
self.key_store = cfg[AndroidBuilder.CFG_KEY_STORE]
|
|
|
|
move_cfg[self.key_store_str] = self.key_store
|
|
|
|
del cfg[AndroidBuilder.CFG_KEY_STORE]
|
|
|
|
|
|
|
|
self.key_store_pass = None
|
2021-04-22 22:01:47 +08:00
|
|
|
if cocos.dict_contains(cfg, AndroidBuilder.CFG_KEY_STORE_PASS):
|
2020-07-20 18:46:15 +08:00
|
|
|
self.key_store_pass = cfg[AndroidBuilder.CFG_KEY_STORE_PASS]
|
|
|
|
move_cfg[self.key_store_pass_str] = self.key_store_pass
|
|
|
|
del cfg[AndroidBuilder.CFG_KEY_STORE_PASS]
|
|
|
|
|
|
|
|
self.alias = None
|
2021-04-22 22:01:47 +08:00
|
|
|
if cocos.dict_contains(cfg, AndroidBuilder.CFG_KEY_ALIAS):
|
2020-07-20 18:46:15 +08:00
|
|
|
self.alias = cfg[AndroidBuilder.CFG_KEY_ALIAS]
|
|
|
|
move_cfg[self.key_alias_str] = self.alias
|
|
|
|
del cfg[AndroidBuilder.CFG_KEY_ALIAS]
|
|
|
|
|
|
|
|
self.alias_pass = None
|
2021-04-22 22:01:47 +08:00
|
|
|
if cocos.dict_contains(cfg, AndroidBuilder.CFG_KEY_ALIAS_PASS):
|
2020-07-20 18:46:15 +08:00
|
|
|
self.alias_pass = cfg[AndroidBuilder.CFG_KEY_ALIAS_PASS]
|
|
|
|
move_cfg[self.key_alias_pass_str] = self.alias_pass
|
|
|
|
del cfg[AndroidBuilder.CFG_KEY_ALIAS_PASS]
|
|
|
|
|
|
|
|
if len(move_cfg) > 0:
|
|
|
|
# move the config into ant.properties
|
|
|
|
self._move_cfg(move_cfg)
|
|
|
|
with open(self.cfg_path, 'w') as outfile:
|
|
|
|
json.dump(cfg, outfile, sort_keys = True, indent = 4)
|
|
|
|
outfile.close()
|
|
|
|
|
|
|
|
def has_keystore_in_signprops(self):
|
|
|
|
keystore = None
|
|
|
|
pattern = re.compile(r"^RELEASE_STORE_FILE=(.+)")
|
|
|
|
|
|
|
|
try:
|
|
|
|
file_obj = open(self.sign_prop_file)
|
|
|
|
for line in file_obj:
|
|
|
|
str1 = line.replace(' ', '')
|
|
|
|
str2 = str1.replace('\t', '')
|
|
|
|
match = pattern.match(str2)
|
|
|
|
if match is not None:
|
|
|
|
keystore = match.group(1)
|
|
|
|
break
|
|
|
|
file_obj.close()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if keystore is None:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _write_sign_properties(self, cfg):
|
|
|
|
file_obj = open(self.sign_prop_file, "a+")
|
|
|
|
for key in cfg.keys():
|
|
|
|
str_cfg = "%s=%s\n" % (key, cfg[key])
|
|
|
|
file_obj.write(str_cfg)
|
|
|
|
|
|
|
|
file_obj.close()
|
|
|
|
|
|
|
|
def _move_cfg(self, cfg):
|
|
|
|
if not self.has_keystore_in_signprops():
|
|
|
|
self._write_sign_properties(cfg)
|
|
|
|
|
|
|
|
def remove_c_libs(self, libs_dir):
|
|
|
|
for file_name in os.listdir(libs_dir):
|
|
|
|
lib_file = os.path.join(libs_dir, file_name)
|
|
|
|
if os.path.isfile(lib_file):
|
|
|
|
ext = os.path.splitext(lib_file)[1]
|
|
|
|
if ext == ".a" or ext == ".so":
|
|
|
|
os.remove(lib_file)
|
|
|
|
|
|
|
|
def _get_android_sdk_tools_ver(self, sdk_tools_path):
|
|
|
|
cfg_file = os.path.join(sdk_tools_path, 'source.properties')
|
|
|
|
|
|
|
|
if os.path.isfile(cfg_file):
|
|
|
|
f = open(cfg_file)
|
|
|
|
lines = f.readlines()
|
|
|
|
pattern = r'^Pkg\.Revision=(\d+)\.(\d+)'
|
|
|
|
for l in lines:
|
|
|
|
match = re.match(pattern, l.strip())
|
|
|
|
if match:
|
|
|
|
return ((int)(match.group(1)), (int)(match.group(2)))
|
|
|
|
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_ERROR_UNKNOWN_ANDROID_SDK_TOOLS_VERSION'),
|
|
|
|
cocos.CCPluginError.ERROR_PATH_NOT_FOUND)
|
|
|
|
|
|
|
|
def _update_project_properties(self, folder_path, target_str):
|
|
|
|
props_path = os.path.join(folder_path, 'project.properties')
|
|
|
|
f = open(props_path)
|
|
|
|
lines = f.readlines()
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
pattern = r'^target=(.*)$'
|
|
|
|
matched = False
|
|
|
|
new_line = 'target=%s\n' % target_str
|
|
|
|
for i in range(0, len(lines)):
|
|
|
|
l = lines[i]
|
|
|
|
match = re.match(pattern, l.strip())
|
|
|
|
if match:
|
|
|
|
lines[i] = new_line
|
|
|
|
matched = True
|
|
|
|
|
|
|
|
if not matched:
|
|
|
|
lines.append('\n')
|
|
|
|
lines.append(new_line)
|
|
|
|
|
|
|
|
f = open(props_path, 'w')
|
|
|
|
f.writelines(lines)
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
def _write_local_properties(self, folder_path):
|
|
|
|
local_porps_path = os.path.join(folder_path, 'local.properties')
|
|
|
|
sdk_dir = self.sdk_root
|
2021-04-22 22:01:47 +08:00
|
|
|
ndk_dir = cocos.check_environment_variable('ANDROID_NDK')
|
2020-07-20 18:46:15 +08:00
|
|
|
if cocos.os_is_win32():
|
|
|
|
# On Windows, the path should be like:
|
|
|
|
# sdk.dir = C:\\path\\android-sdk
|
|
|
|
sdk_dir = sdk_dir.replace('\\', '\\\\')
|
|
|
|
ndk_dir = ndk_dir.replace('\\', '\\\\')
|
|
|
|
lines = [
|
|
|
|
'sdk.dir=%s\n' % sdk_dir,
|
|
|
|
'ndk.dir=%s\n' % ndk_dir
|
|
|
|
]
|
|
|
|
f = open(local_porps_path, 'w')
|
|
|
|
f.writelines(lines)
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
def update_project(self, android_platform):
|
|
|
|
if self.gradle_support_ndk:
|
|
|
|
# If gradle supports ndk build, should write local.properties manually
|
|
|
|
self._write_local_properties(self.app_android_root)
|
|
|
|
return
|
|
|
|
|
|
|
|
manifest_path = os.path.join(self.app_android_root, 'app')
|
|
|
|
|
|
|
|
# check the android platform
|
|
|
|
target_str = self.check_android_platform(self.sdk_root, android_platform, manifest_path)
|
|
|
|
|
|
|
|
# should manually update the project
|
|
|
|
self._write_local_properties(manifest_path)
|
|
|
|
self._update_project_properties(manifest_path, target_str)
|
|
|
|
|
|
|
|
# copy the local.properties to the app_android_root
|
|
|
|
file_name = 'local.properties'
|
|
|
|
src_path = os.path.normpath(os.path.join(manifest_path, file_name))
|
|
|
|
dst_path = os.path.normpath(os.path.join(self.app_android_root, file_name))
|
|
|
|
if src_path != dst_path:
|
|
|
|
if os.path.isfile(dst_path):
|
|
|
|
os.remove(dst_path)
|
|
|
|
shutil.copy(src_path, dst_path)
|
|
|
|
|
|
|
|
def get_toolchain_version(self, ndk_root, compile_obj):
|
|
|
|
# it should be possible to override the toolchain
|
|
|
|
if 'NDK_TOOLCHAIN_VERSION' in os.environ:
|
|
|
|
return os.environ['NDK_TOOLCHAIN_VERSION']
|
|
|
|
return '4.9'
|
|
|
|
|
|
|
|
|
|
|
|
def do_ndk_build(self, ndk_build_param, mode, build_type, compile_obj):
|
|
|
|
cocos.Logging.info(MultiLanguage.get_string('COMPILE_INFO_NDK_BUILD_TYPE', build_type))
|
2021-04-22 22:01:47 +08:00
|
|
|
ndk_root = cocos.check_environment_variable('ANDROID_NDK')
|
2020-07-20 18:46:15 +08:00
|
|
|
|
|
|
|
toolchain_version = self.get_toolchain_version(ndk_root, compile_obj)
|
|
|
|
|
|
|
|
ndk_work_dir = os.path.join(self.app_android_root, 'app')
|
|
|
|
reload(sys)
|
|
|
|
sys.setdefaultencoding('utf8')
|
|
|
|
ndk_path = cocos.CMDRunner.convert_path_to_cmd(os.path.join(ndk_root, "ndk-build"))
|
|
|
|
|
|
|
|
# delete template static and dynamic files
|
|
|
|
obj_local_dir = os.path.join(ndk_work_dir, "obj", "local")
|
|
|
|
if os.path.isdir(obj_local_dir):
|
|
|
|
for abi_dir in os.listdir(obj_local_dir):
|
|
|
|
static_file_path = os.path.join(ndk_work_dir, "obj", "local", abi_dir)
|
|
|
|
if os.path.isdir(static_file_path):
|
|
|
|
self.remove_c_libs(static_file_path)
|
|
|
|
|
|
|
|
if ndk_build_param is None:
|
|
|
|
ndk_build_cmd = '%s -C %s' % (ndk_path, ndk_work_dir)
|
|
|
|
else:
|
|
|
|
ndk_build_cmd = '%s -C %s %s' % (ndk_path, ndk_work_dir, ' '.join(ndk_build_param))
|
|
|
|
|
|
|
|
ndk_build_cmd = '%s NDK_TOOLCHAIN_VERSION=%s' % (ndk_build_cmd, toolchain_version)
|
|
|
|
|
|
|
|
if mode == 'debug':
|
|
|
|
ndk_build_cmd = '%s NDK_DEBUG=1' % ndk_build_cmd
|
|
|
|
|
|
|
|
self._run_cmd(ndk_build_cmd)
|
|
|
|
|
|
|
|
|
|
|
|
def _xml_attr(self, dir, file_name, node_name, attr):
|
|
|
|
doc = minidom.parse(os.path.join(dir, file_name))
|
|
|
|
return doc.getElementsByTagName(node_name)[0].getAttribute(attr)
|
|
|
|
|
|
|
|
def update_lib_projects(self, sdk_root, sdk_tool_path, android_platform, property_path):
|
|
|
|
property_file = os.path.join(property_path, "project.properties")
|
|
|
|
if not os.path.isfile(property_file):
|
|
|
|
return
|
|
|
|
|
|
|
|
patten = re.compile(r'^android\.library\.reference\.[\d]+=(.+)')
|
|
|
|
for line in open(property_file):
|
|
|
|
str1 = line.replace(' ', '')
|
|
|
|
str2 = str1.replace('\t', '')
|
|
|
|
match = patten.match(str2)
|
|
|
|
if match is not None:
|
|
|
|
# a lib project is found
|
|
|
|
lib_path = match.group(1)
|
|
|
|
abs_lib_path = os.path.join(property_path, lib_path)
|
|
|
|
abs_lib_path = os.path.normpath(abs_lib_path)
|
|
|
|
if os.path.isdir(abs_lib_path):
|
|
|
|
target_str = self.check_android_platform(sdk_root, android_platform, abs_lib_path)
|
|
|
|
command = "%s update lib-project -p %s -t %s" % (cocos.CMDRunner.convert_path_to_cmd(sdk_tool_path), abs_lib_path, target_str)
|
|
|
|
self._run_cmd(command)
|
|
|
|
|
|
|
|
self.update_lib_projects(sdk_root, sdk_tool_path, android_platform, abs_lib_path)
|
|
|
|
|
|
|
|
def get_api_level(self, target_str, raise_error=True):
|
|
|
|
match = re.match(r'android-(\d+)', target_str)
|
|
|
|
if match is not None:
|
|
|
|
ret = int(match.group(1))
|
|
|
|
else:
|
|
|
|
if raise_error:
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_ERROR_NOT_VALID_AP_FMT', target_str),
|
|
|
|
cocos.CCPluginError.ERROR_PARSE_FILE)
|
|
|
|
else:
|
|
|
|
ret = -1
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def get_target_config(self, proj_path):
|
|
|
|
property_file = os.path.join(proj_path, "project.properties")
|
|
|
|
if not os.path.isfile(property_file):
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_ERROR_FILE_NOT_FOUND_FMT', property_file),
|
|
|
|
cocos.CCPluginError.ERROR_PATH_NOT_FOUND)
|
|
|
|
|
|
|
|
patten = re.compile(r'^target=(.+)')
|
|
|
|
for line in open(property_file):
|
|
|
|
str1 = line.replace(' ', '')
|
|
|
|
str2 = str1.replace('\t', '')
|
|
|
|
match = patten.match(str2)
|
|
|
|
if match is not None:
|
|
|
|
target = match.group(1)
|
|
|
|
target_num = self.get_api_level(target)
|
|
|
|
if target_num > 0:
|
|
|
|
return target_num
|
|
|
|
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_ERROR_TARGET_NOT_FOUND_FMT', property_file),
|
|
|
|
cocos.CCPluginError.ERROR_PARSE_FILE)
|
|
|
|
|
|
|
|
# check the selected android platform
|
|
|
|
def check_android_platform(self, sdk_root, android_platform, proj_path):
|
|
|
|
ret = android_platform
|
|
|
|
if android_platform is None:
|
|
|
|
min_platform = self.get_target_config(proj_path)
|
|
|
|
# not specified platform, use the one in project.properties
|
|
|
|
ret = 'android-%d' % min_platform
|
|
|
|
|
|
|
|
ret_path = os.path.join(cocos.CMDRunner.convert_path_to_python(sdk_root), "platforms", ret)
|
|
|
|
if not os.path.isdir(ret_path):
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_ERROR_NO_AP_IN_SDK_FMT', ret),
|
|
|
|
cocos.CCPluginError.ERROR_PATH_NOT_FOUND)
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def gradle_build_apk(self, mode, android_platform, compile_obj):
|
|
|
|
# check the compileSdkVersion & buildToolsVersion
|
|
|
|
check_file = os.path.join(self.app_android_root, 'app', 'build.gradle')
|
|
|
|
f = open(check_file)
|
|
|
|
lines = f.readlines()
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
compile_sdk_ver = None
|
|
|
|
build_tools_ver = None
|
|
|
|
compile_sdk_pattern = r'compileSdkVersion[ \t]+([\d]+)'
|
|
|
|
build_tools_pattern = r'buildToolsVersion[ \t]+"(.+)"'
|
|
|
|
for line in lines:
|
|
|
|
line_str = line.strip()
|
|
|
|
match1 = re.match(compile_sdk_pattern, line_str)
|
|
|
|
if match1:
|
|
|
|
compile_sdk_ver = match1.group(1)
|
|
|
|
|
|
|
|
match2 = re.match(build_tools_pattern, line_str)
|
|
|
|
if match2:
|
|
|
|
build_tools_ver = match2.group(1)
|
|
|
|
|
|
|
|
if compile_sdk_ver is not None:
|
|
|
|
# check the compileSdkVersion
|
|
|
|
check_folder_name = 'android-%s' % compile_sdk_ver
|
|
|
|
check_path = os.path.join(self.sdk_root, 'platforms', check_folder_name)
|
|
|
|
if not os.path.isdir(check_path):
|
|
|
|
cocos.Logging.warning(MultiLanguage.get_string('COMPILE_WARNING_COMPILE_SDK_FMT',
|
|
|
|
(compile_sdk_ver, check_path)))
|
|
|
|
|
|
|
|
if build_tools_ver is not None:
|
|
|
|
# check the buildToolsVersion
|
|
|
|
check_path = os.path.join(self.sdk_root, 'build-tools', build_tools_ver)
|
|
|
|
if not os.path.isdir(check_path):
|
|
|
|
cocos.Logging.warning(MultiLanguage.get_string('COMPILE_WARNING_BUILD_TOOLS_FMT',
|
|
|
|
(build_tools_ver, check_path)))
|
|
|
|
|
|
|
|
# invoke gradlew for gradle building
|
|
|
|
if cocos.os_is_win32():
|
|
|
|
gradle_path = os.path.join(self.app_android_root, 'gradlew.bat')
|
|
|
|
else:
|
|
|
|
gradle_path = os.path.join(self.app_android_root, 'gradlew')
|
|
|
|
|
|
|
|
if not os.path.isfile(gradle_path):
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_ERROR_GRALEW_NOT_EXIST_FMT', gradle_path),
|
|
|
|
cocos.CCPluginError.ERROR_PATH_NOT_FOUND)
|
|
|
|
|
|
|
|
mode_str = 'Debug' if mode == 'debug' else 'Release'
|
|
|
|
cmd = '"%s" --parallel --info assemble%s' % (gradle_path, mode_str)
|
|
|
|
|
|
|
|
if self.gradle_support_ndk:
|
|
|
|
add_props = {
|
|
|
|
AndroidBuilder.GRADLE_PROP_BUILD_TYPE: self.build_type
|
|
|
|
}
|
|
|
|
if android_platform:
|
|
|
|
ret = self.check_android_platform(self.sdk_root, android_platform, None)
|
|
|
|
pattern = r'android-(\d+)'
|
|
|
|
match = re.match(pattern, ret)
|
|
|
|
if match:
|
|
|
|
add_props[AndroidBuilder.GRADLE_PROP_TARGET_VERSION] = (int)(match.group(1))
|
|
|
|
|
|
|
|
if self.app_abi:
|
|
|
|
add_props[AndroidBuilder.GRADLE_PROP_APP_ABI] = ':'.join(self.app_abi.split(' '))
|
|
|
|
|
|
|
|
if self._project._is_script_project():
|
|
|
|
if compile_obj._compile_script:
|
|
|
|
add_props[AndroidBuilder.GRADLE_PROP_COMPILE_SCRIPT] = 1
|
|
|
|
else:
|
|
|
|
add_props[AndroidBuilder.GRADLE_PROP_COMPILE_SCRIPT] = 0
|
|
|
|
|
|
|
|
if self._project._is_lua_project():
|
|
|
|
if compile_obj._lua_encrypt:
|
|
|
|
add_props[AndroidBuilder.GRADLE_PROP_LUA_ENCRYPT] = 1
|
|
|
|
add_props[AndroidBuilder.GRADLE_PROP_LUA_ENCRYPT_KEY] = compile_obj._lua_encrypt_key
|
|
|
|
add_props[AndroidBuilder.GRADLE_PROP_LUA_ENCRYPT_SIGN] = compile_obj._lua_encrypt_sign
|
|
|
|
|
|
|
|
for key in add_props.keys():
|
|
|
|
cmd += ' -P%s=%s' % (key, add_props[key])
|
|
|
|
|
|
|
|
self._run_cmd(cmd, cwd=self.app_android_root)
|
|
|
|
|
|
|
|
class LuaBuildArch:
|
|
|
|
UNKNOWN = -1
|
|
|
|
ONLY_BUILD_64BIT = 1
|
|
|
|
ONLY_BUILD_32BIT = 2
|
|
|
|
BUILD_32BIT_AND_64BIT = 3
|
|
|
|
|
|
|
|
def _do_get_build_arch(self, str):
|
|
|
|
# remove the '#' and the contents after it
|
|
|
|
str = str.split('#')[0]
|
|
|
|
build_64bit = str.find('arm64-v8a') != -1
|
|
|
|
|
|
|
|
# check if need to build other architecture
|
|
|
|
build_other_arch = False
|
|
|
|
other_archs = ('armeabi-v7a', 'x86') # other arches are not supported
|
|
|
|
for arch in other_archs:
|
|
|
|
if str.find(arch) != -1:
|
|
|
|
build_other_arch = True
|
|
|
|
break
|
|
|
|
|
|
|
|
if build_64bit or build_other_arch:
|
|
|
|
if build_64bit:
|
|
|
|
if build_other_arch:
|
2020-10-21 21:37:45 +08:00
|
|
|
print('build 64bit and 32bit')
|
2020-07-20 18:46:15 +08:00
|
|
|
return self.LuaBuildArch.BUILD_32BIT_AND_64BIT
|
|
|
|
else:
|
2020-10-21 21:37:45 +08:00
|
|
|
print('only build 64bit')
|
2020-07-20 18:46:15 +08:00
|
|
|
return self.LuaBuildArch.ONLY_BUILD_64BIT
|
|
|
|
else:
|
2020-10-21 21:37:45 +08:00
|
|
|
print('only build 32bit')
|
2020-07-20 18:46:15 +08:00
|
|
|
return self.LuaBuildArch.ONLY_BUILD_32BIT
|
|
|
|
|
|
|
|
return self.LuaBuildArch.UNKNOWN
|
|
|
|
|
|
|
|
# check if arm64-v8a is set in Application.mk
|
|
|
|
def _get_build_arch(self, param_of_appabi):
|
|
|
|
|
|
|
|
# get build type from parameter
|
|
|
|
if param_of_appabi:
|
|
|
|
return self._do_get_build_arch(param_of_appabi)
|
|
|
|
|
|
|
|
# get build type from Application.mk
|
|
|
|
applicationmk_path = os.path.join(self.app_android_root, "app/jni/Application.mk")
|
|
|
|
with open(applicationmk_path) as f:
|
|
|
|
for line in f:
|
|
|
|
if line.find('APP_ABI') == -1:
|
|
|
|
continue
|
|
|
|
build_arch = self._do_get_build_arch(line)
|
|
|
|
if build_arch != self.LuaBuildArch.UNKNOWN:
|
|
|
|
return build_arch
|
|
|
|
|
|
|
|
return self.LuaBuildArch.UNKNOWN
|
|
|
|
|
|
|
|
def do_build_apk(self, mode, no_apk, no_sign, output_dir, custom_step_args, android_platform, compile_obj):
|
|
|
|
assets_dir = os.path.join(self.app_android_root, "app", "assets")
|
|
|
|
project_name = None
|
|
|
|
setting_file = os.path.join(self.app_android_root, 'settings.gradle')
|
|
|
|
if os.path.isfile(setting_file):
|
|
|
|
# get project name from settings.gradle
|
|
|
|
f = open(setting_file)
|
|
|
|
lines = f.readlines()
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
pattern = r"project\(':(.*)'\)\.projectDir[ \t]*=[ \t]*new[ \t]*File\(settingsDir, 'app'\)"
|
|
|
|
for line in lines:
|
|
|
|
line_str = line.strip()
|
|
|
|
match = re.match(pattern, line_str)
|
|
|
|
if match:
|
|
|
|
project_name = match.group(1)
|
|
|
|
break
|
|
|
|
|
|
|
|
if project_name is None:
|
|
|
|
# use default project name
|
|
|
|
project_name = 'app'
|
|
|
|
gen_apk_folder = os.path.join(self.app_android_root, 'app/build/outputs/apk', mode)
|
|
|
|
|
|
|
|
# gradle supports copy assets & compile scripts from engine 3.15
|
|
|
|
if not self.gradle_support_ndk:
|
|
|
|
# copy resources
|
|
|
|
self._copy_resources(custom_step_args, assets_dir)
|
|
|
|
|
|
|
|
# check the project config & compile the script files
|
|
|
|
if self._project._is_lua_project():
|
|
|
|
src_dir = os.path.join(assets_dir, 'src')
|
|
|
|
build_arch = self._get_build_arch(compile_obj.app_abi)
|
|
|
|
|
|
|
|
# only build 64bit
|
|
|
|
if build_arch == self.LuaBuildArch.ONLY_BUILD_64BIT:
|
|
|
|
dst_dir = os.path.join(assets_dir, 'src/64bit')
|
|
|
|
is_compiled = compile_obj.compile_lua_scripts(src_dir, dst_dir, True)
|
|
|
|
if is_compiled:
|
|
|
|
# remove unneeded lua files
|
|
|
|
compile_obj._remove_file_with_ext(src_dir, '.lua')
|
|
|
|
shutil.rmtree(os.path.join(src_dir, 'cocos'))
|
|
|
|
|
|
|
|
# only build 32bit
|
|
|
|
if build_arch == self.LuaBuildArch.ONLY_BUILD_32BIT:
|
|
|
|
# build 32-bit bytecode
|
|
|
|
compile_obj.compile_lua_scripts(src_dir, src_dir, False)
|
|
|
|
|
|
|
|
# build 32bit and 64bit
|
|
|
|
if build_arch == self.LuaBuildArch.BUILD_32BIT_AND_64BIT:
|
|
|
|
# build 64-bit bytecode
|
|
|
|
dst_dir = os.path.join(assets_dir, 'src/64bit')
|
|
|
|
compile_obj.compile_lua_scripts(src_dir, dst_dir, True)
|
|
|
|
# build 32-bit bytecode
|
|
|
|
compile_obj.compile_lua_scripts(src_dir, src_dir, False)
|
|
|
|
|
|
|
|
if build_arch == self.LuaBuildArch.UNKNOWN:
|
|
|
|
# haven't set APP_ABI in parameter and Application.mk, default build 32bit
|
|
|
|
compile_obj.compile_lua_scripts(src_dir, src_dir, False)
|
|
|
|
|
|
|
|
if self._project._is_js_project():
|
|
|
|
compile_obj.compile_js_scripts(assets_dir, assets_dir)
|
|
|
|
|
|
|
|
if not no_apk:
|
|
|
|
# gather the sign info if necessary
|
|
|
|
if not no_sign:
|
|
|
|
if mode == "release" and not self.has_keystore_in_signprops():
|
|
|
|
self._gather_sign_info()
|
|
|
|
|
|
|
|
# build apk
|
|
|
|
self.gradle_build_apk(mode, android_platform, compile_obj)
|
|
|
|
|
|
|
|
# copy the apk to output dir
|
|
|
|
if output_dir:
|
|
|
|
# support generate unsigned apk
|
|
|
|
if mode == "release" and no_sign:
|
|
|
|
apk_name = '%s-%s-unsigned.apk' % (project_name, mode)
|
|
|
|
else:
|
|
|
|
apk_name = '%s-%s.apk' % (project_name, mode)
|
|
|
|
gen_apk_path = os.path.join(gen_apk_folder, apk_name)
|
|
|
|
if not os.path.exists(output_dir):
|
|
|
|
os.makedirs(output_dir)
|
|
|
|
shutil.copy(gen_apk_path, output_dir)
|
|
|
|
cocos.Logging.info(MultiLanguage.get_string('COMPILE_INFO_MOVE_APK_FMT', output_dir))
|
|
|
|
|
|
|
|
if mode == "release" and not no_sign:
|
|
|
|
signed_name = "%s-%s-signed.apk" % (project_name, mode)
|
|
|
|
apk_path = os.path.join(output_dir, signed_name)
|
|
|
|
if os.path.exists(apk_path):
|
|
|
|
os.remove(apk_path)
|
|
|
|
os.rename(os.path.join(output_dir, apk_name), apk_path)
|
|
|
|
else:
|
|
|
|
apk_path = os.path.join(output_dir, apk_name)
|
|
|
|
|
|
|
|
return apk_path
|
|
|
|
else:
|
|
|
|
raise cocos.CCPluginError(MultiLanguage.get_string('COMPILE_ERROR_NOT_SPECIFY_OUTPUT'),
|
|
|
|
cocos.CCPluginError.ERROR_WRONG_ARGS)
|
|
|
|
|
|
|
|
def _gather_sign_info(self):
|
|
|
|
user_cfg = {}
|
|
|
|
# get the path of keystore file
|
|
|
|
while True:
|
|
|
|
inputed = self._get_user_input(MultiLanguage.get_string('COMPILE_TIP_INPUT_KEYSTORE'))
|
|
|
|
inputed = inputed.strip()
|
|
|
|
if not os.path.isabs(inputed):
|
|
|
|
start_path = os.path.join(self.app_android_root, 'app')
|
|
|
|
abs_path = os.path.join(start_path, inputed)
|
|
|
|
else:
|
|
|
|
abs_path = inputed
|
|
|
|
|
|
|
|
if os.path.isfile(abs_path):
|
|
|
|
user_cfg[self.key_store_str] = inputed.replace('\\', '/')
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
cocos.Logging.warning(MultiLanguage.get_string('COMPILE_INFO_NOT_A_FILE'))
|
|
|
|
|
|
|
|
# get the alias of keystore file
|
|
|
|
user_cfg[self.key_alias_str] = self._get_user_input(MultiLanguage.get_string('COMPILE_TIP_INPUT_ALIAS'))
|
|
|
|
|
|
|
|
# get the keystore password
|
|
|
|
user_cfg[self.key_store_pass_str] = self._get_user_input(MultiLanguage.get_string('COMPILE_TIP_INPUT_KEY_PASS'))
|
|
|
|
|
|
|
|
# get the alias password
|
|
|
|
user_cfg[self.key_alias_pass_str] = self._get_user_input(MultiLanguage.get_string('COMPILE_TIP_INPUT_ALIAS_PASS'))
|
|
|
|
|
|
|
|
# write the config into ant.properties
|
|
|
|
self._write_sign_properties(user_cfg)
|
|
|
|
|
|
|
|
def _get_user_input(self, tip_msg):
|
|
|
|
cocos.Logging.warning(tip_msg)
|
|
|
|
ret = None
|
|
|
|
while True:
|
2020-10-21 21:37:45 +08:00
|
|
|
ret = cocos.get_input()
|
2020-07-20 18:46:15 +08:00
|
|
|
break
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def _copy_resources(self, custom_step_args, assets_dir):
|
|
|
|
app_android_root = self.app_android_root
|
|
|
|
res_files = self.res_files
|
|
|
|
|
|
|
|
# remove app_android_root/assets if it exists
|
|
|
|
if os.path.isdir(assets_dir):
|
|
|
|
shutil.rmtree(assets_dir)
|
|
|
|
|
|
|
|
# generate parameters for custom steps
|
|
|
|
target_platform = cocos_project.Platforms.ANDROID
|
|
|
|
cur_custom_step_args = custom_step_args.copy()
|
|
|
|
cur_custom_step_args["assets-dir"] = assets_dir
|
|
|
|
|
|
|
|
# make dir
|
|
|
|
os.mkdir(assets_dir)
|
|
|
|
|
|
|
|
# invoke custom step : pre copy assets
|
|
|
|
self._project.invoke_custom_step_script(cocos_project.Project.CUSTOM_STEP_PRE_COPY_ASSETS, target_platform, cur_custom_step_args)
|
|
|
|
|
|
|
|
# copy resources
|
|
|
|
for cfg in res_files:
|
|
|
|
cocos.copy_files_with_config(cfg, app_android_root, assets_dir)
|
|
|
|
|
|
|
|
# invoke custom step : post copy assets
|
|
|
|
self._project.invoke_custom_step_script(cocos_project.Project.CUSTOM_STEP_POST_COPY_ASSETS, target_platform, cur_custom_step_args)
|
|
|
|
|
|
|
|
def get_apk_info(self):
|
|
|
|
manifest_path = os.path.join(self.app_android_root, 'app')
|
|
|
|
gradle_cfg_path = os.path.join(manifest_path, 'build.gradle')
|
|
|
|
package = None
|
|
|
|
if os.path.isfile(gradle_cfg_path):
|
|
|
|
# get package name from build.gradle
|
|
|
|
f = open(gradle_cfg_path)
|
|
|
|
for line in f.readlines():
|
|
|
|
line_str = line.strip()
|
|
|
|
pattern = r'applicationId[ \t]+"(.*)"'
|
|
|
|
match = re.match(pattern, line_str)
|
|
|
|
if match:
|
|
|
|
package = match.group(1)
|
|
|
|
break
|
|
|
|
|
|
|
|
if package is None:
|
|
|
|
# get package name from AndroidManifest.xml
|
|
|
|
package = self._xml_attr(manifest_path, 'AndroidManifest.xml', 'manifest', 'package')
|
|
|
|
|
|
|
|
activity_name = self._xml_attr(manifest_path, 'AndroidManifest.xml', 'activity', 'android:name')
|
|
|
|
if activity_name.startswith('.'):
|
|
|
|
activity = package + activity_name
|
|
|
|
else:
|
|
|
|
activity = activity_name
|
|
|
|
ret = (package, activity)
|
|
|
|
|
|
|
|
return ret
|