/// <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> /// <returns>The path to the exploded aar. internal virtual string ProcessAar(string dir, string aarFile) { string workingDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(aarFile)); PlayServicesSupport.DeleteExistingFileOrDirectory(workingDir, includeMetaFiles: true); Directory.CreateDirectory(workingDir); if (!ExtractAar(aarFile, null, workingDir)) { return(workingDir); } // Create the libs directory to store the classes.jar and non-Java shared libraries. string libDir = Path.Combine(workingDir, "libs"); Directory.CreateDirectory(libDir); // Move the classes.jar file to libs. string classesFile = Path.Combine(workingDir, "classes.jar"); if (File.Exists(classesFile)) { string targetClassesFile = Path.Combine(libDir, Path.GetFileName(classesFile)); if (File.Exists(targetClassesFile)) { File.Delete(targetClassesFile); } File.Move(classesFile, targetClassesFile); } // 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"); if (Directory.Exists(jniLibDir)) { PlayServicesSupport.CopyDirectory(jniLibDir, libDir); PlayServicesSupport.DeleteExistingFileOrDirectory(jniLibDir, includeMetaFiles: true); } // 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. File.Delete(Path.GetFullPath(aarFile)); Debug.Log(aarFile + " expanded successfully"); return(workingDir); }
/// <summary> /// Delete the full set of assets managed from this plugin. /// This is used for uninstalling or switching between resolvers which maintain a different /// set of assets. /// </summary> internal static void DeleteLabeledAssets() { foreach (var assetPath in PlayServicesResolver.FindLabeledAssets()) { PlayServicesSupport.DeleteExistingFileOrDirectory(assetPath, includeMetaFiles: true); } AssetDatabase.Refresh(); }
/// <summary> /// Determined whether an aar file should be exploded (extracted). /// /// This is required for some aars so that the Unity Jar Resolver can perform variable /// expansion on manifests in the package before they're merged by aapt. /// </summary> /// <returns><c>true</c>, if the aar should be exploded, <c>false</c> otherwise.</returns> /// <param name="aarFile">The aar file.</param> internal virtual bool ShouldExplode(string aarFile) { AarExplodeData aarData = null; if (!aarExplodeData.TryGetValue(aarFile, out aarData)) { aarData = new AarExplodeData(); } bool explode = !SupportsAarFiles; if (!explode) { System.DateTime modificationTime = File.GetLastWriteTime(aarFile); if (modificationTime.CompareTo(aarData.modificationTime) <= 0) { explode = aarData.explode; } } if (!explode) { string temporaryDirectory = CreateTemporaryDirectory(); if (temporaryDirectory == null) { return(false); } string manifestFilename = "AndroidManifest.xml"; try { if (ExtractAar(aarFile, new string[] { manifestFilename }, temporaryDirectory)) { string manifestPath = Path.Combine(temporaryDirectory, manifestFilename); if (File.Exists(manifestPath)) { string manifest = File.ReadAllText(manifestPath); explode = manifest.IndexOf("${applicationId}") >= 0; } aarData.modificationTime = File.GetLastWriteTime(aarFile); } } catch (System.Exception e) { Debug.Log("Unable to examine AAR file " + aarFile + ", err: " + e); throw e; } finally { PlayServicesSupport.DeleteExistingFileOrDirectory(temporaryDirectory); } } aarData.explode = explode; aarExplodeData[aarFile] = aarData; return(explode); }
/// <summary> /// Delete the specified array of files and directories. /// </summary> /// <param name="filenames">Array of files or directories to delete.</param> /// <returns>true if files are deleted, false otherwise.</returns> private static bool DeleteFiles(string[] filenames) { if (filenames == null) { return(false); } foreach (string artifact in filenames) { PlayServicesSupport.DeleteExistingFileOrDirectory(artifact); } AssetDatabase.Refresh(); return(true); }
/// <summary> /// Delete the specified array of files and directories. /// </summary> /// <param name="filenames">Array of files or directories to delete.</param> /// <returns>true if files are deleted, false otherwise.</returns> private static bool DeleteFiles(string[] filenames) { if (filenames == null) { return(false); } bool deletedFiles = false; foreach (string artifact in filenames) { deletedFiles |= PlayServicesSupport.DeleteExistingFileOrDirectory( artifact, includeMetaFiles: true); } if (deletedFiles) { AssetDatabase.Refresh(); } return(deletedFiles); }
/// <summary> /// Processes the aars. /// </summary> /// <remarks>Each aar copied is inspected and determined if it should be /// exploded into a directory or not. Unneeded exploded directories are /// removed. /// <para> /// Exploding is needed if the version of Unity is old, or if the artifact /// has been explicitly flagged for exploding. This allows the subsequent /// processing of variables in the AndroidManifest.xml file which is not /// supported by the current versions of the manifest merging process that /// Unity uses. /// </para> /// <param name="dir">The directory to process.</param> void ProcessAars(string dir) { string[] files = Directory.GetFiles(dir, "*.aar"); foreach (string f in files) { string dirPath = Path.Combine(dir, Path.GetFileNameWithoutExtension(f)); string targetPath = Path.Combine(dir, Path.GetFileName(f)); if (ShouldExplode(f)) { ReplaceVariables(ProcessAar(Path.GetFullPath(dir), f)); targetPath = dirPath; } else { // Clean up previously expanded / exploded versions of the AAR. PlayServicesSupport.DeleteExistingFileOrDirectory(dirPath, includeMetaFiles: true); } aarExplodeData[f].path = targetPath; aarExplodeData[f].bundleId = PlayerSettings.bundleIdentifier; } }
// Implementation of OnPostProcessUpdateProjectDeps(). // NOTE: This is separate from the post-processing method to prevent the // Mono runtime from loading the Xcode API before calling the post // processing step. public static void UpdateProjectDeps( BuildTarget buildTarget, string pathToBuiltProject) { // If the Pods directory does not exist, the pod download step // failed. var podsDir = Path.Combine(pathToBuiltProject, PODS_DIR); if (!Directory.Exists(podsDir)) { return; } Directory.CreateDirectory(Path.Combine(pathToBuiltProject, "Frameworks")); Directory.CreateDirectory(Path.Combine(pathToBuiltProject, "Resources")); string pbxprojPath = GetProjectPath(pathToBuiltProject); var project = new UnityEditor.iOS.Xcode.PBXProject(); project.ReadFromString(File.ReadAllText(pbxprojPath)); string target = project.TargetGuidByName(TARGET_NAME); HashSet <string> frameworks = new HashSet <string>(); HashSet <string> linkFlags = new HashSet <string>(); foreach (var frameworkFullPath in Directory.GetDirectories(podsDir, "*.framework", SearchOption.AllDirectories)) { string frameworkName = new DirectoryInfo(frameworkFullPath).Name; string destFrameworkPath = Path.Combine("Frameworks", frameworkName); string destFrameworkFullPath = Path.Combine(pathToBuiltProject, destFrameworkPath); // Only move this framework if it contains a library. // Skip frameworks that consist of just resources, they're handled // in a separate import step. if (!File.Exists(Path.Combine( frameworkFullPath, Path.GetFileName(frameworkFullPath) .Replace(".framework", "")))) { continue; } PlayServicesSupport.DeleteExistingFileOrDirectory( destFrameworkFullPath); Directory.Move(frameworkFullPath, destFrameworkFullPath); project.AddFileToBuild( target, project.AddFile(destFrameworkPath, destFrameworkPath, UnityEditor.iOS.Xcode.PBXSourceTree.Source)); string moduleMapPath = Path.Combine(Path.Combine(destFrameworkFullPath, "Modules"), "module.modulemap"); if (File.Exists(moduleMapPath)) { // Parse the modulemap, format spec here: // http://clang.llvm.org/docs/Modules.html#module-map-language using (StreamReader moduleMapFile = new StreamReader(moduleMapPath)) { string line; char[] delim = { ' ' }; while ((line = moduleMapFile.ReadLine()) != null) { string[] items = line.TrimStart(delim).Split(delim, 2); if (items.Length > 1) { if (items[0] == "link") { if (items[1].StartsWith("framework")) { items = items[1].Split(delim, 2); frameworks.Add(items[1].Trim( new char[] { '\"' }) + ".framework"); } else { linkFlags.Add("-l" + items[1]); } } } } } } string resourcesFolder = Path.Combine(destFrameworkFullPath, "Resources"); if (Directory.Exists(resourcesFolder)) { string[] resFiles = Directory.GetFiles(resourcesFolder); string[] resFolders = Directory.GetDirectories(resourcesFolder); foreach (var resFile in resFiles) { string destFile = Path.Combine("Resources", Path.GetFileName(resFile)); File.Copy(resFile, Path.Combine(pathToBuiltProject, destFile), true); project.AddFileToBuild( target, project.AddFile( destFile, destFile, UnityEditor.iOS.Xcode.PBXSourceTree.Source)); } foreach (var resFolder in resFolders) { string destFolder = Path.Combine("Resources", new DirectoryInfo(resFolder).Name); string destFolderFullPath = Path.Combine(pathToBuiltProject, destFolder); PlayServicesSupport.DeleteExistingFileOrDirectory( destFolderFullPath); Directory.Move(resFolder, destFolderFullPath); project.AddFileToBuild( target, project.AddFile( destFolder, destFolder, UnityEditor.iOS.Xcode.PBXSourceTree.Source)); } } } foreach (var framework in frameworks) { project.AddFrameworkToProject(target, framework, false); } foreach (var linkFlag in linkFlags) { project.AddBuildProperty(target, "OTHER_LDFLAGS", linkFlag); } // Add all source files found under the pods directory to the project. // This is a very crude way of partially supporting source pods. var podPathToProjectPaths = new Dictionary <string, string>(); // Find pod source files and map them to paths relative to the target // Xcode project. foreach (var filename in FindFilesWithExtensions(podsDir, SOURCE_FILE_EXTENSIONS)) { // Save the path relative to the target project for each path // relative to the generated pods Xcode project. // +1 in the following expressions to strip the file separator. podPathToProjectPaths[filename.Substring(podsDir.Length + 1)] = filename.Substring(pathToBuiltProject.Length + 1); } // Add a reference to each source file in the target project. foreach (var podPathProjectPath in podPathToProjectPaths) { project.AddFileToBuild( target, project.AddFile(podPathProjectPath.Value, podPathProjectPath.Value, UnityEditor.iOS.Xcode.PBXSourceTree.Source)); } // Attempt to read per-file compile / build settings from the Pods // project. var podsProjectPath = GetProjectPath(podsDir, PODS_PROJECT_NAME); var podsProject = new UnityEditor.iOS.Xcode.PBXProject(); podsProject.ReadFromString(File.ReadAllText(podsProjectPath)); foreach (var directory in Directory.GetDirectories(podsDir)) { // Each pod will have a top level directory under the pods dir // named after the pod. Also, some pods have build targets in the // xcode project where each build target has the same name as the // pod such that pod Foo is in directory Foo with build target Foo. // Since we can't read the build targets from the generated Xcode // project using Unity's API, we scan the Xcode project for targets // to optionally retrieve build settings for each source file // the settings can be applied in the target project. var podTargetName = Path.GetFileName(directory); var podTargetGuid = podsProject.TargetGuidByName(podTargetName); Log(String.Format("Looking for target: {0} guid: {1}", podTargetName, podTargetGuid ?? "null"), verbose: true); if (podTargetGuid == null) { continue; } foreach (var podPathProjectPath in podPathToProjectPaths) { var podSourceFileGuid = podsProject.FindFileGuidByRealPath( podPathProjectPath.Key); if (podSourceFileGuid == null) { continue; } var podSourceFileCompileFlags = podsProject.GetCompileFlagsForFile(podTargetGuid, podSourceFileGuid); if (podSourceFileCompileFlags == null) { continue; } var targetSourceFileGuid = project.FindFileGuidByProjectPath( podPathProjectPath.Value); if (targetSourceFileGuid == null) { Log("Unable to find " + podPathProjectPath.Value + " in generated project", level: LogLevel.Warning); continue; } Log(String.Format( "Setting {0} compile flags to ({1})", podPathProjectPath.Key, String.Join(", ", podSourceFileCompileFlags.ToArray())), verbose: true); project.SetCompileFlagsForFile(target, targetSourceFileGuid, podSourceFileCompileFlags); } } File.WriteAllText(pbxprojPath, project.WriteToString()); }
/// <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); }
/// <summary> /// Determined whether an aar file should be exploded (extracted). /// /// This is required for some aars so that the Unity Jar Resolver can perform variable /// expansion on manifests in the package before they're merged by aapt. /// </summary> /// <returns><c>true</c>, if the aar should be exploded, <c>false</c> otherwise.</returns> /// <param name="aarFile">The aar file.</param> internal virtual bool ShouldExplode(string aarFile) { AarExplodeData aarData = null; if (!aarExplodeData.TryGetValue(aarFile, out aarData)) { aarData = new AarExplodeData(); } bool explosionEnabled = true; // Unfortunately, as of Unity 5.5.0f3, Unity does not set the applicationId variable // in the build.gradle it generates. This results in non-functional libraries that // require the ${applicationId} variable to be expanded in their AndroidManifest.xml. // To work around this when Gradle builds are enabled, explosion is enabled for all // AARs that require variable expansion unless this behavior is explicitly disabled // in the settings dialog. if (PlayServicesResolver.GradleBuildEnabled && PlayServicesResolver.ProjectExportEnabled && !SettingsDialog.ExplodeAars) { explosionEnabled = false; } bool explode = false; if (explosionEnabled) { explode = !SupportsAarFiles; if (!explode) { System.DateTime modificationTime = File.GetLastWriteTime(aarFile); if (modificationTime.CompareTo(aarData.modificationTime) <= 0) { explode = aarData.explode; } } if (!explode) { string temporaryDirectory = CreateTemporaryDirectory(); if (temporaryDirectory == null) { return(false); } string manifestFilename = "AndroidManifest.xml"; try { if (ExtractAar(aarFile, new string[] { manifestFilename }, temporaryDirectory)) { string manifestPath = Path.Combine(temporaryDirectory, manifestFilename); if (File.Exists(manifestPath)) { string manifest = File.ReadAllText(manifestPath); explode = manifest.IndexOf("${applicationId}") >= 0; } aarData.modificationTime = File.GetLastWriteTime(aarFile); } } catch (System.Exception e) { Debug.Log("Unable to examine AAR file " + aarFile + ", err: " + e); throw e; } finally { PlayServicesSupport.DeleteExistingFileOrDirectory(temporaryDirectory); } } } aarData.explode = explode; aarExplodeData[aarFile] = aarData; return(explode); }
public static void OnPostProcessUpdateProjectDeps( BuildTarget buildTarget, string pathToBuiltProject) { if (!InjectDependencies()) { return; } // If the Pods directory does not exist, the pod download step // failed. var podsDir = Path.Combine(pathToBuiltProject, "Pods"); if (!Directory.Exists(podsDir)) { return; } Directory.CreateDirectory(Path.Combine(pathToBuiltProject, "Frameworks")); Directory.CreateDirectory(Path.Combine(pathToBuiltProject, "Resources")); string pbxprojPath = GetProjectPath(pathToBuiltProject); var project = new UnityEditor.iOS.Xcode.PBXProject(); project.ReadFromString(File.ReadAllText(pbxprojPath)); string target = project.TargetGuidByName(TARGET_NAME); HashSet <string> frameworks = new HashSet <string>(); HashSet <string> linkFlags = new HashSet <string>(); foreach (var frameworkFullPath in Directory.GetDirectories(podsDir, "*.framework", SearchOption.AllDirectories)) { string frameworkName = new DirectoryInfo(frameworkFullPath).Name; string destFrameworkPath = Path.Combine("Frameworks", frameworkName); string destFrameworkFullPath = Path.Combine(pathToBuiltProject, destFrameworkPath); // Only move this framework if it contains a library. // Skip frameworks that consist of just resources, they're handled // in a separate import step. if (!File.Exists(Path.Combine( frameworkFullPath, Path.GetFileName(frameworkFullPath) .Replace(".framework", "")))) { continue; } PlayServicesSupport.DeleteExistingFileOrDirectory( destFrameworkFullPath); Directory.Move(frameworkFullPath, destFrameworkFullPath); project.AddFileToBuild( target, project.AddFile(destFrameworkPath, destFrameworkPath, UnityEditor.iOS.Xcode.PBXSourceTree.Source)); string moduleMapPath = Path.Combine(Path.Combine(destFrameworkFullPath, "Modules"), "module.modulemap"); if (File.Exists(moduleMapPath)) { // Parse the modulemap, format spec here: // http://clang.llvm.org/docs/Modules.html#module-map-language using (StreamReader moduleMapFile = new StreamReader(moduleMapPath)) { string line; char[] delim = { ' ' }; while ((line = moduleMapFile.ReadLine()) != null) { string[] items = line.TrimStart(delim).Split(delim, 2); if (items.Length > 1) { if (items[0] == "link") { if (items[1].StartsWith("framework")) { items = items[1].Split(delim, 2); frameworks.Add(items[1].Trim( new char[] { '\"' }) + ".framework"); } else { linkFlags.Add("-l" + items[1]); } } } } } } string resourcesFolder = Path.Combine(destFrameworkFullPath, "Resources"); if (Directory.Exists(resourcesFolder)) { string[] resFiles = Directory.GetFiles(resourcesFolder); string[] resFolders = Directory.GetDirectories(resourcesFolder); foreach (var resFile in resFiles) { string destFile = Path.Combine("Resources", Path.GetFileName(resFile)); File.Copy(resFile, Path.Combine(pathToBuiltProject, destFile), true); project.AddFileToBuild( target, project.AddFile( destFile, destFile, UnityEditor.iOS.Xcode.PBXSourceTree.Source)); } foreach (var resFolder in resFolders) { string destFolder = Path.Combine("Resources", new DirectoryInfo(resFolder).Name); string destFolderFullPath = Path.Combine(pathToBuiltProject, destFolder); PlayServicesSupport.DeleteExistingFileOrDirectory( destFolderFullPath); Directory.Move(resFolder, destFolderFullPath); project.AddFileToBuild( target, project.AddFile( destFolder, destFolder, UnityEditor.iOS.Xcode.PBXSourceTree.Source)); } } } foreach (var framework in frameworks) { project.AddFrameworkToProject(target, framework, false); } foreach (var linkFlag in linkFlags) { project.AddBuildProperty(target, "OTHER_LDFLAGS", linkFlag); } File.WriteAllText(pbxprojPath, project.WriteToString()); }
// 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(); } }); }