/// <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 directory to unpack / explode the AAR to. If antProject is true /// the ant project will be located in Path.Combine(dir, Path.GetFileName(aarFile)).</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="abis">ABIs in 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 AndroidAbis abis) { PlayServicesResolver.Log(String.Format("ProcessAar {0} {1} antProject={2}", dir, aarFile, antProject), level: LogLevel.Verbose); abis = null; string workingDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(aarFile)); FileUtils.DeleteExistingFileOrDirectory(workingDir); Directory.CreateDirectory(workingDir); if (!PlayServicesResolver.ExtractZip(aarFile, null, workingDir)) { return(false); } PlayServicesResolver.ReplaceVariablesInAndroidManifest( Path.Combine(workingDir, "AndroidManifest.xml"), UnityCompat.ApplicationId, new Dictionary <string, string>()); 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); } if (!ArchiveAar(targetClassesFile, temporaryDirectory)) { return(false); } } } // 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)) { var abisInArchive = AarDirectoryFindAbis(workingDir); if (jniLibDir != nativeLibsDir) { FileUtils.CopyDirectory(jniLibDir, nativeLibsDir); FileUtils.DeleteExistingFileOrDirectory(jniLibDir); } if (abisInArchive != null) { // Remove shared libraries for all ABIs that are not required for the selected // ABIs. var activeAbisSet = AndroidAbis.Current.ToSet(); var abisInArchiveSet = abisInArchive.ToSet(); var abisInArchiveToRemoveSet = new HashSet <string>(abisInArchiveSet); abisInArchiveToRemoveSet.ExceptWith(activeAbisSet); Func <IEnumerable <string>, string> setToString = (setToConvert) => { return(String.Join(", ", (new List <string>(setToConvert)).ToArray())); }; PlayServicesResolver.Log( String.Format( "Target ABIs [{0}], ABIs [{1}] in {2}, will remove [{3}] ABIs", setToString(activeAbisSet), setToString(abisInArchiveSet), aarFile, setToString(abisInArchiveToRemoveSet)), level: LogLevel.Verbose); foreach (var abiToRemove in abisInArchiveToRemoveSet) { abisInArchiveSet.Remove(abiToRemove); FileUtils.DeleteExistingFileOrDirectory(Path.Combine(nativeLibsDir, abiToRemove)); } abis = new AndroidAbis(abisInArchiveSet); } } 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" }); } PlayServicesResolver.Log(String.Format("Replacing {0} with {1}", aarFile, workingDir), level: LogLevel.Verbose); // Clean up the aar file. FileUtils.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile)); // 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 }); PlayServicesResolver.Log(String.Format("Repacking {0} from {1}", aarFile, workingDir), level: LogLevel.Verbose); // Create a new AAR file. FileUtils.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile)); if (!ArchiveAar(aarFile, workingDir)) { return(false); } // Clean up the exploded directory. FileUtils.DeleteExistingFileOrDirectory(workingDir); } return(true); }
/// <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 directory to unpack / explode the AAR to. If antProject is true /// the ant project will be located in Path.Combine(dir, Path.GetFileName(aarFile)).</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="abis">ABIs in 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 AndroidAbis abis) { PlayServicesResolver.Log(String.Format("ProcessAar {0} {1} antProject={2}", dir, aarFile, antProject), level: LogLevel.Verbose); abis = null; string aarDirName = Path.GetFileNameWithoutExtension(aarFile); // Output directory for the contents of the AAR / JAR. string outputDir = Path.Combine(dir, aarDirName); string stagingDir = FileUtils.CreateTemporaryDirectory(); if (stagingDir == null) { PlayServicesResolver.Log(String.Format( "Unable to create temporary directory to process AAR {0}", aarFile), level: LogLevel.Error); return(false); } try { string workingDir = Path.Combine(stagingDir, aarDirName); FileUtils.DeleteExistingFileOrDirectory(workingDir); Directory.CreateDirectory(workingDir); if (!PlayServicesResolver.ExtractZip(aarFile, null, workingDir)) { return(false); } PlayServicesResolver.ReplaceVariablesInAndroidManifest( Path.Combine(workingDir, "AndroidManifest.xml"), PlayServicesResolver.GetAndroidApplicationId(), new Dictionary <string, string>()); 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)) { FileUtils.MoveFile(classesFile, targetClassesFile); } else { // Some libraries publish AARs that are poorly formatted (e.g missing // a classes.jar file). Firebase's license AARs at certain versions are // examples of this. When Unity's internal build system detects an Ant // project or AAR without a classes.jar, the build is aborted. This // generates an empty classes.jar file to workaround the issue. string emptyClassesDir = Path.Combine(stagingDir, "empty_classes_jar"); if (!ArchiveAar(targetClassesFile, emptyClassesDir)) { return(false); } } } // 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)) { var abisInArchive = AarDirectoryFindAbis(workingDir); if (jniLibDir != nativeLibsDir) { FileUtils.CopyDirectory(jniLibDir, nativeLibsDir); FileUtils.DeleteExistingFileOrDirectory(jniLibDir); } if (abisInArchive != null) { // Remove shared libraries for all ABIs that are not required for the // selected ABIs. var activeAbisSet = AndroidAbis.Current.ToSet(); var abisInArchiveSet = abisInArchive.ToSet(); var abisInArchiveToRemoveSet = new HashSet <string>(abisInArchiveSet); abisInArchiveToRemoveSet.ExceptWith(activeAbisSet); Func <IEnumerable <string>, string> setToString = (setToConvert) => { return(String.Join(", ", (new List <string>(setToConvert)).ToArray())); }; PlayServicesResolver.Log( String.Format( "Target ABIs [{0}], ABIs [{1}] in {2}, will remove [{3}] ABIs", setToString(activeAbisSet), setToString(abisInArchiveSet), aarFile, setToString(abisInArchiveToRemoveSet)), level: LogLevel.Verbose); foreach (var abiToRemove in abisInArchiveToRemoveSet) { abisInArchiveSet.Remove(abiToRemove); FileUtils.DeleteExistingFileOrDirectory(Path.Combine(nativeLibsDir, abiToRemove)); } abis = new AndroidAbis(abisInArchiveSet); } } 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" }); } PlayServicesResolver.Log( String.Format("Creating Ant project: Replacing {0} with {1}", aarFile, outputDir), level: LogLevel.Verbose); // Clean up the aar file. FileUtils.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile)); // Create the output directory. FileUtils.MoveDirectory(workingDir, outputDir); // Add a tracking label to the exploded files. PlayServicesResolver.LabelAssets(new [] { outputDir }); } else { // Add a tracking label to the exploded files just in-case packaging fails. PlayServicesResolver.Log(String.Format("Repacking {0} from {1}", aarFile, workingDir), level: LogLevel.Verbose); // Create a new AAR file. FileUtils.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile)); if (!ArchiveAar(aarFile, workingDir)) { return(false); } PlayServicesResolver.LabelAssets(new [] { aarFile }); } } catch (Exception e) { PlayServicesResolver.Log(String.Format("Failed to process AAR {0} ({1}", aarFile, e), level: LogLevel.Error); } finally { // Clean up the temporary directory. FileUtils.DeleteExistingFileOrDirectory(stagingDir); } return(true); }