/// <summary> /// Read package metadata from the source.properties file within the specified directory. /// </summary> /// <param name="sdkDirectory">Android SDK directory to query.</param> /// <param name="packageDirectory">Directory containing the package relative to /// sdkDirectory.</param> public static AndroidSdkPackage ReadFromSourceProperties(string sdkDirectory, string packageDirectory) { var propertiesPath = System.IO.Path.Combine( sdkDirectory, System.IO.Path.Combine(packageDirectory, "source.properties")); string propertiesText = null; try { propertiesText = File.ReadAllText(propertiesPath); } catch (Exception e) { PlayServicesSupport.Log(String.Format("Unable to read {0}\n{1}\n", propertiesPath, e.ToString()), verbose: true); return(null); } // Unfortunately the package name is simply based upon the path within the SDK. var sdkPackage = new AndroidSdkPackage { Path = packageDirectory }; const string VERSION_FIELD_NAME = "Pkg.Revision="; const string DESCRIPTION_FIELD_NAME = "Pkg.Desc="; foreach (var rawLine in CommandLine.SplitLines(propertiesText)) { var line = rawLine.Trim(); // Ignore comments. if (line.StartsWith("#")) { continue; } // Parse fields if (line.StartsWith(VERSION_FIELD_NAME)) { sdkPackage.VersionString = line.Substring(VERSION_FIELD_NAME.Length); } else if (line.StartsWith(DESCRIPTION_FIELD_NAME)) { sdkPackage.Description = line.Substring(DESCRIPTION_FIELD_NAME.Length); } } return(sdkPackage); }
/// <summary> /// Find a tool in the Android SDK. /// </summary> /// <param name="toolName">Name of the tool to search for.</param> /// <param name="sdkPath">SDK path to search for the tool. If this is null or empty, the // system path is searched instead.</param> /// <returns>String with the path to the tool if found, null otherwise.</returns> private static string FindAndroidSdkTool(string toolName, string sdkPath = null) { if (String.IsNullOrEmpty(sdkPath)) { PlayServicesSupport.Log(String.Format( "{0}\n" + "Falling back to searching for the Android SDK tool {1} in the system path.", PlayServicesSupport.AndroidSdkConfigurationError, toolName)); } else { var extensions = new List <string> { CommandLine.GetExecutableExtension() }; if (UnityEngine.RuntimePlatform.WindowsEditor == UnityEngine.Application.platform) { extensions.AddRange(new [] { ".bat", ".cmd" }); } foreach (var dir in new [] { "tools", Path.Combine("tools", "bin") }) { foreach (var extension in extensions) { var currentPath = Path.Combine(sdkPath, Path.Combine(dir, toolName + extension)); if (File.Exists(currentPath)) { return(currentPath); } } } } var toolPath = CommandLine.FindExecutable(toolName); return(toolPath != null && File.Exists(toolPath) ? toolPath : null); }
/// <summary> /// Resolve dependencies. /// </summary> /// <param name="resolutionComplete">Delegate called when resolution is complete /// with a parameter that indicates whether it succeeded or failed.</param> /// <param name="forceResolution">Whether resolution should be executed when no dependencies /// have changed. This is useful if a dependency specifies a wildcard in the version /// expression.</param> private static void ResolveUnsafe(Action <bool> resolutionComplete = null, bool forceResolution = false) { JavaUtilities.CheckJdkForApiLevel(); if (!buildConfigChanged) { DeleteFiles(Resolver.OnBuildSettings()); } xmlDependencies.ReadAll(logger); if (forceResolution) { DeleteLabeledAssets(); } else { // Only resolve if user specified dependencies changed or the output files // differ to what is present in the project. var currentState = DependencyState.GetState(); var previousState = DependencyState.ReadFromFile(); if (previousState != null) { if (currentState.Equals(previousState)) { if (resolutionComplete != null) { resolutionComplete(true); } return; } // Delete all labeled assets to make sure we don't leave any stale transitive // dependencies in the project. DeleteLabeledAssets(); } } System.IO.Directory.CreateDirectory(GooglePlayServices.SettingsDialog.PackageDir); PlayServicesSupport.Log("Resolving...", verbose: true); lastError = ""; Resolver.DoResolution(svcSupport, GooglePlayServices.SettingsDialog.PackageDir, (oldDependency, newDependency) => { return(Resolver.ShouldReplaceDependency(oldDependency, newDependency)); }, () => { System.Action complete = () => { bool succeeded = String.IsNullOrEmpty(lastError); AssetDatabase.Refresh(); DependencyState.GetState().WriteToFile(); PlayServicesSupport.Log(String.Format( "Resolution {0}.\n\n{1}", succeeded ? "Succeeded" : "Failed", lastError), verbose: true); if (resolutionComplete != null) { resolutionComplete(succeeded); } }; updateQueue.Enqueue(complete); }); }
/// <summary> /// Explodes a single aar file. This is done by calling the /// JDK "jar" command, then moving the classes.jar file. /// </summary> /// <param name="dir">the parent directory of the plugin.</param> /// <param name="aarFile">Aar file to explode.</param> /// <param name="antProject">true to explode into an Ant style project or false /// to repack the processed AAR as a new AAR.</param> /// <param name="abi">ABI of the AAR or null if it's universal.</param> /// <returns>true if successful, false otherwise.</returns> internal virtual bool ProcessAar(string dir, string aarFile, bool antProject, out string abi) { PlayServicesSupport.Log(String.Format("ProcessAar {0} {1} antProject={2}", dir, aarFile, antProject), verbose: true); abi = null; string workingDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(aarFile)); PlayServicesSupport.DeleteExistingFileOrDirectory(workingDir, includeMetaFiles: true); Directory.CreateDirectory(workingDir); if (!ExtractAar(aarFile, null, workingDir)) { return(false); } ReplaceVariables(workingDir); string nativeLibsDir = null; if (antProject) { // Create the libs directory to store the classes.jar and non-Java shared // libraries. string libDir = Path.Combine(workingDir, "libs"); nativeLibsDir = libDir; Directory.CreateDirectory(libDir); // Move the classes.jar file to libs. string classesFile = Path.Combine(workingDir, "classes.jar"); string targetClassesFile = Path.Combine(libDir, Path.GetFileName(classesFile)); if (File.Exists(targetClassesFile)) { File.Delete(targetClassesFile); } if (File.Exists(classesFile)) { File.Move(classesFile, targetClassesFile); } else { // Generate an empty classes.jar file. string temporaryDirectory = CreateTemporaryDirectory(); if (temporaryDirectory == null) { return(false); } ArchiveAar(targetClassesFile, temporaryDirectory); } } // Copy non-Java shared libraries (.so) files from the "jni" directory into the // lib directory so that Unity's legacy (Ant-like) build system includes them in the // built APK. string jniLibDir = Path.Combine(workingDir, "jni"); nativeLibsDir = nativeLibsDir ?? jniLibDir; if (Directory.Exists(jniLibDir)) { if (jniLibDir != nativeLibsDir) { PlayServicesSupport.CopyDirectory(jniLibDir, nativeLibsDir); PlayServicesSupport.DeleteExistingFileOrDirectory(jniLibDir, includeMetaFiles: true); } // Remove shared libraries for all ABIs that are not required for the selected // target ABI. var currentAbi = PlayServicesResolver.AndroidTargetDeviceAbi; var activeAbis = GetSelectedABIDirs(currentAbi); foreach (var directory in Directory.GetDirectories(nativeLibsDir)) { var abiDir = Path.GetFileName(directory); if (!activeAbis.Contains(abiDir)) { PlayServicesSupport.DeleteExistingFileOrDirectory( directory, includeMetaFiles: true); } } abi = currentAbi; } if (antProject) { // Create the project.properties file which indicates to // Unity that this directory is a plugin. string projectProperties = Path.Combine(workingDir, "project.properties"); if (!File.Exists(projectProperties)) { File.WriteAllLines(projectProperties, new [] { "# Project target.", "target=android-9", "android.library=true" }); } // Clean up the aar file. PlayServicesSupport.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile), includeMetaFiles: true); // Add a tracking label to the exploded files. PlayServicesResolver.LabelAssets(new [] { workingDir }); } else { // Add a tracking label to the exploded files just in-case packaging fails. PlayServicesResolver.LabelAssets(new [] { workingDir }); // Create a new AAR file. PlayServicesSupport.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile), includeMetaFiles: true); if (!ArchiveAar(aarFile, workingDir)) { return(false); } // Clean up the exploded directory. PlayServicesSupport.DeleteExistingFileOrDirectory(workingDir, includeMetaFiles: true); } return(true); }
// 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); PlayServicesSupport.Log( String.Format("TargetSDK is set to Auto-detect, and the latest Platform has been " + "detected as: android-{0}", targetSdkVersion), level: PlayServicesSupport.LogLevel.Info, verbose: true); 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) { PlayServicesSupport.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: PlayServicesSupport.LogLevel.Error); return; } else { PlayServicesSupport.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: PlayServicesSupport.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 = svcSupport.LoadDependencies(true, true, false); 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)) { PlayServicesSupport.DeleteExistingFileOrDirectory( directory, includeMetaFiles: true); } } } if (Directory.Exists(outDir)) { PlayServicesResolver.LabelAssets(new [] { outDir }, true, true); } AssetDatabase.Refresh(); resolutionComplete(); } }); }