/// <summary> /// Find the latest build-tools minor version matching a major version. /// </summary> /// <remarks> /// This is intended to find the latest version of the build-tools for a given platform. /// For example with platform android-25, it will search the AndroidSdkPackageCollection of /// available packages and returns the latest version string for build tools such as "25.0.3". /// </remarks> /// <param name="packages">available packages from the android sdk manager query.</param> /// <param name="majorVersion">Platform version to match for the major version.</param> /// <returns>Semantic version (http://semver.org/), ie. 26.0.0</returns> public static string GetLatestMinorBuildToolsVersion(AndroidSdkPackageCollection packages, int majorVersion) { Regex buildToolsRegex = new Regex(@"^build-tools;(\d+)\.(\d+)\.(\d+)(?:-.*)?$", RegexOptions.Compiled | RegexOptions.Multiline); int latestVersion = 0; string latestVersionString = null; foreach (Match match in buildToolsRegex.Matches( String.Join("\n", packages.PackageNames.ToArray()))) { int thisVersion = Int32.Parse(match.Groups[1].Value) * 1000000 + Int32.Parse(match.Groups[2].Value) * 1000 + Int32.Parse(match.Groups[3].Value); if (Int32.Parse(match.Groups[1].Value) == majorVersion && thisVersion > latestVersion) { latestVersion = thisVersion; latestVersionString = String.Format("{0}.{1}.{2}", match.Groups[1].Value, match.Groups[2].Value, match.Groups[3].Value); } } return(latestVersionString); }
/// <summary> /// Parse "android list sdk -u -e -a" output. /// </summary> private AndroidSdkPackageCollection ParseAndroidListSdkOutput( string androidListSdkOutput) { var packages = new AndroidSdkPackageCollection(); AndroidSdkPackage currentPackage = null; foreach (string line in CommandLine.SplitLines(androidListSdkOutput)) { // Check for the start of a new package entry. if (line.StartsWith("---")) { currentPackage = null; continue; } Match match; // If this is the start of a package description, add a package. match = PACKAGE_ID_REGEX.Match(line); if (match.Success) { // TODO(smiles): Convert the legacy package name to a new package name. currentPackage = new AndroidSdkPackage { LegacyName = match.Groups[1].Value }; packages[currentPackage.Name].Add(currentPackage); continue; } if (currentPackage == null) { continue; } // Add a package description. match = PACKAGE_DESCRIPTION_REGEX.Match(line); if (match.Success) { currentPackage.Description = match.Groups[1].Value; continue; } // Parse the install path and record whether the package is installed. match = PACKAGE_INSTALL_LOCATION_REGEX.Match(line); if (match.Success) { currentPackage.Installed = File.Exists( Path.Combine(Path.Combine(sdkPath, match.Groups[1].Value), "source.properties")); } } return(packages); }
/// <summary> /// Search the AndroidSdkPackageCollection for the latest platform version. /// </summary> /// <remarks> /// This finds the latest installed android platform, and returns the version. /// </remarks> /// <param name="packages">available packages from the android sdk manager query.</param> /// <returns>sdk version (ie. 24 for Android 7.0 Nougat, 25 for Android 7.1 Nougat)</returns> public static int GetLatestInstalledAndroidPlatformVersion( AndroidSdkPackageCollection packages) { Regex buildToolsRegex = new Regex(@"^platforms;android-(\d+)$", RegexOptions.Compiled | RegexOptions.Multiline); int latestVersion = 0; foreach (Match match in buildToolsRegex.Matches( String.Join("\n", packages.PackageNames.ToArray()))) { int thisVersion = Int32.Parse(match.Groups[1].Value); if (thisVersion > latestVersion) { if (packages.GetInstalledPackage(match.Groups[0].Value) != null) { latestVersion = thisVersion; } } } return(latestVersion); }
// Private method to avoid too deeply nested code in "DoResolution". private void GradleResolve(AndroidSdkPackageCollection packages, PlayServicesSupport svcSupport, string destinationDirectory, System.Action resolutionComplete) { string errorOutro = "make sure you have the latest version of this plugin and if you " + "still get this error, report it in a a bug here:\n" + "https://github.com/googlesamples/unity-jar-resolver/issues\n"; string errorIntro = null; int targetSdkVersion = UnityCompat.GetAndroidTargetSDKVersion(); string buildToolsVersion = null; if (targetSdkVersion < 0) { // A value of -1 means the targetSDK Version enum returned "Auto" // instead of an actual version, so it's up to us to actually figure it out. targetSdkVersion = GetLatestInstalledAndroidPlatformVersion(packages); PlayServicesResolver.Log( String.Format("TargetSDK is set to Auto-detect, and the latest Platform has " + "been detected as: android-{0}", targetSdkVersion), level: LogLevel.Verbose); errorIntro = String.Format("The Target SDK is set to automatically pick the highest " + "installed platform in the Android Player Settings, which appears to be " + "\"android-{0}\". This requires build-tools with at least the same version, " + "however ", targetSdkVersion); } else { errorIntro = String.Format("The target SDK version is set in the Android Player " + "Settings to \"android-{0}\" which requires build tools with " + "at least the same version, however ", targetSdkVersion); } // You can use a higher version of the build-tools than your compileSdkVersion, in order // to pick up new/better compiler while not changing what you build your app against. --Xav // https://stackoverflow.com/a/24523113 // Implicitly Xav is also saying, you can't use a build tool version less than the // platform level you're building. This is backed up from testing. if (targetSdkVersion > TESTED_BUILD_TOOLS_VERSION_MAJOR) { buildToolsVersion = GetLatestMinorBuildToolsVersion(packages, targetSdkVersion); if (buildToolsVersion == null) { PlayServicesResolver.Log( errorIntro + String.Format("no build-tools are available " + "at this level in the sdk manager. This plugin has been tested with " + "platforms up to android-{0} using build-tools {0}.{1}.{2}. You can try " + "selecting a lower targetSdkVersion in the Android Player Settings. Please ", TESTED_BUILD_TOOLS_VERSION_MAJOR, TESTED_BUILD_TOOLS_VERSION_MINOR, TESTED_BUILD_TOOLS_VERSION_REV) + errorOutro, level: LogLevel.Error); return; } else { PlayServicesResolver.Log( errorIntro + String.Format("this plugin has only been " + "tested with build-tools up to version {0}.{1}.{2}. Corresponding " + "build-tools version {3} will be used, however this is untested with this " + "plugin and MAY NOT WORK! If you have trouble, please select a target SDK " + "version at or below \"android-{0}\". If you need to get this working with " + "the latest platform, please ", TESTED_BUILD_TOOLS_VERSION_MAJOR, TESTED_BUILD_TOOLS_VERSION_MINOR, TESTED_BUILD_TOOLS_VERSION_REV, buildToolsVersion) + errorOutro, level: LogLevel.Warning); } } if (buildToolsVersion == null) { // Use the tested build tools version, which we know will be able to handle // this targetSDK version. buildToolsVersion = String.Format("{0}.{1}.{2}", TESTED_BUILD_TOOLS_VERSION_MAJOR, TESTED_BUILD_TOOLS_VERSION_MINOR, TESTED_BUILD_TOOLS_VERSION_REV); // We don't have to bother with checking if it's installed because gradle actually // does that for us. } string minSdkVersion = UnityCompat.GetAndroidMinSDKVersion().ToString(); var config = new Dictionary <string, string>() { { "app_id", UnityCompat.ApplicationId }, { "sdk_version", targetSdkVersion.ToString() }, { "min_sdk_version", minSdkVersion }, { "build_tools_version", buildToolsVersion }, { "android_sdk_dir", svcSupport.SDK } }; // This creates an enumerable of strings with the json lines for each dep like this: // "[\"namespace\", \"package\", \"version\"]" var dependencies = PlayServicesSupport.GetAllDependencies(); var depLines = from d in dependencies select "[" + ToJSONList(DepsVersionAsArray(d.Value)) + "]"; // Get a flattened list of dependencies, excluding any with the "$SDK" path variable, // since those will automatically be included in the gradle build. var repoLines = new HashSet <string>( dependencies.SelectMany(d => d.Value.Repositories) .Where(s => !s.Contains(PlayServicesSupport.SdkVariable))); var proguard_config_paths = new List <string>() { Path.Combine(GRADLE_SCRIPT_LOCATION, PROGUARD_UNITY_CONFIG), Path.Combine(GRADLE_SCRIPT_LOCATION, PROGUARD_MSG_FIX_CONFIG) }; // Build the full json config as a string. string json_config = @"{{ ""config"": {{ {0} }}, ""project_deps"": [ {1} ], ""extra_m2repositories"": [ {2} ], ""extra_proguard_configs"": [ {3} ] }}"; json_config = String.Format(json_config, ToJSONDictionary(config), ToJSONList(depLines, ",\n", 4, true), ToJSONList(repoLines, ",\n", 4), ToJSONList(proguard_config_paths, ",\n", 4)); // Escape any literal backslashes (such as those from paths on windows), since we want to // preserve them when reading the config as backslashes and not interpret them // as escape characters. json_config = json_config.Replace(@"\", @"\\"); System.IO.File.WriteAllText(GENERATE_CONFIG_PATH, json_config); var outDir = Path.Combine(destinationDirectory, GENERATE_GRADLE_OUTPUT_DIR); RunGenGradleScript( " -c \"" + GENERATE_CONFIG_PATH + "\"" + " -b \"" + GENERATE_GRADLE_BUILD_PATH + "\"" + " -o \"" + outDir + "\"", (result) => { if (result.exitCode == 0) { var currentAbi = PlayServicesResolver.AndroidTargetDeviceAbi; var activeAbis = GetSelectedABIDirs(currentAbi); var libsDir = Path.Combine(outDir, "libs"); if (Directory.Exists(libsDir)) { foreach (var directory in Directory.GetDirectories(libsDir)) { var abiDir = Path.GetFileName(directory).ToLower(); if (!activeAbis.Contains(abiDir)) { FileUtils.DeleteExistingFileOrDirectory(directory); } } } if (Directory.Exists(outDir)) { PlayServicesResolver.LabelAssets(new [] { outDir }, true, true); } AssetDatabase.Refresh(); resolutionComplete(); } }); }
/// <summary> /// Parse "sdkmanager --list" output. /// </summary> /// <returns>Dictionary of packages bucketed by package name</returns> private AndroidSdkPackageCollection ParseListOutput( string sdkManagerListOutput) { var packages = new AndroidSdkPackageCollection(); // Whether we're parsing a set of packages. bool parsingPackages = false; // Whether we're parsing within the set of installed packages vs. available packages. bool parsingInstalledPackages = false; // Whether we're parsing the contents of the package table vs. the header. bool inPackageTable = false; foreach (var rawLine in CommandLine.SplitLines(sdkManagerListOutput)) { var line = rawLine.Trim(); var lowerCaseLine = line.ToLower(); if (lowerCaseLine == AVAILABLE_UPDATES_HEADER) { parsingPackages = false; continue; } bool installedPackagesLine = lowerCaseLine == INSTALLED_PACKAGES_HEADER; bool availablePackagesLine = lowerCaseLine == AVAILABLE_PACKAGES_HEADER; if (installedPackagesLine || availablePackagesLine) { parsingPackages = true; parsingInstalledPackages = installedPackagesLine; inPackageTable = false; continue; } if (!parsingPackages) { continue; } if (!inPackageTable) { // If we've reached end of the table header, start parsing the set of packages. if (line.StartsWith("----")) { inPackageTable = true; } continue; } // Split into the fields package_name|version|description|location. // Where "location" is an optional field that contains the install path. var rawTokens = line.Split(new [] { '|' }); if (rawTokens.Length < 3 || String.IsNullOrEmpty(line)) { parsingPackages = false; continue; } // Each field is surrounded by whitespace so trim the fields. string[] tokens = new string[rawTokens.Length]; for (int i = 0; i < rawTokens.Length; ++i) { tokens[i] = rawTokens[i].Trim(); } var packageName = tokens[0]; packages[packageName].Add(new AndroidSdkPackage { Name = packageName, Description = tokens[2], VersionString = tokens[1], Installed = parsingInstalledPackages }); } return(packages); }
/// <summary> /// Parse "sdkmanager --list --verbose" output. /// NOTE: The --verbose output format is only reported by sdkmanager 26.0.2 and above. /// </summary> private AndroidSdkPackageCollection ParseListVerboseOutput( string sdkManagerListVerboseOutput) { var packages = new AndroidSdkPackageCollection(); // Whether we're parsing a set of packages. bool parsingPackages = false; // Whether we're parsing within the set of installed packages vs. available packages. bool parsingInstalledPackages = false; // Fields of the package being parsed. AndroidSdkPackage currentPackage = null; foreach (var rawLine in CommandLine.SplitLines(sdkManagerListVerboseOutput)) { var line = rawLine.Trim(); var lowerCaseLine = line.ToLower(); if (lowerCaseLine == AVAILABLE_UPDATES_HEADER) { parsingPackages = false; continue; } bool installedPackagesLine = lowerCaseLine == INSTALLED_PACKAGES_HEADER; bool availablePackagesLine = lowerCaseLine == AVAILABLE_PACKAGES_HEADER; if (installedPackagesLine || availablePackagesLine) { parsingPackages = true; parsingInstalledPackages = installedPackagesLine; continue; } else if (line.StartsWith("---")) { // Ignore section separators. continue; } else if (String.IsNullOrEmpty(line)) { if (currentPackage != null && !(String.IsNullOrEmpty(currentPackage.Name) || String.IsNullOrEmpty(currentPackage.VersionString))) { packages[currentPackage.Name].Add(currentPackage); } currentPackage = null; continue; } else if (!parsingPackages) { continue; } // Fields of the package are indented. bool indentedLine = rawLine.StartsWith(" "); if (!indentedLine) { // If this isn't an indented line it should be a package name. if (currentPackage == null) { currentPackage = new AndroidSdkPackage { Name = line, Installed = parsingInstalledPackages }; } } else if (currentPackage != null) { // Parse the package field. var fieldSeparatorIndex = line.IndexOf(":"); if (fieldSeparatorIndex >= 0) { var fieldName = line.Substring(0, fieldSeparatorIndex).Trim().ToLower(); var fieldValue = line.Substring(fieldSeparatorIndex + 1).Trim(); if (fieldName == "description") { currentPackage.Description = fieldValue; } else if (fieldName == "version") { currentPackage.VersionString = fieldValue; } } } } return(packages); }