import os
from shutil import copy
from textops import grep, cat, sed, head, echo
import json
import re
from anadroid.application.AndroidProject import BUILD_TYPE
from anadroid.application.Application import App
from anadroid.application.Dependency import DependencyType
from anadroid.build.AbstractBuilder import AbstractBuilder
from anadroid.build.versionUpgrader import DefaultSemanticVersion, can_be_semantic_version
from anadroid.device.MockedDevice import MockedDevice
from anadroid.utils.Utils import mega_find, execute_shell_command, sign_apk, log_to_file, loge, logw, logs, logi
from anadroid.build.GracleBuildErrorSolver import is_known_error, solve_known_error
TRANSITIVE = "implementation"
TEST_TRANSITIVE = "testImplementation"
ANDROID_TEST_TRANSITIVE = "androidTestImplementation"
DEBUG_TRANSITIVE = "debugImplementation"
TEST_RUNNERS = {
'android.test.InstrumentationTestRunner': 22,
'android.support.test.runner.AndroidJUnitRunner': 28,
'androidx.test.runner.AndroidJUnitRunner': 32
}
LOCAL_PROPERTIES_FLAGS = {
'android.enableBuildCache': 'true',
'org.gradle.caching': 'true',
}
LINT_ISSUES = {
'Recycle', 'Wakelock', 'DrawAllocation',
'ObsoleteLayoutParam', 'ViewHolder'}
DEX_OPTIONS = {
'preDexLibraries': 'false',
'javaMaxHeapSize': '"8g"'
}
LINT_OPTIONS = {
'abortOnError': 'false',
}
ATTRIBS_NAME_REMAPS = {
'packageName': 'applicationId',
'testPackageName': 'testApplicationId',
'packageNameSuffix': 'applicationIdSuffix',
'android.plugin.bootClasspath': 'android.bootClasspath',
'android.plugin.ndkFolder': 'android.plugin.ndkDirectory ',
'zipAlign': 'zipAlignEnabled',
'jniDebugBuild': 'jniDebuggable',
'renderscriptDebug': 'renderscriptDebuggable',
'flavorGroups': 'flavorDimensions',
'renderscriptSupportMode': 'renderscriptSupportModeEnabled',
'ProductFlavor.renderscriptNdkMode': 'renderscriptNdkModeEnabled',
'InstrumentTest': 'androidTest',
'instrumentTestCompile': 'jniDebuggable',
}
BUILD_RESULTS_FILE = "buildStatus.json"
SUCCESS_VALUE = "Success"
ERROR_VALUE = "Error"
BUILD_SUCCESS_VALUE = "BUILD SUCCESSFUL"
DEFAULT_BUILD_TIMES_TO_TRY = 5
DEFAULT_BUILD_TOOLS_VERSION = '25.0.3'# TODO
[docs]def gen_dependency_string(dependency):
"""generates dependency format to be inserted in gradle files.
Args:
dependency(obj: `BuildDependency`): dependency.
Returns:
dependency_string(str): dependency format as string.
"""
if dependency.dep_type == DependencyType.LOCAL_BINARY:
return """{transitive} (name:'{name}', ext:'{tp}')""".format(transitive=TRANSITIVE, name=dependency.name,
tp=dependency.bin_type)
elif dependency.dep_type == DependencyType.LOCAL_MODULE:
return "{transitive} project(:{name})".format(transitive=TRANSITIVE, name=dependency.name)
elif dependency.dep_type == DependencyType.CLASSPATH:
return "classpath '{name}".format(name=dependency.name) \
+ ":{version}'".format(version=dependency.version) if dependency.version is not None else "'"
else:
return "{transitive} '{name}".format(transitive=TRANSITIVE, name=dependency.name) \
+ ":{version}'".format(version=dependency.version) if dependency.version is not None else "'"
[docs]def set_transitive_names(gradle_plugin_version):
"""Given the gradle-plugin version, sets the most adequate nomenclature to use in dependencies definition.
Args:
gradle_plugin_version: gradle-plugin version.
"""
x = DefaultSemanticVersion(str(gradle_plugin_version))
if x.major < 3:
global TRANSITIVE
TRANSITIVE = "compile"
global TEST_TRANSITIVE
TEST_TRANSITIVE = "testCompile"
global ANDROID_TEST_TRANSITIVE
ANDROID_TEST_TRANSITIVE = "AndroidTestCompile"
global DEBUG_TRANSITIVE
DEBUG_TRANSITIVE = "debugCompile"
# TODO api vs implementation vs compilonly ... https://developer.android.com/studio/build/dependencies#dependency_configurations
[docs]def is_library_gradle(bld_file):
"""checks if bld_file is a build file from a library.
Returns:
bool: True if is a library, False otherwise.
"""
return 'com.android.library' in str(cat(bld_file))
[docs]class GradleBuilder(AbstractBuilder):
"""Class that extends AbstractBuilder functionality in order to build Gradle projects.
Attributes:
build_flags(dict): build flags.
change_history(list): list of changes performed to project.
gradle_plg_version(str): Gradle plugin version for proj.
build_tools_version(str): Gradle version for proj.
"""
def __init__(self, proj, device, resources_dir, instrumenter):
super(GradleBuilder, self).__init__(proj, device, resources_dir, instrumenter)
self.build_flags = {}
self.change_history = []
self.gradle_plg_version = proj.get_gradle_plugin()
self.build_tools_version = None
set_transitive_names(self.gradle_plg_version)
self.retry_on_fail = self.get_config("retry_failed", True)
[docs] def build_proj_and_apk(self, build_type=BUILD_TYPE.DEBUG, build_tests_apk=False, rebuild=False):
"""builds project and generates apk of build type. It can optionally build the tests apk and/or rebuild
the current project in case it was already built.
Args:
build_type(BUILD_TYPE): type of build to perform.
build_tests_apk(bool): True if the tests' apk has to be generated, False otherwise.
rebuild(bool): True if the current build has to be cleaned and rebuilt, False otherwise.
Returns:
bool: build results.
"""
res = self.build(rebuild=rebuild) # might fail because of release only tasks
if build_type == BUILD_TYPE.RELEASE:
return res and self.build_apk(build_type=build_type) \
and self.proj.set_version(build_type) is None \
and (True if not build_tests_apk else self.build_tests_apk())
return self.build_apk(build_type=build_type) \
and self.proj.set_version(build_type) is None \
and (True if not build_tests_apk else self.build_tests_apk())
[docs] def install_apks(self, build_type=BUILD_TYPE.DEBUG, install_apk_test=False):
"""install apk of build_type and optionally de tests apk.
Args:
build_type(BUILD_TYPE): build type.
install_apk_test(bool): True if the tests' apk has to be installed, False otherwise.
Returns:
apps_list(list): list of installed apks.
"""
# TODO background installer
apps_list = []
task_name = "install" + build_type.value
val = self.__execute_gradlew_task(task_name)
was_success = re.search(BUILD_SUCCESS_VALUE, val)
if was_success:
logs(f"{task_name}: SUCCESSFUL")
app = self.create_app_from_installed_apk(val, build_type)
apps_list.append(app)
filename = os.path.join(self.proj.proj_dir, "{task}_{results}.log".format(task=task_name, results=(
"SUCCESS" if was_success else "ERROR")))
log_to_file(content=val, filename=filename)
if install_apk_test:
task_name = f"install{build_type.value}AndroidTest"
val = self.__execute_gradlew_task(task_name)
was_success = re.search(BUILD_SUCCESS_VALUE, val)
if was_success:
logs(f"{task_name}: SUCCESSFUL")
filename = os.path.join(self.proj.proj_dir, "{task}_{results}.log".format(task=task_name, results=(
"SUCCESS" if was_success else "ERROR")))
log_to_file(content=val, filename=filename)
return apps_list
[docs] def uninstall_all_apks(self):
"""uninstall all project apks."""
if isinstance(self.device, MockedDevice):
return
task_name = "uninstallAll"
self.__execute_gradlew_task(task_name)
[docs] def create_app_from_installed_apk(self, gradle_output, build_type):
"""create App object from installed apk on device.
Args:
gradle_output: build output.
build_type: build type.
Returns:
app(App): created app.
"""
installed_apk_simple_name = re.search(r"Installing APK \'(.*?)\'", gradle_output).groups()[0]
full_apk_path = next(
filter(lambda x: str(x).endswith(installed_apk_simple_name), self.proj.get_apks(build_type=build_type)),
self.proj.get_apks(build_type=build_type)[0])
new_pkgs = self.device.get_new_installed_pkgs()
if len(new_pkgs) == 0:
# app was already installed
logi("app already installed")
app_pack = self.device.get_package_matching(self.proj.pkg_name)
new_pkgs.append(app_pack)
apk_pkg = new_pkgs[-1] # ASSUMING JUST ONE
app = App(self.device, self.proj, apk_pkg, apk_path=full_apk_path, local_res_dir=self.proj.results_dir)
return app
[docs] def sign_apks(self, build_type=BUILD_TYPE.DEBUG):
"""Sign project apks of build_type.
Args:
build_type: build type.
Returns:
bool: True if success, False otherwise.
"""
if self.was_last_build_successful(task="sign") or build_type == BUILD_TYPE.DEBUG:
return True
for apk_path in self.proj.get_apks(build_type):
ret, o, e = sign_apk(apk_path)
if ret == 0 and len(e) < 3:
logs("APK Successfully signed")
return True
else:
loge(f"Error signing apk {apk_path} {e}")
print(e)
return False
[docs] def build_apk(self, build_type=BUILD_TYPE.DEBUG):
"""Build apk of build_type.
Args:
build_type: build type.
Returns:
bool: True if success, False otherwise.
"""
task = f"assemble{build_type.value}"
if self.was_last_build_successful(task) and not self.needs_rebuild():
logi(f"Not building again {build_type}. Last build was successful")
return True
apks_built = self.proj.get_apks()
val = self.__execute_gradlew_task(task=task)
was_success = BUILD_SUCCESS_VALUE in val
if was_success:
logs(f"BUILD ({build_type.value}) SUCCESSFUL")
self.regist_successful_build(task)
apks_now = self.proj.get_apks(build_type=build_type)
fresh_apks = [x for x in apks_now if x not in apks_built]
for apk in fresh_apks:
if build_type == BUILD_TYPE.RELEASE:
sign_apk(apk)
self.proj.add_apk(apk, build_type)
else:
loge("Error Building APK")
self.regist_error_build(task)
return False
return True
[docs] def build_tests_apk(self):
"""builds tests apk.
Returns:
bool: True if success, False otherwise.
"""
task = "assembleAndroidTest"
if self.was_last_build_successful(task) and not self.needs_rebuild():
logw("Not building again. Last build was successful")
return True
apks_built = self.proj.get_apks()
val = self.__execute_gradlew_task(task=task)
was_success = str(val | grep(BUILD_SUCCESS_VALUE)) != ""
if was_success:
logs(f"{task}: SUCCESSFUL")
self.regist_successful_build(task)
apks_now = self.proj.get_apks()
apks_test = [x for x in apks_now if x not in apks_built]
for apks_test in apks_test:
self.proj.add_apk(apks_test, None)
else:
loge("Error Building test APK")
self.regist_error_build(task)
return False
return True
[docs] def build(self, rebuild=False):
"""builds project if project is not build yet or rebuild is True.
Args:
rebuild: True if the project has to be rebuilt.
Returns:
bool: True if success, False otherwise.
"""
build_was_succcessful = self.was_last_build_successful()
if build_was_succcessful and not rebuild:
logi("Not building again. Last build was successful")
return True
if not self.retry_on_fail and not build_was_succcessful and self.was_attempted_to_build_before():
logw("Skipping failed build. Retry on failed flag is disabled")
return True
self.__execute_gradlew_task("clean") # mainly to ensure that built apks from original proj do not persist
for mod_name, proj_module in self.proj.modules.items():
bld_file = proj_module.build_file
self.__set_build_tools_version(bld_file)
self.__add_or_update_dexoptions(bld_file)
self.__add_or_update_lintoptions(bld_file)
self.__add_plugins(bld_file)
self.needs_min_sdk_upgrade(bld_file)
self.adapt_target_sdk_upgrade(bld_file)
self.__update_test_instrumentation_runner(bld_file)
self.__add_external_libs_fldr(proj_module)
self.__add_external_libs(proj_module)
# maybe change build tools?
self.__add_build_classpaths()
self.__add_external_libs_to_repositories()
self.__add_or_replace_local_properties_files()
return self.build_with_gradlew(self.get_config("build_fail_retries", DEFAULT_BUILD_TIMES_TO_TRY), skip_lint=True)
[docs] def build_with_gradlew(self, tries=DEFAULT_BUILD_TIMES_TO_TRY, target_task="build", skip_lint=False):
"""performs build task with gradle.
Args:
tries: number max of tries to fix build errors.
target_task: task to perform.
Returns:
bool: True if success, False otherwise.
"""
has_gradle_wrapper = self.proj.has_gradle_wrapper()
if not has_gradle_wrapper:
# create gradle wrapper
copy(os.path.join(self.resources_dir, "build", "gradle", "gradlew"), self.proj.proj_dir)
lint_option_cmd = " -x lint" if skip_lint else ""
val = self.__execute_gradlew_task(target_task + lint_option_cmd + " -x test")
was_success = BUILD_SUCCESS_VALUE in val
if was_success:
logs(f"{target_task}: BUILD SUCCESSFUL")
self.regist_successful_build(target_task)
return True
else:
error = is_known_error(val)
if error is not None and tries > 0:
solve_known_error(self.proj, error, error_msg=val, **{'build-tools': self.build_tools_version})
loge(f"{target_task}: BUILD FAILED. error is known ({error}). Fixing error and retrying")
log_to_file(f"{error}", os.path.join(self.proj.proj_dir, "registered_errors.log"))
return self.build_with_gradlew(tries=tries - 1, target_task=target_task)
else:
print(val)
loge("Unable to solve Building error")
self.regist_error_build(target_task)
log_to_file(f"{val}\n-------", os.path.join(self.proj.proj_dir, "unknown_errors.log"))
return False
def __execute_gradlew_task(self, task):
"""execute gradle task with gradle wrapper.
Args:
task: task name.
Returns:
str: command output.
"""
logi(f"Executing Gradle task: {task}")
build_timeout_val = self.get_config("build_timeout", None)
build_timeout_val = None if build_timeout_val == 0 else build_timeout_val
#build_timeout = f'gtimeout -s 9 {build_timeout_val}' if build_timeout_val > 0 else ""
#print(build_timeout)
cmd = "cd {projdir}; chmod +x gradlew ; ./gradlew {task}".format(
projdir=self.proj.proj_dir, task=task, build_timeout=build_timeout_val)
res = execute_shell_command(cmd, timeout=build_timeout_val)
if res.validate(f"error running gradle task ({task})"):
return res.output
else:
return res.errors
[docs] def needs_min_sdk_upgrade(self, gradle_file):
"""infers if module needs min sdk upgrade in order to the app be installed on the current device.
Args:
gradle_file: gradle file.
"""
has_min_sdk = cat(gradle_file) | grep(r'minSdkVersion.*[0-9]+')
if str(has_min_sdk) != "":
min_sdk = has_min_sdk | sed('minSdkVersion| |=|\n', "") | head(1)
device_sdk_version = self.device.get_device_sdk_version()
if int(str(min_sdk)) > device_sdk_version:
logw(f"This app target sdk version {min_sdk}. This is greater than the device version and the application"
f" might not work properly on the connected device")
new_file = re.sub(r'minSdkVersion (.+)', r'minSdkVersion %d' % device_sdk_version,
str(cat(gradle_file)))
with open(gradle_file, 'w') as u:
u.write(new_file)
return True
return False
[docs] def adapt_target_sdk_upgrade(self, gradle_file):
"""Adapts target sdk upgrade to connected device if needed.
Args:
gradle_file: gradle file.
"""
has_target_sdk = cat(gradle_file) | grep(r'targetSdkVersion.*[0-9]+')
if str(has_target_sdk) != "":
device_target_sdk_version = self.device.get_device_sdk_version()
new_file = re.sub(r'targetSdkVersion (.+)', r'targetSdkVersion %d' % device_target_sdk_version,
str(cat(gradle_file)))
with open(gradle_file, 'w') as u:
u.write(new_file)
return True
return False
def __add_or_update_dexoptions(self, gradle_file):
"""Adds dexoptions to build file if needed.
Args:
gradle_file: gradle file.
"""
new_dex_opts = {}
file_ctent = str(cat(gradle_file))
#has_android = re.search(r'android.*?\{', file_ctent)
has_android = re.search(r'android[^\w]*?\{', file_ctent)
if has_android is None:
return
has_dex_options = re.search(r'dexOptions.*?\{', file_ctent)
if has_dex_options is not None:
original_dex_options = re.search(r'dexOptions.*?\{([^}]+)', file_ctent).groups()[0].strip().split("\n")
new_dex_opts = self.__filter_opts(original_dex_options, DEX_OPTIONS.keys())
for dx, dval in DEX_OPTIONS.items():
new_dex_opts[dx] = (" ", dval)
else:
for dx, dval in DEX_OPTIONS.items():
new_dex_opts[dx] = (" ", dval)
# build string for adding to dexoptions
line = ""
for a, b in new_dex_opts.items():
line += "\t\t%s%s%s\n" % (a, b[0], b[1])
line = "android {\n\tdexOptions {\n%s\t}" % line
fl_without_dexopts = re.sub(r'dexOptions.*?\{([^{}]+)}', "", file_ctent)
fl_ok = re.sub(r'android[^\w]*?\{', line, fl_without_dexopts)
with open(gradle_file, 'w') as u:
u.write(fl_ok)
def __add_or_update_lintoptions(self, gradle_file):
"""Adds lint options.
"""
new_lint_opts = {}
file_ctent = str(cat(gradle_file))
has_android = re.search(r'android[^\w]*?\{', file_ctent)
if has_android is None:
return
has_lint_options = re.search(r'lintOptions.*?\{', file_ctent)
if has_lint_options is not None:
original_lint_options = re.search(r'lintOptions.*?\{([^{}]+)}', file_ctent).groups()[0].strip().split("\n")
new_lint_opts = self.__filter_opts(original_lint_options, LINT_OPTIONS.keys())
for dx, dval in LINT_OPTIONS.items():
new_lint_opts[dx] = (" ", dval)
else:
for dx, dval in LINT_OPTIONS.items():
new_lint_opts[dx] = (" ", dval)
# build string for adding to dexoptions
line = ""
for a, b in new_lint_opts.items():
line += "\t\t%s%s%s\n" % (a, b[0], b[1])
line = "android {\n\tlintOptions {\n%s\t}" % line
fl_without_dexopts = re.sub(r'lintOptions.*?\{([^{}]+)}', "", file_ctent)
fl_ok = re.sub(r'android[^\w]*?\{', line, fl_without_dexopts)
with open(gradle_file, 'w') as u:
u.write(fl_ok)
def __update_test_instrumentation_runner(self, gradle_file):
"""Updates test runner according to device sdk version.
Args:
gradle_file: gradle file
"""
file_ctent = str(cat(gradle_file))
has_inst_runner = re.search(r'testInstrumentationRunner', file_ctent)
if has_inst_runner is None:
return
contains_androidx_dependency = re.search(r'androidx\.test\.*', file_ctent)
device_sdk = self.device.get_device_sdk_version() if contains_androidx_dependency is None else 100
adequate_test_runner = list(filter(lambda x: x[1] <= device_sdk, TEST_RUNNERS.items()))[-1]
new_file = re.sub(r"testInstrumentationRunner (.+)",
r"testInstrumentationRunner '%s'" % adequate_test_runner[0], file_ctent)
with open(gradle_file, 'w') as u:
u.write(new_file)
def __filter_opts(self, opts, excluding_set):
to_keep = {}
try:
for dex_op in opts:
opt_tpls = re.search(r'(.*?)(=|\s|:)([^{}]+)', dex_op.strip()).groups()
opt_key = opt_tpls[0].strip()
opt_oper = opt_tpls[1]
opt_val = opt_tpls[2].strip()
if opt_key not in excluding_set:
to_keep[opt_key] = (opt_oper, opt_val)
else:
self.change_history.append("Removed or replaced opt %s" % opt_key)
except:
loge("error filtering opts in gradle file")
return opts
return to_keep
def __add_or_replace_local_properties_files(self):
"""Update or create local.properties file.
"""
local_props_files = mega_find(self.proj.proj_dir, pattern="local.properties", maxdepth=3)
custom_prop_file_ctent = self.build_local_properties_file()
for loc_prop in local_props_files:
with open(loc_prop, 'w') as u:
u.write(custom_prop_file_ctent)
u.close()
[docs] def build_local_properties_file(self):
"""builds local.properties file content.
Returns:
str: file content.
"""
return '''
sdk.dir={android_home}
sdk-location={android_home}
ndk.dir={android_home}/ndk-bundle
ndk-location={android_home}/ndk-bundle''' \
.format(android_home=self.android_home_dir)
def __add_external_libs_to_repositories(self):
if self.needs_external_lib_dependency():
file_ctent = str(cat(self.proj.root_build_file))
new_file = file_ctent + "\nallprojects {repositories {flatDir { dirs 'libs'}}}"
with open(self.proj.root_build_file, 'w') as u:
u.write(new_file)
def __add_external_libs_fldr(self, proj_module):
"""adds folder to include local 3rd party libs.
Args:
proj_module(obj:ProjectModule): project module.
"""
bld_file = proj_module.build_file
if bld_file == self.proj.root_build_file:
return
if self.instrumenter.profiler.needs_external_dependencies():
# create folder
created_dir = proj_module.create_inner_folder(name="libs")
# copy external lib to folder
lib_filepath = self.instrumenter.profiler.local_dep_location
copy(lib_filepath, created_dir)
def __add_external_libs(self, proj_module):
"""adds dependencies to module.
Args:
proj_module(obj:ProjectModule): project module.
"""
bld_file = proj_module.build_file
if not self.needs_external_lib_dependency():
return
file_ctent = str(cat(bld_file))
dependencies = self.instrumenter.get_build_dependencies()
#has_depts = re.search(r'dependencies.*?\{(.|\n)*}', file_ctent).group(0)
new_deps="\ndependencies{\n\t"
for n_dp in dependencies:
new_deps += gen_dependency_string(n_dp) + "\n\t"
new_file_ctent = file_ctent + new_deps + "}\n"
with open(bld_file, 'w') as u:
u.write(new_file_ctent)
[docs] def needs_external_lib_dependency(self):
"""needs build dependencies from instrumenter.
Returns:
bool: True if needs, False otherwise.
"""
return self.instrumenter.needs_build_dependency()
[docs] def was_last_build_successful(self, task="build"):
"""checks if last build attempt was successful.
inspects BUILD_RESULTS_FILE file and checks build result.
Returns:
bool: True if build was successful, False otherwise.
"""
filename = os.path.join(self.proj.proj_dir, BUILD_RESULTS_FILE)
if os.path.exists(filename):
with open(filename, 'r') as fl:
js = json.load(fl)
return js[task].lower() == SUCCESS_VALUE.lower() if task in js else False
return False
[docs] def regist_successful_build(self, task="build"):
"""record successful build in file.
Args:
task: build task name.
"""
filename = os.path.join(self.proj.proj_dir, BUILD_RESULTS_FILE)
js = {}
if os.path.exists(filename):
with open(filename, 'r') as fl:
js = json.load(fl)
js[task] = SUCCESS_VALUE
with open(filename, 'w') as outfile:
json.dump(js, outfile)
[docs] def regist_error_build(self, task="build"):
"""record successful build in file.
Args:
task: build task name.
"""
filename = os.path.join(self.proj.proj_dir, BUILD_RESULTS_FILE)
js = {}
if os.path.exists(filename):
with open(filename, 'r') as fl:
js = json.load(fl)
js[task] = ERROR_VALUE
with open(filename, 'w') as outfile:
json.dump(js, outfile)
def __set_build_tools_version(self, bld_file, btools_version=DEFAULT_BUILD_TOOLS_VERSION):
"""sets build tools version btools_version on bld_file.
Args:
bld_file: build file.
btools_version: version.
"""
has_bld_tools = str((cat(bld_file) | grep("buildToolsVersion") | sed("buildToolsVersion|\"", ""))).strip()
if has_bld_tools != "":
if can_be_semantic_version(has_bld_tools):
self.build_tools_version = DefaultSemanticVersion(has_bld_tools)
return
elif "ext." in has_bld_tools:
var_name = has_bld_tools.split("ext.")[-1]
target_pattern1, target_pattern2 = r'ext.*?\{', r'%s.*?=.*' % var_name
x = list(filter(lambda t: re.search(target_pattern1, str(cat(t))) and re.search(target_pattern2, str(cat(t))), mega_find(self.proj.proj_dir, "*.gradle", type_file='f')))
if len(x) > 0:
# assume 0
pattern = re.sub(r'\"|\'', "", re.search(target_pattern2, str(cat(x[0]))).group().split("=")[1].strip())
if can_be_semantic_version(pattern):
self.build_tools_version = DefaultSemanticVersion(pattern)
return
else:
logw(f"unable to determinate build tools version. Using default: {btools_version}")
self.build_tools_version = DefaultSemanticVersion(btools_version)
def __add_plugins(self, bld_file):
"""adds build plugins if needed
Args:
bld_file: build file.
"""
if not self.instrumenter.needs_build_plugin():
return
file_content = str(cat(bld_file))
plugins = self.instrumenter.get_build_plugins()
if len(plugins) > 0 and plugins[0] in file_content:
return
has_plugin_apply = re.search(r'apply.*plugin.*', file_content)
plg_string = ""
if has_plugin_apply:
# replace
# plgs = echo(file_content) | grep(r'apply.*plugin')
# it can't be the first plugin
plgs = re.findall(r'apply.*plugin.*', file_content)
for plg in plugins:
if 'hunter' in plg and is_library_gradle(bld_file):
continue
plg_string += f"apply plugin: '{plg}'\n"
# file_content = re.sub(plgs, (plg_string + plgs), file_content)
last_plg = plgs[len(plgs) - 1]
new_plgs = last_plg + "\n" + plg_string
file_content = file_content.replace(last_plg, new_plgs)
else:
has_plugins = re.search(r'plugins.*\{', file_content)
if has_plugins:
original_plugs = re.search(r'plugins.*?\{([^{}]+)}', file_content).groups()[0]
for plg in plugins:
plg_string += f"\tid '{plg}'\n"
file_content = re.sub(original_plugs, original_plugs + plg_string, file_content)
else:
# append at beginning
for plg in plugins:
plg_string += f"apply plugin: '{plg}'\n"
file_content = plg_string + file_content
with open(bld_file, 'w') as u:
u.write(file_content)
def __add_build_classpaths(self):
"""adds build dependencies of the instrumenter if needed."""
if not self.instrumenter.needs_build_classpaths():
return
file_ctent = str(cat(self.proj.root_build_file))
# TODO: more elegant way to do this, instead of just appending to build file
classpaths = self.instrumenter.get_build_classpaths()
pato_str = "buildscript{\n\tdependencies{\n"
for pato in classpaths:
pato_str += gen_dependency_string(pato) + "\n"
new_file_ctnt = file_ctent + "\n" + pato_str + "\n}\n}"
with open(self.proj.root_build_file, 'w') as u:
u.write(new_file_ctnt)
def __has_built_apks(self):
"""checks if project has apks already built.
Returns:
bool: True if has, False otherwise.
"""
return len(mega_find(self.proj.proj_dir, pattern="*.apk", type_file='f')) > 0
[docs] def needs_rebuild(self):
"""checks if project needs rebuild.
Returns:
bool: True if needs, False otherwise.
"""
return not self.__has_built_apks() # TODO check build type and maybe last build output, lint, etc
[docs] def was_attempted_to_build_before(self):
"""checks if there was a build attempt before.
Returns:
bool: True if yes, False otherwise.
"""
filename = os.path.join(self.proj.proj_dir, BUILD_RESULTS_FILE)
return os.path.exists(filename)