import os
import re
import shutil
from enum import Enum
from termcolor import colored
from textops import grep, cat, echo, sed
from distutils.dir_util import copy_tree
from anadroid.build.SdkManagerWrapper import SDKManager
from anadroid.build.versionUpgrader import DefaultSemanticVersion
from anadroid.utils.Utils import mega_find, get_resources_dir, loge
RES_DIR = get_resources_dir()
GRADLE_RES_DIR = os.path.join(RES_DIR, "build", "gradle")
GRADLE_WRAPPER_DIR = os.path.join(GRADLE_RES_DIR, "wrapper", "gradle")
[docs]class KNOWN_ERROR(Enum):
"""Enumerates the list of known gradle build errors that possibly can be fixed the framework."""
GOOGLE_REPO_ERROR = "method google() for arguments"
LIBS_ERROR = "No signature of method: java.util.ArrayList.call() is applicable for argument types"
MIN_SDK_ERROR = "uses-sdk:minSdkVersion (.+) cannot be smaller than version (.+) declared in"
BUILD_SDK_ERROR = "The SDK Build Tools revision \((.+)\) is too low for project "
WRAPPER_ERROR = "try editing the distributionUrl"
WRAPPER_MISMATCH_ERROR = "Failed to notify project evaluation listener"
WRAPPER_PROP_ERROR = "Wrapper properties file"
NDK_TOOLCHAIN_ERROR = "NDK toolchains folder"
BUILD_TOOLS_CPU_ERROR = "Bad CPU type in executable"
NO_WRAPPER = "Could not find or load main class org.gradle.wrapper.GradleWrapperMain"
NO_GRADLEW_EXEC = "gradlew: No such file or directory"
NO_TARGET_PLATFORM = "failed to find target with hash string"
NO_BUILD_TOOLS = "failed to find Build Tools revision"
MAYBE_MISSING_GOOGLE_REPO = "Could not resolve all dependencies for configuration"
USER_HAS_TO_ACCEPT_INSTALL = "INSTALL_FAILED_USER_RESTRICTED"
NDK_BAD_CONFIG = "did not contain a valid NDK and couldn't be used"
#UNSUPPORTED_DEPENDENCY_VERSION = "The Android Gradle plugin supports only Butterknife Gradle plugin version 9.0.0-rc2 and higher."
[docs]def is_known_error(output):
"""Given the build output, checks if a known error occurred.
Returns:
object (obJ:`KNOW_ERROR`): the inferred error. If the error is not known, returns None.
"""
for error in KNOWN_ERROR:
is_this_error = error.value in output
if is_this_error:
return error
return None
[docs]def solve_known_error(proj, error, error_msg, **kwargs):
"""Tries to fix known build error.
Args:
proj(obj:`AndroidProject`): project that raised the error.
error(obj:`KNOWN_ERROR`):
error_msg(str): build output containing the error.
"""
if error == KNOWN_ERROR.WRAPPER_MISMATCH_ERROR:
#adjust gradle wrapper version
grad_prop_files = mega_find(proj.proj_dir, "gradle-wrapper.properties", type_file='f')
bld_gradle_files = proj.get_build_files()
for f in bld_gradle_files:
possible_plgin_vers = str(cat(f) | grep("com.android.tools.build:gradle*"))
has_plg_version = possible_plgin_vers != ""
if not has_plg_version:
continue
plg_version = possible_plgin_vers.split(":")[-1]
adeq_v = get_adequate_gradle_version(plg_version)
for fprop in grad_prop_files:
update_gradle_wrapper_version(fprop, adeq_v)
elif error == KNOWN_ERROR.NO_WRAPPER:
# no wrapper config
copy_tree(GRADLE_WRAPPER_DIR, os.path.join(proj.proj_dir, "gradle"))
elif error == KNOWN_ERROR.NO_GRADLEW_EXEC:
shutil.copytree(os.path.join(GRADLE_RES_DIR, "gradlew"), proj.proj_dir)
elif error == KNOWN_ERROR.NO_TARGET_PLATFORM:
# extract version -> use sdkmanager to download -> retry
output = kwargs.get("out") if "out" in kwargs else ""
current_compileSdkVersion = re.search("android-([0-9]+)",output).groups()[0]
sdkman = SDKManager()
sdkman.download_platform(current_compileSdkVersion)
elif error == KNOWN_ERROR.NO_BUILD_TOOLS:
# the build tools version being used was not downloaded
current_build_tools_version = kwargs.get('build-tools')
SDKManager().download_build_tools_version(current_build_tools_version)
elif error == KNOWN_ERROR.BUILD_TOOLS_CPU_ERROR:
# usually this error can be solved by upgrading BuildToolsVersion
sdkman = SDKManager()
current_build_tools_version = kwargs.get('build-tools')
available_build_tools_list = sdkman.get_list_of_available_build_tools()
new_vers = upgrade_version_from_version_list(available_build_tools_list, current_build_tools_version)
sdkman.download_build_tools_version(new_vers)
for bld_file in proj.get_build_files():
replace_build_tools_version(bld_file, current_build_tools_version, new_vers)
elif error == KNOWN_ERROR.MAYBE_MISSING_GOOGLE_REPO:
# add repository in main gradle file
main_grdl = proj.root_build_file
add_google_repo(main_grdl)
has_glu_glu = "google()" in str(cat(proj.root_build_file))
if not has_glu_glu:
add_google_repo_build_script(main_grdl)
elif error == KNOWN_ERROR.MIN_SDK_ERROR:
pass
x='''output = kwargs.get("out") if "out" in kwargs else ""
if output == "":
return
tha_manif_file = re.match(r"(.+?AndroidManifest\.xml)", output)
print(tha_manif_file.groups())
print(tha_manif_file)'''
elif error == KNOWN_ERROR.WRAPPER_ERROR:
# can be solved the same way of WRAPPER_MISMATCH_ERROR
recom_v = re.search("Minimum supported Gradle version is (.*). Current version", error_msg)
recom_v = f'{recom_v.groups()[0].strip()}-all' if recom_v else get_adequate_gradle_version(get_gradle_plugin_version(proj.root_build_file))
grad_prop_files = mega_find(proj.proj_dir, "gradle-wrapper.properties", type_file='f')
update_gradle_wrapper_version(grad_prop_files[0], recom_v )
#solve_known_error(proj, error=KNOWN_ERRORS.WRAPPER_MISMATCH_ERROR, **kwargs)
elif error == KNOWN_ERROR.NDK_BAD_CONFIG:
loge("BAD NDK configuration. please update ndk path in resources/config/local.properties")
local_Prop_file = os.path.join(RES_DIR, "config", "local.properties")
shutil.copy(local_Prop_file, proj.proj_dir)
elif error == KNOWN_ERROR.GOOGLE_REPO_ERROR:
# upgrade build tools (min at least 3.6)
min = DefaultSemanticVersion("3.6.3")
current_build_version = get_gradle_plugin_version(proj.root_build_file)
replace_gradle_plugin_version( proj.root_build_file, current_build_version, min)
solve_known_error(proj, KNOWN_ERROR.WRAPPER_MISMATCH_ERROR, error_msg, **kwargs)
#elif error == KNOWN_ERRORS.UNSUPPORTED_DEPENDENCY_VERSION:
# pass
else:
loge(f"Unable to solve {error}")
[docs]def get_adequate_gradle_version(plugin_version):
"""given the gradle plugin version, returns an adequate gradle version to match the plugin version.
Based on https://developer.android.com/studio/releases/gradle-plugin#updating-gradle table.
Args:
plugin_version: Gradle-plugin version.
Returns:
version: adequate gradle version.
"""
version = DefaultSemanticVersion(plugin_version)
if DefaultSemanticVersion("1.0.0") <= version <= DefaultSemanticVersion("1.1.3"):
return "2.3-all"
elif DefaultSemanticVersion("1.2.0") <= version <= DefaultSemanticVersion("1.3.1"):
return "2.9-all"
elif DefaultSemanticVersion("1.5.0") == version:
return "2.13-all"
elif DefaultSemanticVersion("2.0.0") <= version <= DefaultSemanticVersion("2.1.2"):
return "2.13-all"
elif DefaultSemanticVersion("2.1.3") <= version <= DefaultSemanticVersion("2.2.3"):
return "3.5-all"
elif DefaultSemanticVersion("2.3.0") <= version < DefaultSemanticVersion("3.0"):
return "3.3-all"
elif DefaultSemanticVersion("3.0.0") <= version < DefaultSemanticVersion("3.1.0"):
return "4.1-all"
elif DefaultSemanticVersion("3.1.0") <= version < DefaultSemanticVersion("3.2.0"):
return "4.4-all"
elif DefaultSemanticVersion("3.2.0") <= version <= DefaultSemanticVersion("3.2.1"):
return "4.6-all"
elif DefaultSemanticVersion("3.3.0") <= version <= DefaultSemanticVersion("3.3.3"):
return "4.10.1-all"
elif DefaultSemanticVersion("3.4.0") <= version <= DefaultSemanticVersion("3.4.3"):
return "5.1.1-all"
elif DefaultSemanticVersion("3.5.0") <= version <= DefaultSemanticVersion("3.5.4"):
return "5.4.1-all"
elif DefaultSemanticVersion("3.6.0") <= version <= DefaultSemanticVersion("3.6.4"):
return "5.6.4-all"
elif DefaultSemanticVersion("4.0.0") <= version < DefaultSemanticVersion("4.1.0"):
return "6.1.1-all"
elif DefaultSemanticVersion("4.2.0") <= version:
return "6.7.1-all"
[docs]def upgrade_version_from_version_list( version_list , upgradable_candidate, opt="max"):
if opt == "min":
list_of_bigger_patches = filter(lambda x: x.major == upgradable_candidate.major
and x.minor == upgradable_candidate.minor
and x.patch > upgradable_candidate.patch,
version_list)
final_l = sorted(list_of_bigger_patches, key=lambda x: x.patch, reverse=True)
if len(final_l) > 0:
return final_l[0]
else:
# try major first
list_of_bigger_majors = filter(lambda x: x.major > upgradable_candidate.major, version_list)
final_l = list(sorted(set(list_of_bigger_majors), key=lambda x: x.major))
if len(final_l) > 0:
return final_l[0]
# minor version
list_of_bigger_minors = filter(
lambda x: x.major == upgradable_candidate.major and x.minor > upgradable_candidate.minor, version_list)
final_l = sorted(list_of_bigger_minors, key=lambda x: x.minor, reverse=True)
if len(final_l) > 0:
return final_l[0]
if opt == "min":
# major version
list_of_bigger_majors = filter(lambda x: x.major > upgradable_candidate.major, version_list)
final_l = sorted(list_of_bigger_majors, key=lambda x: x.major)
if len(final_l) > 0:
return final_l[0]
else:
# try major first
list_of_bigger_patches = filter(lambda
x: x.major == upgradable_candidate.major and x.minor == upgradable_candidate.minor and x.patch > upgradable_candidate.patch,
version_list)
final_l = sorted(list_of_bigger_patches, key=lambda x: x.patch, reverse=True)
if len(final_l) > 0:
return final_l[0]
return upgradable_candidate
[docs]def replace_gradle_plugin_version(bld_file, old_v, new_v):
"""replace gradle plugin version on bld_file gradle file.
Args:
bld_file: path of build file.
old_v: old version.
new_v: new version.
"""
new_file = str(cat(bld_file)).replace(f'com.android.tools.build:gradle:{str(old_v)}', f'com.android.tools.build:gradle:{str(new_v)}')
with open(bld_file, 'w') as u:
u.write(new_file)
[docs]def add_google_repo(main_grdl):
"""Adds Google as repository to allprojects in main_grdl gradle file.
Args:
main_grdl: path of build file.
"""
file_ctent = str(cat(main_grdl))
new_file = file_ctent + "\nallprojects {repositories { maven { url 'https://maven.google.com/'\nname 'Google'}}}"
with open(main_grdl, 'w') as u:
u.write(new_file)
[docs]def add_google_repo_build_script(main_grdl):
"""Adds Google as repository to buildscript block in main_grdl file.
Args:
main_grdl: path of build file.
"""
file_ctent = str(cat(main_grdl))
new_file = file_ctent + "\nbuildscript {repositories { google() }}"
with open(main_grdl, 'w') as u:
u.write(new_file)
[docs]def update_gradle_wrapper_version(gradle_wrap_prop_filepath, new_v):
"""updates gradle wrapper version in properties file.
Args:
gradle_wrap_prop_filepath: properties file path.
new_v: new version.
"""
fl_ctnt = file_content = str(cat(gradle_wrap_prop_filepath))
current_v = str(echo(fl_ctnt)
| grep("distributionUrl=.*")).split("/")[-1].replace("gradle-", "").replace(".zip", "")
file_content = re.sub(current_v, new_v, file_content)
with open(gradle_wrap_prop_filepath, 'w') as u:
u.write(file_content)
[docs]def get_gradle_plugin_version(gradle_file):
"""returns gradle plugin version.
gets value of com.android.tools.build contained in gradle_file.
Args:
gradle_fileU (str): build.gradle filepath.
Returns:
gradle_plugin_version (str): plugin version.
"""
gradle_plugin_version = str(cat(gradle_file) | grep("com.android.tools.build")
| sed("classpath|com.android.tools.build:gradle:|\"", "")).strip().replace("'","")
return gradle_plugin_version