/// <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);
        }
Example #2
0
        /// <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();
                }
            });
        }
Example #5
0
        /// <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);
        }
Example #6
0
        /// <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);
        }