import java.nio.file.Files import java.nio.file.Paths import java.util.regex.Pattern import java.util.stream.Stream class VersionComparator implements Comparator { static private final List SNAPSHOT_SUFFIXES = ["-SNAPSHOT", ".BUILD-SNAPSHOT"].asImmutable() int compare(String o1, String o2) { int result = 0 if (o1 == '*') { result = 1 } else if (o2 == '*') { result = -1 } else { def nums1 try { def tokens = deSnapshot(o1).split(/\./) tokens = tokens.findAll { String it -> it.trim() ==~ /\d+/ } nums1 = tokens*.toInteger() } catch (NumberFormatException e) { throw new Exception("Cannot compare versions, left side [$o1] is invalid: ${e.message}") } def nums2 try { def tokens = deSnapshot(o2).split(/\./) tokens = tokens.findAll { String it -> it.trim() ==~ /\d+/ } nums2 = tokens*.toInteger() } catch (NumberFormatException e) { throw new Exception("Cannot compare versions, right side [$o2] is invalid: ${e.message}") } boolean bigRight = nums2.size() > nums1.size() boolean bigLeft = nums1.size() > nums2.size() for (int i in 0.. i) { result = nums1[i].compareTo(nums2[i]) if (result != 0) { break } if (i == (nums1.size()-1) && bigRight) { if (nums2[i+1] != 0) result = -1; break } } else if (bigLeft) { if (nums1[i] != 0) result = 1; break } } } if (result == 0) { // Versions are equal, but one may be a snapshot. // A snapshot version is considered less than a non snapshot version def o1IsSnapshot = isSnapshot(o1) def o2IsSnapshot = isSnapshot(o2) if (o1IsSnapshot && !o2IsSnapshot) { result = -1 } else if (!o1IsSnapshot && o2IsSnapshot) { result = 1 } } result } boolean equals(obj) { false } /** * Removes any suffixes that indicate that the version is a kind of snapshot */ protected String deSnapshot(String version) { String suffix = SNAPSHOT_SUFFIXES.find { String it -> version?.endsWith(it) } if (suffix) { return version[0..-(suffix.size() + 1)] } else { return version } } protected boolean isSnapshot(String version) { SNAPSHOT_SUFFIXES.any { String it -> version?.endsWith(it) } } } class axutils { static String[] findNativeBuildTools(project, ndkVer = "23.2.8568313+", cmakeVer = "3.22.1+") { // Detecting sdkRoot def sdkRoot = null Properties localProps = new Properties() try { localProps.load(project.rootProject.file("local.properties").newDataInputStream()) sdkRoot = localProps.getProperty("sdk.dir") } catch(Exception ex) { ex.printStackTrace() } if (sdkRoot == null || !new File(sdkRoot).isDirectory()) { sdkRoot = Paths.get("${System.env.ANDROID_SDK_ROOT}").toAbsolutePath().toString() if (!new File(sdkRoot).isDirectory()) { throw new Exception('android sdk not specified properly in local.properties or system env ANDROID_SDK_ROOT') } } def rets = new ArrayList(4) // ndkVer,ndkPath,cmakeVer,cmakeOptions(AX_USE_XXX, AX_ENABLE_XXX, AX_ENABLE_EXT_XXX) findNDK(sdkRoot, ndkVer, rets) findCMake(sdkRoot, cmakeVer, rets) parseCMakeOptions(project.properties, rets) return rets } /** * Find suitable ndk for current project * @param project The current android project * @param ndkVer The required ndk version for current project * @param rets * @return */ private static void findNDK(sdkRoot, ndkVer, rets) { def allowNewerNdk = null if (ndkVer.endsWith('+')) { allowNewerNdk = true ndkVer = ndkVer.substring(0, ndkVer.length() - 1) } def verList = [] File dir = new File("${sdkRoot}${File.separator}ndk") if (dir.isDirectory()) { for (ndkDir in dir.listFiles()) { verList.add(ndkDir.getName()) } } verList.sort {a,b -> return axutils.compareVersion(b, a) } def ndkDirs = [] // Collect ndkDirs for search for(ver in verList){ if (ver.startsWith('.')) continue ndkDirs.add("${sdkRoot}${File.separator}ndk${File.separator}${ver}") } /* Find suitable ndk in dirs */ rets[0] = ndkVer for (ndkDir in ndkDirs) { if (findNDKInDir(ndkVer, allowNewerNdk, ndkDir, rets)) { return } } println("No installed ndk found, the gradle will install ndk: $ndkVer automatically") rets[1] = null } /** * Find suitable cmake for current project * @param project The current android project * @param cmakeVer The required cmake version of project * @param rets * @return */ private static void findCMake(sdkRoot, String cmakeVer, rets) { def allowNewerCMake = false if(cmakeVer.endsWith('+')) { allowNewerCMake = true cmakeVer = cmakeVer.substring(0, cmakeVer.length() - 1) } def cmakeBinDirs = [] def cmakeBinDir = null def verList = [] // Scan installed cmake in $sdk_root/cmake File sdkCMakeDir = new File("${sdkRoot}${File.separator}cmake") if (sdkCMakeDir.isDirectory()) { for (cmakeDir in sdkCMakeDir.listFiles()) { verList.add(cmakeDir.getName()) } } // Sort cmake verList verList.sort {a,b -> return axutils.compareVersion(b, a) } // Collect cmakeBinDirs for search for(ver in verList){ if (ver.startsWith('.')) continue cmakeBinDir = sdkRoot + File.separator + "cmake" + File.separator + ver + File.separator + "bin" if(new File(cmakeBinDir).isDirectory()) { cmakeBinDirs.add(cmakeBinDir) } } cmakeBinDir = getCMakeBinFromPath() if(cmakeBinDir != null) { cmakeBinDirs.add(cmakeBinDir) } // find in cmakeBinDirs def foundCMakeVer = null for(item in cmakeBinDirs) { foundCMakeVer = findCMakeFromBinDir(cmakeVer, item, allowNewerCMake) if(foundCMakeVer != null) { break } } if(foundCMakeVer == null) { println("No suitable cmake found, required $cmakeVer, the gradle will install it") foundCMakeVer = cmakeVer } rets[2] = foundCMakeVer } private static void parseCMakeOptions(properties, rets) { def options = [] for(item in properties) { if (item.key.startsWith('AX_')) { options.add("\"-D${item.key}=${item.value}\"") } } rets[3] = options } private static int compareVersion(String ver1, String ver2) { return new VersionComparator().compare(ver1, ver2) } private static String findNDKInDir(ndkVer, allowNewerNdk, ndkDir, rets) { def found = null def properties = new Properties() File propertiesFile = new File("$ndkDir${File.separator}source.properties") propertiesFile.withInputStream { properties.load(it) def foundNdkVer = properties['Pkg.Revision'] def ret = axutils.compareVersion(foundNdkVer, ndkVer) if(ret == 0) { println("Using found ndk (revision=$foundNdkVer,path=$ndkDir)") found = true } else if(ret > 0){ if(allowNewerNdk) { println("Using found newer ndk (revision=$foundNdkVer,path=$ndkDir), (minimum required is: ${ndkVer})") ndkVer = foundNdkVer found = true } else { //throw new GradleException("The ndk ${ndkVer} is required, but $foundNdkVer found!") } } else { //throw new GradleException("The ndk ${ndkVer}+ is required, but $foundNdkVer found!") } } if (found) { rets[0] = ndkVer rets[1] = ndkDir return true } return null } private static String findCMakeFromBinDir(String cmakeVer, String cmakeBin, boolean allowNewerCMake) { def foundCMakeVer = null String ninjaPath = cmakeBin + File.separator + getNinjaProgramName() if(!new File(ninjaPath).isFile()) { println("The required ninja program is not present in cmake bin dir: '$cmakeBin'") return null } try { def programPath = cmakeBin + File.separator + getCMakeProgramName() Process proc = isWindows() ? Runtime.getRuntime().exec("\"$programPath\" --version") : Runtime.getRuntime().exec("$programPath --version") proc.waitFor() int exitVal = proc.exitValue() if (exitVal == 0) { InputStream stdIn = proc.getInputStream(); InputStreamReader isr = new InputStreamReader(stdIn) BufferedReader br = new BufferedReader(isr) String verLine = br.readLine() def verInfo = verLine.split("\\s") if (verInfo.length >= 3) { def foundVer = verInfo[2] def minusIdx = foundVer.indexOf('-') def canonVer = minusIdx == -1 ? foundVer : foundVer.substring(0, minusIdx) def ret = axutils.compareVersion(canonVer, cmakeVer) if (ret == 0) { println("Using found cmake version: $canonVer") foundCMakeVer = canonVer } else if(ret > 0) { if(allowNewerCMake) { println("Using found newer cmake (version=$canonVer,path=$programPath), (minimum required is: ${cmakeVer})") foundCMakeVer = canonVer } else { println("The cmake ${cmakeVer} is required, but $canonVer found!") } } } } } catch(Exception ex) { println("Execute cmake failed: " + ex.message) } return foundCMakeVer } private static String getCMakeBinFromPath() { String cmakeExecName = getCMakeProgramName() def foundBinPath = null Stream.of(System.getenv("PATH").split(Pattern.quote(File.pathSeparator))) .map(Paths::get) .anyMatch(path -> { def programPath = path.resolve(cmakeExecName) boolean fileExist = Files.exists(path.resolve(cmakeExecName)) if(fileExist) { foundBinPath = path.toAbsolutePath().toString() } return fileExist }) return foundBinPath } private static String getCMakeProgramName() { return isWindows() ? "cmake.exe" : "cmake" } private static String getNinjaProgramName() { return isWindows() ? "ninja.exe" : "ninja" } private static boolean isWindows() { return System.getProperty("os.name").toLowerCase().contains("windows") } } ext.axutils = axutils