internal static string ValidatePackageDir(string directory) { // Make sure the package directory starts with the same name. // This is case insensitive to handle cases where developers rename Unity // project directories on Windows (which has a case insensitive file system by // default) then they use the project on OSX / Linux. if (!directory.ToLowerInvariant().StartsWith(AndroidPluginsDir.ToLower())) { directory = AndroidPluginsDir; } directory = FileUtils.NormalizePathSeparators(directory); var searchDirectory = FileUtils.FindDirectoryByCaseInsensitivePath(directory); if (String.IsNullOrEmpty(searchDirectory)) { searchDirectory = directory; } if (directory != searchDirectory && (previouslyValidatedPackageDir == null || searchDirectory != previouslyValidatedPackageDir)) { PlayServicesResolver.Log( String.Format("Resolving to Android package directory {0} instead of the " + "requested target directory {1}\n" + "\n" + "Is {0} in a different case to {1} ?\n", searchDirectory, directory), level: LogLevel.Warning); directory = searchDirectory; } else if ((previouslyValidatedPackageDir == null || searchDirectory != previouslyValidatedPackageDir) && searchDirectory == null) { PlayServicesResolver.Log( String.Format("Android package directory {0} not found.", directory), level: LogLevel.Warning); } previouslyValidatedPackageDir = searchDirectory; return(directory); }
/// <summary> /// Find paths to repositories that are included in the project. /// </summary> /// <param name="dependencies">Dependencies to search for local repositories.</param> /// <returns>Set of repository paths in the project.</returns> public static HashSet <string> FindLocalRepos(ICollection <Dependency> dependencies) { // Find all repositories embedded in the project. var repos = new HashSet <string>(); var projectPath = FileUtils.PosixPathSeparators(Path.GetFullPath(".")); foreach (var reposAndSources in PlayServicesResolver.GetRepos(dependencies: dependencies)) { var repoUri = reposAndSources.Key; if (repoUri.StartsWith(PlayServicesResolver.FILE_SCHEME)) { var fullPath = repoUri.Substring(PlayServicesResolver.FILE_SCHEME.Length); if (fullPath.StartsWith(projectPath)) { repos.Add(fullPath.Substring(projectPath.Length + 1)); } } } return(repos); }
internal static string ValidateLocalMavenRepoDir(string directory) { if (!directory.ToLowerInvariant().StartsWith(FileUtils.ASSETS_FOLDER.ToLower())) { directory = DefaultLocalMavenRepoDir; } directory = FileUtils.NormalizePathSeparators(directory); // TODO: Remove these restrictions // Cannot set to be under "Assets/Plugins/Android". Seems like all .aar and .pom // genereted under this folder will be removed in gradle template mode after // being generated. Need to investigate why. if (directory.StartsWith(AndroidPluginsDir)) { directory = DefaultLocalMavenRepoDir; PlayServicesResolver.Log(String.Format( "Currently LocalMavenRepoDir does not work at any folder " + "under \"Assets/Plugins/Android\""), level: LogLevel.Warning); } if (directory.EndsWith(Path.DirectorySeparatorChar.ToString())) { directory = directory.Substring(0, directory.Length - 1); } return directory; }
/// <summary> /// Inject properties in the gradle properties template file. /// Because of a change in structure of android projects built with /// Unity 2019.3 and above, the correct way to enable jetifier and /// Android X is by updating the gradle properties template. /// </summary> /// <returns>true if successful, false otherwise.</returns> public static bool InjectProperties() { var resolutionMeasurementParameters = PlayServicesResolver.GetResolutionMeasurementParameters(null); PlayServicesResolver.analytics.Report( "/resolve/gradleproperties", resolutionMeasurementParameters, "Gradle Properties Resolve"); var propertiesLines = new List <string>(); // Lines to add Custom Gradle properties template to enable // jetifier and androidx propertiesLines.AddRange(new [] { "android.useAndroidX=true", "android.enableJetifier=true", }); var propertiesFileDescription = String.Format( "gradle properties template" + GradlePropertiesTemplatePath); TextFileLineInjector[] propertiesInjectors = new [] { new TextFileLineInjector(PropertiesInjectionLine, PropertiesStartLine, PropertiesEndLine, propertiesLines, "Properties", propertiesFileDescription) }; if (!PatchFile(GradlePropertiesTemplatePath, propertiesFileDescription, "Gradle Properties", "gradleproperties", propertiesInjectors, resolutionMeasurementParameters)) { PlayServicesResolver.Log( String.Format("Unable to patch " + propertiesFileDescription), level: LogLevel.Error); return(false); } return(true); }
/// <summary> /// Write new contents to a file on disk /// </summary> /// <param name="filePath">Path to file to modify</param> /// <param name="fileDescription">Used in logs for describing the file</param> /// <param name="outputText">Updated contents to write to the file</param> /// <param name="analyticsReportName">Name used in analytics logs</param> /// <param name="analyticsReportUrlToken">Token used in forming analytics path</param> /// <param name="resolutionMeasurementProperties">used in analytics reporting</param> /// <returns>true if successful, false otherwise.</returns> private static bool WriteToFile(string filePath, string fileDescription, string outputText, string analyticsReportName, string analyticsReportUrlToken, ICollection <KeyValuePair <string, string> > resolutionMeasurementParameters) { if (!FileUtils.CheckoutFile(filePath, PlayServicesResolver.logger)) { PlayServicesResolver.Log( String.Format("Failed to checkout '{0}', unable to patch the file.", filePath), level: LogLevel.Error); PlayServicesResolver.analytics.Report( "/resolve/" + analyticsReportUrlToken + "/failed/checkout", analyticsReportName + " Resolve: Failed to checkout"); return(false); } PlayServicesResolver.Log( String.Format("Writing updated {0}", fileDescription), level: LogLevel.Verbose); try { File.WriteAllText(filePath, outputText); PlayServicesResolver.analytics.Report( "/resolve/" + analyticsReportUrlToken + "/success", resolutionMeasurementParameters, analyticsReportName + " Resolve Success"); } catch (Exception ex) { PlayServicesResolver.analytics.Report( "/resolve/" + analyticsReportUrlToken + "/failed/write", analyticsReportName + " Resolve: Failed to write"); PlayServicesResolver.Log( String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), level: LogLevel.Error); return(false); } return(true); }
/// <summary> /// Asynchronously execute a command line tool in this window, showing progress /// and finally calling the specified delegate on completion from the main / UI thread. /// </summary> /// <param name="toolPath">Tool to execute.</param> /// <param name="arguments">String to pass to the tools' command line.</param> /// <param name="completionDelegate">Called when the tool completes.</param> /// <param name="workingDirectory">Directory to execute the tool from.</param> /// <param name="ioHandler">Allows a caller to provide interactive input and also handle /// both output and error streams from a single delegate.</param> /// <param name="maxProgressLines">Specifies the number of lines output by the /// command line that results in a 100% value on a progress bar.</param> /// <returns>Reference to the new window.</returns> public void RunAsync( string toolPath, string arguments, CommandLine.CompletionHandler completionDelegate, string workingDirectory = null, Dictionary <string, string> envVars = null, CommandLine.IOHandler ioHandler = null, int maxProgressLines = 0) { CommandLineDialog.ProgressReporter reporter = new CommandLineDialog.ProgressReporter(); reporter.maxProgressLines = maxProgressLines; // Call the reporter from the UI thread from this window. UpdateEvent += reporter.Update; // Connect the user's delegate to the reporter's completion method. reporter.Complete += completionDelegate; // Connect the caller's IoHandler delegate to the reporter. reporter.DataHandler += ioHandler; // Disconnect the reporter when the command completes. CommandLine.CompletionHandler reporterUpdateDisable = (CommandLine.Result unusedResult) => { this.UpdateEvent -= reporter.Update; }; reporter.Complete += reporterUpdateDisable; PlayServicesResolver.Log(String.Format( "Executing command: {0} {1}", toolPath, arguments), level: LogLevel.Verbose); CommandLine.RunAsync(toolPath, arguments, reporter.CommandLineToolCompletion, workingDirectory: workingDirectory, envVars: envVars, ioHandler: reporter.AggregateLine); }
/// <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)) { PlayServicesResolver.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> /// Install a set of packages. /// </summary> /// <param name="packages">List of package versions to install / upgrade.</param> /// <param name="complete">Called when installation is complete.</param> public void InstallPackages(HashSet <AndroidSdkPackageNameVersion> packages, Action <bool> complete) { var packagesString = AndroidSdkPackageNameVersion.ListToString(packages); // TODO: Remove this dialog when the package manager provides feedback while // downloading. bool installPackage = UnityEditor.EditorUtility.DisplayDialog( "Missing Android SDK packages", String.Format( "Android SDK packages need to be installed:\n" + "{0}\n" + "\n" + "The install process can be *slow* and does not provide any feedback " + "which may lead you to think Unity has hung / crashed. Would you like " + "to wait for these package to be installed?", packagesString), "Yes", cancel: "No"); if (!installPackage) { PlayServicesResolver.Log( "User cancelled installation of Android SDK tools package.", level: LogLevel.Warning); complete(false); return; } var packageNames = new List <string>(); foreach (var pkg in packages) { packageNames.Add(pkg.Name); } SdkManagerUtil.InstallPackages(toolPath, String.Join(" ", packageNames.ToArray()), packages, "Accept? (y/N):", "y", "N", new Regex("^License\\W+[^ ]+:"), complete); }
// 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> /// Copy srcaar files to aar files that are excluded from Unity's build process. /// </summary> /// <param name="dependencies">Dependencies to inject.</param> /// <returns>true if successful, false otherwise.</returns> private static bool CopySrcAars(ICollection <Dependency> dependencies) { bool succeeded = true; var aarFiles = new List <string>(); // Copy each .srcaar file to .aar while configuring the plugin importer to ignore the // file. foreach (var aar in LocalMavenRepository.FindAarsInLocalRepos(dependencies)) { var dir = Path.GetDirectoryName(aar); var filename = Path.GetFileNameWithoutExtension(aar); var targetFilename = Path.Combine(dir, filename + ".aar"); bool configuredAar = File.Exists(targetFilename); if (!configuredAar) { bool copiedAndLabeledAar = AssetDatabase.CopyAsset(aar, targetFilename); if (copiedAndLabeledAar) { var unlabeledAssets = new HashSet <string>(); PlayServicesResolver.LabelAssets( new [] { targetFilename }, complete: (unlabeled) => { unlabeledAssets.UnionWith(unlabeled); }); copiedAndLabeledAar = unlabeledAssets.Count == 0; } if (copiedAndLabeledAar) { try { PluginImporter importer = (PluginImporter)AssetImporter.GetAtPath( targetFilename); importer.SetCompatibleWithAnyPlatform(false); importer.SetCompatibleWithPlatform(BuildTarget.Android, false); configuredAar = true; } catch (Exception ex) { PlayServicesResolver.Log(String.Format( "Failed to disable {0} from being included by Unity's " + "internal build. {0} has been deleted and will not be " + "included in Gradle builds. ({1})", aar, ex), level: LogLevel.Error); } } else { PlayServicesResolver.Log(String.Format( "Unable to copy {0} to {1}. {1} will not be included in Gradle " + "builds.", aar, targetFilename), level: LogLevel.Error); } if (configuredAar) { aarFiles.Add(targetFilename); // Some versions of Unity do not mark the asset database as dirty when // plugin importer settings change so reimport the asset to synchronize // the state. AssetDatabase.ImportAsset(targetFilename, ImportAssetOptions.ForceUpdate); } else { if (File.Exists(targetFilename)) { AssetDatabase.DeleteAsset(targetFilename); } succeeded = false; } } } foreach (var aar in aarFiles) { if (!LocalMavenRepository.PatchPomFile(aar)) { succeeded = false; } } return(succeeded); }
/// <summary> /// Inject / update dependencies in the gradle template file. /// </summary> /// <param name="dependencies">Dependencies to inject.</param> /// <returns>true if successful, false otherwise.</returns> public static bool InjectDependencies(ICollection <Dependency> dependencies) { var resolutionMeasurementParameters = PlayServicesResolver.GetResolutionMeasurementParameters(null); if (dependencies.Count > 0) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate", resolutionMeasurementParameters, "Gradle Template Resolve"); } var fileDescription = String.Format("gradle template {0}", GradleTemplatePath); PlayServicesResolver.Log(String.Format("Reading {0}", fileDescription), level: LogLevel.Verbose); IEnumerable <string> lines; try { lines = File.ReadAllLines(GradleTemplatePath); } catch (Exception ex) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/failed/templateunreadable", "Gradle Template Resolve: Failed Template Unreadable"); PlayServicesResolver.Log( String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), level: LogLevel.Error); return(false); } PlayServicesResolver.Log(String.Format("Searching for {0} in {1}", DependenciesToken, fileDescription), level: LogLevel.Verbose); // Determine whether dependencies should be injected. var dependenciesToken = new Regex(DependenciesToken); bool containsDeps = false; foreach (var line in lines) { if (dependenciesToken.IsMatch(line)) { containsDeps = true; break; } } // If a dependencies token isn't present report a warning and abort. if (!containsDeps) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/failed/noinjectionpoint", "Gradle Template Resolve: Failed No Injection Point"); PlayServicesResolver.Log( String.Format("No {0} token found in {1}, Android Resolver libraries will " + "not be added to the file.", DependenciesToken, fileDescription), level: LogLevel.Warning); return(true); } // Copy all srcaar files in the project to aar filenames so that they'll be included in // the Gradle build. if (!CopySrcAars(dependencies)) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/failed/srcaarcopy", "Gradle Template Resolve: Failed srcaar I/O"); return(false); } var repoLines = new List <string>(); // Optionally enable the jetifier. if (SettingsDialog.UseJetifier && dependencies.Count > 0) { repoLines.AddRange(new [] { "([rootProject] + (rootProject.subprojects as List)).each {", " ext {", " it.setProperty(\"android.useAndroidX\", true)", " it.setProperty(\"android.enableJetifier\", true)", " }", "}" }); } repoLines.AddRange(PlayServicesResolver.GradleMavenReposLines(dependencies)); TextFileLineInjector[] injectors = new [] { new TextFileLineInjector(ReposInjectionLine, ReposStartLine, ReposEndLine, repoLines, "Repos", fileDescription), new TextFileLineInjector(DependenciesToken, DependenciesStartLine, DependenciesEndLine, PlayServicesResolver.GradleDependenciesLines( dependencies, includeDependenciesBlock: false), "Dependencies", fileDescription), new TextFileLineInjector(PackagingOptionsToken, PackagingOptionsStartLine, PackagingOptionsEndLine, PlayServicesResolver.PackagingOptionsLines(dependencies), "Packaging Options", fileDescription), }; // Lines that will be written to the output file. var outputLines = new List <string>(); foreach (var line in lines) { var currentOutputLines = new List <string>(); foreach (var injector in injectors) { bool injectionApplied = false; currentOutputLines = injector.ProcessLine(line, out injectionApplied); if (injectionApplied || currentOutputLines.Count == 0) { break; } } outputLines.AddRange(currentOutputLines); } var inputText = String.Join("\n", (new List <string>(lines)).ToArray()) + "\n"; var outputText = String.Join("\n", outputLines.ToArray()) + "\n"; if (inputText == outputText) { PlayServicesResolver.Log(String.Format("No changes to {0}", fileDescription), level: LogLevel.Verbose); return(true); } if (!FileUtils.CheckoutFile(GradleTemplatePath, PlayServicesResolver.logger)) { PlayServicesResolver.Log( String.Format("Failed to checkout '{0}', unable to patch the file.", GradleTemplatePath), level: LogLevel.Error); PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/failed/checkout", "Gradle Template Resolve: Failed to checkout"); return(false); } PlayServicesResolver.Log( String.Format("Writing updated {0}", fileDescription), level: LogLevel.Verbose); try { File.WriteAllText(GradleTemplatePath, outputText); PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/success", resolutionMeasurementParameters, "Gradle Template Resolve Success"); } catch (Exception ex) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/failed/write", "Gradle Template Resolve: Failed to write"); PlayServicesResolver.Log( String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), level: LogLevel.Error); return(false); } 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 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) { 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"); 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"); 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 activeAbis = new HashSet <string>(); var currentAbi = PlayServicesResolver.AndroidTargetDeviceAbi.ToLower(); foreach (var kv in UNITY_ABI_TO_NATIVE_LIBRARY_ABI_DIRECTORY) { if (currentAbi == kv.Key) { activeAbis.Add(kv.Value); } } if (activeAbis.Count == 0) { activeAbis.UnionWith(UNITY_ABI_TO_NATIVE_LIBRARY_ABI_DIRECTORY.Values); } 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> /// Search for the POM file associated with the specified maven artifact and patch the /// packaging reference if the POM doesn't reference the artifact. /// file. /// </summary> /// <param name="artifactFilename">artifactFilename</param> /// <returns>true if successful, false otherwise.</returns> public static bool PatchPomFile(string artifactFilename) { var failureImpact = String.Format("{0} may not be included in your project", Path.GetFileName(artifactFilename)); var pomFilename = PathWithoutExtension(artifactFilename) + ".pom"; if (!File.Exists(pomFilename)) { PlayServicesResolver.Log( String.Format("Maven POM {0} for {1} does not exist. " + failureImpact, pomFilename, artifactFilename), level: LogLevel.Warning); return(false); } var artifactPackaging = Path.GetExtension(artifactFilename).ToLower().Substring(1); var pom = new XmlDocument(); try { using (var stream = new StreamReader(pomFilename)) { pom.Load(stream); } } catch (Exception ex) { PlayServicesResolver.Log( String.Format("Unable to read maven POM {0} for {1} ({2}). " + failureImpact, pom, artifactFilename, ex), level: LogLevel.Error); return(false); } bool updatedPackaging = false; XmlNodeList packagingNode = pom.GetElementsByTagName("packaging"); foreach (XmlNode node in packagingNode) { if (node.InnerText != artifactPackaging) { PlayServicesResolver.Log(String.Format( "Replacing packaging of maven POM {0} {1} --> {2}", pomFilename, node.InnerText, artifactPackaging), level: LogLevel.Verbose); node.InnerText = artifactPackaging; updatedPackaging = true; } } if (updatedPackaging) { try { using (var xmlWriter = XmlWriter.Create(pomFilename, new XmlWriterSettings { Indent = true, IndentChars = " ", NewLineChars = "\n", NewLineHandling = NewLineHandling.Replace })) { pom.Save(xmlWriter); } } catch (Exception ex) { PlayServicesResolver.Log( String.Format("Unable to write patch maven POM {0} for {1} with " + "packaging {2} ({3}). " + failureImpact, pom, artifactFilename, artifactPackaging, ex)); return(false); } } return(true); }
/// <summary> /// Called when the GUI should be rendered. /// </summary> public void OnGUI() { GUI.skin.label.wordWrap = true; GUILayout.BeginVertical(); GUILayout.BeginHorizontal(); GUILayout.Label("Prebuild With Gradle (Experimental)", EditorStyles.boldLabel); prebuildWithGradle = EditorGUILayout.Toggle(prebuildWithGradle); GUILayout.EndHorizontal(); EditorGUI.BeginDisabledGroup(prebuildWithGradle == true); GUILayout.BeginHorizontal(); GUILayout.Label("Fetch Dependencies with Gradle", EditorStyles.boldLabel); fetchDependenciesWithGradle = EditorGUILayout.Toggle(fetchDependenciesWithGradle); GUILayout.EndHorizontal(); if (fetchDependenciesWithGradle) { GUILayout.Label("AARs are fetched using Gradle which enables assets to be " + "fetched from remote Maven repositories and Gradle version " + "expressions."); } else { GUILayout.Label("Legacy AAR fetching method that only queries the Android SDK " + "manager's local maven repository and user specified local " + "maven repositories for dependencies."); } EditorGUI.EndDisabledGroup(); GUILayout.BeginHorizontal(); GUILayout.Label("Enable Auto-Resolution", EditorStyles.boldLabel); enableAutoResolution = EditorGUILayout.Toggle(enableAutoResolution); GUILayout.EndHorizontal(); EditorGUI.BeginDisabledGroup(prebuildWithGradle == true); GUILayout.BeginHorizontal(); GUILayout.Label("Install Android Packages", EditorStyles.boldLabel); installAndroidPackages = EditorGUILayout.Toggle(installAndroidPackages); GUILayout.EndHorizontal(); if (ConfigurablePackageDir) { GUILayout.BeginHorizontal(); string previousPackageDir = packageDir; GUILayout.Label("Package Directory", EditorStyles.boldLabel); if (GUILayout.Button("Browse")) { string path = EditorUtility.OpenFolderPanel("Set Package Directory", PackageDir, ""); int startOfPath = path.IndexOf(AndroidPluginsDir); if (startOfPath < 0) { packageDir = ""; } else { packageDir = path.Substring(startOfPath, path.Length - startOfPath); } } if (!previousPackageDir.Equals(packageDir)) { packageDir = ValidatePackageDir(packageDir); } GUILayout.EndHorizontal(); packageDir = EditorGUILayout.TextField(packageDir); } GUILayout.BeginHorizontal(); GUILayout.Label("Explode AARs", EditorStyles.boldLabel); explodeAars = EditorGUILayout.Toggle(explodeAars); GUILayout.EndHorizontal(); if (explodeAars) { GUILayout.Label("AARs will be exploded (unpacked) when ${applicationId} " + "variable replacement is required in an AAR's " + "AndroidManifest.xml or a single target ABI is selected " + "without a compatible build system."); } else { GUILayout.Label("AAR explosion will be disabled in exported Gradle builds " + "(Unity 5.5 and above). You will need to set " + "android.defaultConfig.applicationId to your bundle ID in your " + "build.gradle to generate a functional APK."); } EditorGUI.EndDisabledGroup(); EditorGUI.BeginDisabledGroup(enableAutoResolution); GUILayout.BeginHorizontal(); GUILayout.Label("Auto-Resolution Disabled Warning", EditorStyles.boldLabel); autoResolutionDisabledWarning = EditorGUILayout.Toggle(autoResolutionDisabledWarning); GUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); GUILayout.BeginHorizontal(); GUILayout.Label("Verbose Logging", EditorStyles.boldLabel); verboseLogging = EditorGUILayout.Toggle(verboseLogging); GUILayout.EndHorizontal(); GUILayout.Space(10); GUILayout.BeginHorizontal(); bool closeWindow = GUILayout.Button("Cancel"); bool ok = GUILayout.Button("OK"); closeWindow |= ok; if (ok) { PrebuildWithGradle = prebuildWithGradle; FetchDependenciesWithGradle = fetchDependenciesWithGradle; EnableAutoResolution = enableAutoResolution; InstallAndroidPackages = installAndroidPackages; if (ConfigurablePackageDir) { PackageDir = packageDir; } ExplodeAars = explodeAars; VerboseLogging = verboseLogging; AutoResolutionDisabledWarning = autoResolutionDisabledWarning; PlayServicesResolver.OnSettingsChanged(); } if (closeWindow) { Close(); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); }
/// <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); }
/// <summary> /// Called when the GUI should be rendered. /// </summary> public void OnGUI() { GUILayout.BeginVertical(); GUILayout.Label(String.Format("Android Resolver (version {0}.{1}.{2})", AndroidResolverVersionNumber.Value.Major, AndroidResolverVersionNumber.Value.Minor, AndroidResolverVersionNumber.Value.Build)); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); GUI.skin.label.wordWrap = true; GUILayout.BeginVertical(); GUILayout.BeginHorizontal(); GUILayout.Label("Use Gradle Daemon", EditorStyles.boldLabel); settings.useGradleDaemon = EditorGUILayout.Toggle(settings.useGradleDaemon); GUILayout.EndHorizontal(); GUILayout.Label( settings.useGradleDaemon ? ("Gradle Daemon will be used to fetch dependencies. " + "This is faster but can be flakey in some environments.") : ("Gradle Daemon will not be used. This is slow but reliable.")); GUILayout.BeginHorizontal(); GUILayout.Label("Enable Auto-Resolution", EditorStyles.boldLabel); settings.enableAutoResolution = EditorGUILayout.Toggle(settings.enableAutoResolution); GUILayout.EndHorizontal(); GUILayout.Label( settings.enableAutoResolution ? ("Android libraries will be downloaded and processed in the editor.") : ("Android libraries will *not* be downloaded or processed in the editor.")); GUILayout.BeginHorizontal(); GUILayout.Label("Enable Resolution On Build", EditorStyles.boldLabel); settings.autoResolveOnBuild = EditorGUILayout.Toggle(settings.autoResolveOnBuild); GUILayout.EndHorizontal(); GUILayout.Label( settings.autoResolveOnBuild ? ("Android libraries will be downloaded and processed in a pre-build step.") : ("Android libraries will *not* be downloaded or processed in a pre-build step.")); GUILayout.BeginHorizontal(); GUILayout.Label("Install Android Packages", EditorStyles.boldLabel); settings.installAndroidPackages = EditorGUILayout.Toggle(settings.installAndroidPackages); GUILayout.EndHorizontal(); if (ConfigurablePackageDir) { GUILayout.BeginHorizontal(); string previousPackageDir = settings.packageDir; GUILayout.Label("Package Directory", EditorStyles.boldLabel); if (GUILayout.Button("Browse")) { string path = EditorUtility.OpenFolderPanel("Set Package Directory", PackageDir, ""); int startOfPath = path.IndexOf(AndroidPluginsDir); settings.packageDir = startOfPath < 0 ? "" : path.Substring(startOfPath, path.Length - startOfPath);; } if (!previousPackageDir.Equals(settings.packageDir)) { settings.packageDir = ValidatePackageDir(settings.packageDir); } GUILayout.EndHorizontal(); settings.packageDir = EditorGUILayout.TextField(settings.packageDir); } GUILayout.BeginHorizontal(); GUILayout.Label("Explode AARs", EditorStyles.boldLabel); settings.explodeAars = EditorGUILayout.Toggle(settings.explodeAars); GUILayout.EndHorizontal(); if (settings.explodeAars) { GUILayout.Label("AARs will be exploded (unpacked) when ${applicationId} " + "variable replacement is required in an AAR's " + "AndroidManifest.xml or a single target ABI is selected " + "without a compatible build system."); } else { GUILayout.Label("AAR explosion will be disabled in exported Gradle builds " + "(Unity 5.5 and above). You will need to set " + "android.defaultConfig.applicationId to your bundle ID in your " + "build.gradle to generate a functional APK."); } // Disable the ability to toggle the auto-resolution disabled warning // when auto resolution is enabled. EditorGUI.BeginDisabledGroup(settings.enableAutoResolution || settings.autoResolveOnBuild); GUILayout.BeginHorizontal(); GUILayout.Label("Auto-Resolution Disabled Warning", EditorStyles.boldLabel); settings.autoResolutionDisabledWarning = EditorGUILayout.Toggle(settings.autoResolutionDisabledWarning); GUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); // Disable the ability to toggle the auto-resolution disabled warning // when auto resolution is enabled. EditorGUI.BeginDisabledGroup(!settings.enableAutoResolution); GUILayout.BeginHorizontal(); GUILayout.Label("Prompt Before Auto-Resolution", EditorStyles.boldLabel); settings.promptBeforeAutoResolution = EditorGUILayout.Toggle(settings.promptBeforeAutoResolution); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Auto Resolution Delay", EditorStyles.boldLabel); settings.autoResolutionDelay = ClampAutoResolutionDelay( EditorGUILayout.IntField(settings.autoResolutionDelay)); GUILayout.EndHorizontal(); GUILayout.Label("Time, in seconds, to wait before auto-resolution."); EditorGUI.EndDisabledGroup(); GUILayout.BeginHorizontal(); GUILayout.Label("Patch AndroidManifest.xml", EditorStyles.boldLabel); settings.patchAndroidManifest = EditorGUILayout.Toggle(settings.patchAndroidManifest); GUILayout.EndHorizontal(); if (settings.patchAndroidManifest) { GUILayout.Label(String.Format( "Instances of \"applicationId\" variable references will be replaced in " + "{0} with the bundle ID. If the bundle ID " + "is changed the previous bundle ID will be replaced with the new " + "bundle ID by the plugin.\n\n" + "This works around a bug in Unity 2018.x where the " + "\"applicationId\" variable is not replaced correctly.", AndroidManifestPath)); } else { GUILayout.Label(String.Format( "{0} is not modified.\n\n" + "If you're using Unity 2018.x and have an AndroidManifest.xml " + "that uses the \"applicationId\" variable, your build may fail.", AndroidManifestPath)); } GUILayout.BeginHorizontal(); GUILayout.Label("Patch mainTemplate.gradle", EditorStyles.boldLabel); settings.patchMainTemplateGradle = EditorGUILayout.Toggle(settings.patchMainTemplateGradle); GUILayout.EndHorizontal(); if (settings.patchMainTemplateGradle) { GUILayout.Label( "If Gradle builds are enabled and a mainTemplate.gradle file is present, " + "the mainTemplate.gradle file will be patched with dependencies managed " + "by the Android Resolver."); } else { GUILayout.Label(String.Format( "If Gradle builds are enabled and a mainTemplate.gradle file is present, " + "the mainTemplate.gradle file will not be modified. Instead dependencies " + "managed by the Android Resolver will be added to the project under {0}", settings.packageDir)); } GUILayout.BeginHorizontal(); GUILayout.Label("Verbose Logging", EditorStyles.boldLabel); settings.verboseLogging = EditorGUILayout.Toggle(settings.verboseLogging); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Use project settings", EditorStyles.boldLabel); settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); GUILayout.EndHorizontal(); GUILayout.Space(10); if (GUILayout.Button("Reset to Defaults")) { // Load default settings into the dialog but preserve the state in the user's // saved preferences. var backupSettings = new Settings(); RestoreDefaultSettings(); LoadSettings(); backupSettings.Save(); } GUILayout.BeginHorizontal(); bool closeWindow = GUILayout.Button("Cancel"); bool ok = GUILayout.Button("OK"); closeWindow |= ok; if (ok) { settings.Save(); PlayServicesResolver.OnSettingsChanged(); } if (closeWindow) { Close(); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); EditorGUILayout.EndScrollView(); GUILayout.EndVertical(); }
/// <summary> /// Copy srcaar files to aar files that are excluded from Unity's build process. /// </summary> /// <param name="dependencies">Dependencies to inject.</param> /// <returns>true if successful, false otherwise.</returns> private static bool CopySrcAars(ICollection <Dependency> dependencies) { bool succeeded = true; var aarFiles = new List <KeyValuePair <string, string> >(); // Copy each .srcaar file to .aar while configuring the plugin importer to ignore the // file. foreach (var aar in LocalMavenRepository.FindAarsInLocalRepos(dependencies)) { // Only need to copy for .srcaar if (Path.GetExtension(aar).CompareTo(".srcaar") != 0) { continue; } var aarPath = aar; if (FileUtils.IsUnderPackageDirectory(aar)) { // Physical paths work better for copy operations than // logical Unity paths. var physicalPackagePath = FileUtils.GetPackageDirectory(aar, FileUtils.PackageDirectoryType.PhysicalPath); aarPath = FileUtils.ReplaceBaseAssetsOrPackagesFolder( aar, physicalPackagePath); } var dir = FileUtils.ReplaceBaseAssetsOrPackagesFolder( Path.GetDirectoryName(aar), GooglePlayServices.SettingsDialog.LocalMavenRepoDir); var filename = Path.GetFileNameWithoutExtension(aarPath); var targetFilename = Path.Combine(dir, filename + ".aar"); // Avoid situations where we can have a mix of file path // separators based on platform. aarPath = FileUtils.NormalizePathSeparators(aarPath); targetFilename = FileUtils.NormalizePathSeparators( targetFilename); bool configuredAar = File.Exists(targetFilename); if (!configuredAar) { var error = PlayServicesResolver.CopyAssetAndLabel( aarPath, targetFilename); if (String.IsNullOrEmpty(error)) { try { PluginImporter importer = (PluginImporter)AssetImporter.GetAtPath( targetFilename); importer.SetCompatibleWithAnyPlatform(false); importer.SetCompatibleWithPlatform(BuildTarget.Android, false); configuredAar = true; } catch (Exception ex) { PlayServicesResolver.Log(String.Format( "Failed to disable {0} from being included by Unity's " + "internal build. {0} has been deleted and will not be " + "included in Gradle builds. ({1})", aar, ex), level: LogLevel.Error); } } else { PlayServicesResolver.Log(String.Format( "Unable to copy {0} to {1}. {1} will not be included in Gradle " + "builds. Reason: {2}", aarPath, targetFilename, error), level: LogLevel.Error); } } if (configuredAar) { aarFiles.Add(new KeyValuePair <string, string>(aarPath, targetFilename)); // Some versions of Unity do not mark the asset database as dirty when // plugin importer settings change so reimport the asset to synchronize // the state. AssetDatabase.ImportAsset(targetFilename, ImportAssetOptions.ForceUpdate); } else { if (File.Exists(targetFilename)) { AssetDatabase.DeleteAsset(targetFilename); } succeeded = false; } } foreach (var keyValue in aarFiles) { succeeded &= LocalMavenRepository.PatchPomFile(keyValue.Value, keyValue.Key); } return(succeeded); }
static ResolverVer1_1() { PlayServicesResolver.RegisterResolver(new ResolverVer1_1()); }
/// <summary> /// Inject / update dependencies in the gradle template file. /// </summary> /// <param name="dependencies">Dependencies to inject.</param> /// <returns>true if successful, false otherwise.</returns> public static bool InjectDependencies(ICollection <Dependency> dependencies) { var resolutionMeasurementParameters = PlayServicesResolver.GetResolutionMeasurementParameters(null); if (dependencies.Count > 0) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate", resolutionMeasurementParameters, "Gradle Template Resolve"); } var fileDescription = String.Format("gradle template {0}", GradleTemplatePath); PlayServicesResolver.Log(String.Format("Reading {0}", fileDescription), level: LogLevel.Verbose); IEnumerable <string> lines; try { lines = File.ReadAllLines(GradleTemplatePath); } catch (Exception ex) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/failed/templateunreadable", "Gradle Template Resolve: Failed Template Unreadable"); PlayServicesResolver.Log( String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), level: LogLevel.Error); return(false); } PlayServicesResolver.Log(String.Format("Searching for {0} in {1}", DependenciesToken, fileDescription), level: LogLevel.Verbose); // Determine whether dependencies should be injected. var dependenciesToken = new Regex(DependenciesToken); bool containsDeps = false; foreach (var line in lines) { if (dependenciesToken.IsMatch(line)) { containsDeps = true; break; } } // If a dependencies token isn't present report a warning and abort. if (!containsDeps) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/failed/noinjectionpoint", "Gradle Template Resolve: Failed No Injection Point"); PlayServicesResolver.Log( String.Format("No {0} token found in {1}, Android Resolver libraries will " + "not be added to the file.", DependenciesToken, fileDescription), level: LogLevel.Warning); return(true); } // Copy all srcaar files in the project to aar filenames so that they'll be included in // the Gradle build. if (!CopySrcAars(dependencies)) { PlayServicesResolver.analytics.Report( "/resolve/gradletemplate/failed/srcaarcopy", "Gradle Template Resolve: Failed srcaar I/O"); return(false); } var repoLines = new List <string>(); // Optionally enable the jetifier. if (SettingsDialog.UseJetifier && dependencies.Count > 0) { // For Unity versions lower than 2019.3 add jetifier and AndroidX // properties to custom main gradle template if (VersionHandler.GetUnityVersionMajorMinor() < 2019.3f) { repoLines.AddRange(new [] { "([rootProject] + (rootProject.subprojects as List)).each {", " ext {", " it.setProperty(\"android.useAndroidX\", true)", " it.setProperty(\"android.enableJetifier\", true)", " }", "}" }); } } repoLines.AddRange(PlayServicesResolver.GradleMavenReposLines(dependencies)); TextFileLineInjector[] injectors = new [] { new TextFileLineInjector(ReposInjectionLine, ReposStartLine, ReposEndLine, repoLines, "Repos", fileDescription), new TextFileLineInjector(DependenciesToken, DependenciesStartLine, DependenciesEndLine, PlayServicesResolver.GradleDependenciesLines( dependencies, includeDependenciesBlock: false), "Dependencies", fileDescription), new TextFileLineInjector(PackagingOptionsToken, PackagingOptionsStartLine, PackagingOptionsEndLine, PlayServicesResolver.PackagingOptionsLines(dependencies), "Packaging Options", fileDescription), }; return(PatchFile(GradleTemplatePath, fileDescription, "Gradle Template", "gradletemplate", injectors, resolutionMeasurementParameters)); }
/// <summary> /// Called when the GUI should be rendered. /// </summary> public void OnGUI() { GUILayout.BeginVertical(); GUILayout.Label(String.Format("Android Resolver (version {0}.{1}.{2})", AndroidResolverVersionNumber.Value.Major, AndroidResolverVersionNumber.Value.Minor, AndroidResolverVersionNumber.Value.Build)); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); GUI.skin.label.wordWrap = true; GUILayout.BeginVertical(); GUILayout.BeginHorizontal(); GUILayout.Label("Use Gradle Daemon", EditorStyles.boldLabel); settings.useGradleDaemon = EditorGUILayout.Toggle(settings.useGradleDaemon); GUILayout.EndHorizontal(); GUILayout.Label( settings.useGradleDaemon ? ("Gradle Daemon will be used to fetch dependencies. " + "This is faster but can be flakey in some environments.") : ("Gradle Daemon will not be used. This is slow but reliable.")); GUILayout.BeginHorizontal(); GUILayout.Label("Enable Auto-Resolution", EditorStyles.boldLabel); settings.enableAutoResolution = EditorGUILayout.Toggle(settings.enableAutoResolution); GUILayout.EndHorizontal(); GUILayout.Label( settings.enableAutoResolution ? ("Android libraries will be downloaded and processed in the editor.") : ("Android libraries will *not* be downloaded or processed in the editor.")); GUILayout.BeginHorizontal(); GUILayout.Label("Enable Resolution On Build", EditorStyles.boldLabel); settings.autoResolveOnBuild = EditorGUILayout.Toggle(settings.autoResolveOnBuild); GUILayout.EndHorizontal(); GUILayout.Label( settings.autoResolveOnBuild ? ("Android libraries will be downloaded and processed in a pre-build step.") : ("Android libraries will *not* be downloaded or processed in a pre-build step.")); GUILayout.BeginHorizontal(); GUILayout.Label("Install Android Packages", EditorStyles.boldLabel); settings.installAndroidPackages = EditorGUILayout.Toggle(settings.installAndroidPackages); GUILayout.EndHorizontal(); if (ConfigurablePackageDir) { GUILayout.BeginHorizontal(); string previousPackageDir = settings.packageDir; GUILayout.Label("Package Directory", EditorStyles.boldLabel); if (GUILayout.Button("Browse")) { string path = EditorUtility.OpenFolderPanel("Set Package Directory", PackageDir, ""); int startOfPath = path.IndexOf(AndroidPluginsDir); settings.packageDir = FileUtils.PosixPathSeparators( startOfPath < 0 ? "" : path.Substring(startOfPath, path.Length - startOfPath)); } if (!previousPackageDir.Equals(settings.packageDir)) { settings.packageDir = ValidatePackageDir(settings.packageDir); } GUILayout.EndHorizontal(); settings.packageDir = FileUtils.PosixPathSeparators( EditorGUILayout.TextField(settings.packageDir)); } GUILayout.BeginHorizontal(); GUILayout.Label("Explode AARs", EditorStyles.boldLabel); settings.explodeAars = EditorGUILayout.Toggle(settings.explodeAars); GUILayout.EndHorizontal(); if (settings.explodeAars) { GUILayout.Label("AARs will be exploded (unpacked) when ${applicationId} " + "variable replacement is required in an AAR's " + "AndroidManifest.xml or a single target ABI is selected " + "without a compatible build system."); } else { GUILayout.Label("AAR explosion will be disabled in exported Gradle builds " + "(Unity 5.5 and above). You will need to set " + "android.defaultConfig.applicationId to your bundle ID in your " + "build.gradle to generate a functional APK."); } // Disable the ability to toggle the auto-resolution disabled warning // when auto resolution is enabled. EditorGUI.BeginDisabledGroup(settings.enableAutoResolution || settings.autoResolveOnBuild); GUILayout.BeginHorizontal(); GUILayout.Label("Auto-Resolution Disabled Warning", EditorStyles.boldLabel); settings.autoResolutionDisabledWarning = EditorGUILayout.Toggle(settings.autoResolutionDisabledWarning); GUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); // Disable the ability to toggle the auto-resolution disabled warning // when auto resolution is enabled. EditorGUI.BeginDisabledGroup(!settings.enableAutoResolution); GUILayout.BeginHorizontal(); GUILayout.Label("Prompt Before Auto-Resolution", EditorStyles.boldLabel); settings.promptBeforeAutoResolution = EditorGUILayout.Toggle(settings.promptBeforeAutoResolution); GUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); GUILayout.BeginHorizontal(); GUILayout.Label("Patch AndroidManifest.xml", EditorStyles.boldLabel); settings.patchAndroidManifest = EditorGUILayout.Toggle(settings.patchAndroidManifest); GUILayout.EndHorizontal(); if (settings.patchAndroidManifest) { GUILayout.Label(String.Format( "Instances of \"applicationId\" variable references will be replaced in " + "{0} with the bundle ID. If the bundle ID " + "is changed the previous bundle ID will be replaced with the new " + "bundle ID by the plugin.\n\n" + "This works around a bug in Unity 2018.x where the " + "\"applicationId\" variable is not replaced correctly.", AndroidManifestPath)); } else { GUILayout.Label(String.Format( "{0} is not modified.\n\n" + "If you're using Unity 2018.x and have an AndroidManifest.xml " + "that uses the \"applicationId\" variable, your build may fail.", AndroidManifestPath)); } GUILayout.BeginHorizontal(); GUILayout.Label("Patch mainTemplate.gradle", EditorStyles.boldLabel); settings.patchMainTemplateGradle = EditorGUILayout.Toggle(settings.patchMainTemplateGradle); GUILayout.EndHorizontal(); if (settings.patchMainTemplateGradle) { GUILayout.Label( "If Gradle builds are enabled and a mainTemplate.gradle file is present, " + "the mainTemplate.gradle file will be patched with dependencies managed " + "by the Android Resolver."); } else { GUILayout.Label(String.Format( "If Gradle builds are enabled and a mainTemplate.gradle file is present, " + "the mainTemplate.gradle file will not be modified. Instead dependencies " + "managed by the Android Resolver will be added to the project under {0}", settings.packageDir)); } if (settings.patchMainTemplateGradle) { GUILayout.BeginHorizontal(); string previousDir = settings.localMavenRepoDir; GUILayout.Label("Local Maven Repo Directory", EditorStyles.boldLabel); if (GUILayout.Button("Browse")) { string path = EditorUtility.OpenFolderPanel("Set Local Maven Repo Directory", settings.localMavenRepoDir, ""); int startOfPath = path.IndexOf( FileUtils.ASSETS_FOLDER + Path.DirectorySeparatorChar); settings.localMavenRepoDir = FileUtils.PosixPathSeparators( startOfPath < 0 ? DefaultLocalMavenRepoDir : path.Substring(startOfPath, path.Length - startOfPath)); } if (!previousDir.Equals(settings.localMavenRepoDir)) { settings.localMavenRepoDir = ValidateLocalMavenRepoDir(settings.localMavenRepoDir); } GUILayout.EndHorizontal(); GUILayout.Label( "Please pick a folder under Assets folder. Currently it won't work at " + "any folder under \"Assets/Plugins/Android\""); settings.localMavenRepoDir = FileUtils.PosixPathSeparators( ValidateLocalMavenRepoDir(EditorGUILayout.TextField( settings.localMavenRepoDir))); } if (settings.patchMainTemplateGradle) { GUILayout.BeginHorizontal(); GUILayout.Label("Use relative repository path", EditorStyles.boldLabel); settings.useRelativeRepoPath = EditorGUILayout.Toggle(settings.useRelativeRepoPath); GUILayout.EndHorizontal(); if (settings.useRelativeRepoPath) { GUILayout.Label( "The mainTemplate.gradle file will be patched with a relative path to " + "the Local Maven Repository for an exported Android project. This can be used " + "to limit file changes when the template is versioned, but the exported " + "project will not work if relocated after building."); } else { GUILayout.Label( "The mainTemplate.gradle file will be patched with an absolute path to " + "the Local Maven Repository. This ensures that the exported project can be " + "relocated anywhere after building."); } } GUILayout.BeginHorizontal(); GUILayout.Label("Use Jetifier.", EditorStyles.boldLabel); settings.useJetifier = EditorGUILayout.Toggle(settings.useJetifier); GUILayout.EndHorizontal(); if (settings.useJetifier) { GUILayout.Label( "Legacy Android support libraries and references to them from other " + "libraries will be rewritten to use Jetpack using the Jetifier tool. " + "Enabling option allows an application to use Android Jetpack " + "when other libraries in the project use the Android support libraries."); } else { GUILayout.Label( "Class References to legacy Android support libraries (pre-Jetpack) will be " + "left unmodified in the project. This will possibly result in broken Android " + "builds when mixing legacy Android support libraries and Jetpack libraries."); } if (settings.useJetifier) { GUILayout.BeginHorizontal(); GUILayout.Label("Patch gradleTemplate.properties", EditorStyles.boldLabel); settings.patchPropertiesTemplateGradle = EditorGUILayout.Toggle(settings.patchPropertiesTemplateGradle); GUILayout.EndHorizontal(); GUILayout.Label( "For Unity 2019.3 and above, it is recommended to enable Jetifier " + "and AndroidX via gradleTemplate.properties. Please enable " + "Custom Gradle Properties Template' found under 'Player Settings > " + "Settings for Android > Publishing Settings' menu item. " + "This has no effect in older versions of Unity."); } settings.analyticsSettings.RenderGui(); GUILayout.BeginHorizontal(); GUILayout.Label("Verbose Logging", EditorStyles.boldLabel); settings.verboseLogging = EditorGUILayout.Toggle(settings.verboseLogging); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Use project settings", EditorStyles.boldLabel); settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); GUILayout.EndHorizontal(); GUILayout.Space(10); if (GUILayout.Button("Reset to Defaults")) { // Load default settings into the dialog but preserve the state in the user's // saved preferences. var backupSettings = new Settings(); RestoreDefaultSettings(); PlayServicesResolver.analytics.Report("settings/reset", "Settings Reset"); LoadSettings(); backupSettings.Save(); } GUILayout.BeginHorizontal(); bool closeWindow = GUILayout.Button("Cancel"); if (closeWindow) { PlayServicesResolver.analytics.Report("settings/cancel", "Settings Cancel"); } bool ok = GUILayout.Button("OK"); closeWindow |= ok; if (ok) { PlayServicesResolver.analytics.Report( "settings/save", new KeyValuePair <string, string>[] { new KeyValuePair <string, string>( "useGradleDaemon", SettingsDialog.UseGradleDaemon.ToString()), new KeyValuePair <string, string>( "enableAutoResolution", SettingsDialog.EnableAutoResolution.ToString()), new KeyValuePair <string, string>( "installAndroidPackages", SettingsDialog.InstallAndroidPackages.ToString()), new KeyValuePair <string, string>( "explodeAars", SettingsDialog.ExplodeAars.ToString()), new KeyValuePair <string, string>( "patchAndroidManifest", SettingsDialog.PatchAndroidManifest.ToString()), new KeyValuePair <string, string>( "localMavenRepoDir", SettingsDialog.LocalMavenRepoDir.ToString()), new KeyValuePair <string, string>( "useJetifier", SettingsDialog.UseJetifier.ToString()), new KeyValuePair <string, string>( "verboseLogging", SettingsDialog.VerboseLogging.ToString()), new KeyValuePair <string, string>( "autoResolutionDisabledWarning", SettingsDialog.AutoResolutionDisabledWarning.ToString()), new KeyValuePair <string, string>( "promptBeforeAutoResolution", SettingsDialog.PromptBeforeAutoResolution.ToString()), }, "Settings Save"); settings.Save(); PlayServicesResolver.OnSettingsChanged(); } if (closeWindow) { Close(); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); EditorGUILayout.EndScrollView(); GUILayout.EndVertical(); }
/// <summary> /// Search for the POM file associated with the specified maven artifact and patch the /// packaging reference if the POM doesn't reference the artifact. /// file. /// </summary> /// <param name="artifactFilename">artifactFilename</param> /// <param name="sourceFilename">If artifactFilename is copied from a different location, /// pass the original location where POM file lives.</param> /// <returns>true if successful, false otherwise.</returns> public static bool PatchPomFile(string artifactFilename, string sourceFilename) { if (sourceFilename == null) { sourceFilename = artifactFilename; } if (FileUtils.IsUnderPackageDirectory(artifactFilename)) { // File under Packages folder is immutable. PlayServicesResolver.Log( String.Format("Cannot patch POM from Packages directory since it is immutable" + " ({0})", artifactFilename), level: LogLevel.Error); return(false); } var failureImpact = String.Format("{0} may not be included in your project", Path.GetFileName(artifactFilename)); var pomFilename = PathWithoutExtension(artifactFilename) + ".pom"; // Copy POM file if artifact has been copied from a different location as well. if (String.Compare(sourceFilename, artifactFilename) != 0 && !File.Exists(pomFilename)) { var sourcePomFilename = PathWithoutExtension(sourceFilename) + ".pom"; var error = PlayServicesResolver.CopyAssetAndLabel( sourcePomFilename, pomFilename); if (!String.IsNullOrEmpty(error)) { PlayServicesResolver.Log( String.Format("Failed to copy POM from {0} to {1} due to:\n{2}", sourcePomFilename, pomFilename, error), level: LogLevel.Error); return(false); } } var artifactPackaging = Path.GetExtension(artifactFilename).ToLower().Substring(1); var pom = new XmlDocument(); try { using (var stream = new StreamReader(pomFilename)) { pom.Load(stream); } } catch (Exception ex) { PlayServicesResolver.Log( String.Format("Unable to read maven POM {0} for {1} ({2}). " + failureImpact, pom, artifactFilename, ex), level: LogLevel.Error); return(false); } bool updatedPackaging = false; XmlNodeList packagingNode = pom.GetElementsByTagName("packaging"); foreach (XmlNode node in packagingNode) { if (node.InnerText != artifactPackaging) { PlayServicesResolver.Log(String.Format( "Replacing packaging of maven POM {0} {1} --> {2}", pomFilename, node.InnerText, artifactPackaging), level: LogLevel.Verbose); node.InnerText = artifactPackaging; updatedPackaging = true; } } if (!FileUtils.CheckoutFile(pomFilename, PlayServicesResolver.logger)) { PlayServicesResolver.Log( String.Format("Unable to checkout '{0}' to patch the file for inclusion in a " + "Gradle project.", pomFilename), LogLevel.Error); return(false); } if (updatedPackaging) { try { using (var xmlWriter = XmlWriter.Create(pomFilename, new XmlWriterSettings { Indent = true, IndentChars = " ", NewLineChars = "\n", NewLineHandling = NewLineHandling.Replace })) { pom.Save(xmlWriter); } } catch (Exception ex) { PlayServicesResolver.Log( String.Format("Unable to write patch maven POM {0} for {1} with " + "packaging {2} ({3}). " + failureImpact, pom, artifactFilename, artifactPackaging, ex)); return(false); } } 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 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> /// Called when the GUI should be rendered. /// </summary> public void OnGUI() { GUI.skin.label.wordWrap = true; GUILayout.BeginVertical(); GUILayout.BeginHorizontal(); GUILayout.Label("Prebuild With Gradle (Experimental)", EditorStyles.boldLabel); settings.prebuildWithGradle = EditorGUILayout.Toggle(settings.prebuildWithGradle); GUILayout.EndHorizontal(); EditorGUI.BeginDisabledGroup(settings.prebuildWithGradle == true); GUILayout.BeginHorizontal(); GUILayout.Label("Fetch Dependencies with Gradle", EditorStyles.boldLabel); settings.fetchDependenciesWithGradle = EditorGUILayout.Toggle(settings.fetchDependenciesWithGradle); GUILayout.EndHorizontal(); if (settings.fetchDependenciesWithGradle) { GUILayout.Label("AARs are fetched using Gradle which enables assets to be " + "fetched from remote Maven repositories and Gradle version " + "expressions."); } else { GUILayout.Label("Legacy AAR fetching method that only queries the Android SDK " + "manager's local maven repository and user specified local " + "maven repositories for dependencies."); } EditorGUI.BeginDisabledGroup(settings.fetchDependenciesWithGradle == false); GUILayout.BeginHorizontal(); GUILayout.Label("Use Gradle Daemon", EditorStyles.boldLabel); settings.useGradleDaemon = EditorGUILayout.Toggle(settings.useGradleDaemon); GUILayout.EndHorizontal(); GUILayout.Label( settings.useGradleDaemon ? ("Gradle Daemon will be used to fetch dependencies. " + "This is faster but can be flakey in some environments.") : ("Gradle Daemon will not be used. This is slow but reliable.")); EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup(); GUILayout.BeginHorizontal(); GUILayout.Label("Enable Auto-Resolution", EditorStyles.boldLabel); settings.enableAutoResolution = EditorGUILayout.Toggle(settings.enableAutoResolution); GUILayout.EndHorizontal(); EditorGUI.BeginDisabledGroup(settings.prebuildWithGradle == true); GUILayout.BeginHorizontal(); GUILayout.Label("Install Android Packages", EditorStyles.boldLabel); settings.installAndroidPackages = EditorGUILayout.Toggle(settings.installAndroidPackages); GUILayout.EndHorizontal(); if (ConfigurablePackageDir) { GUILayout.BeginHorizontal(); string previousPackageDir = settings.packageDir; GUILayout.Label("Package Directory", EditorStyles.boldLabel); if (GUILayout.Button("Browse")) { string path = EditorUtility.OpenFolderPanel("Set Package Directory", PackageDir, ""); int startOfPath = path.IndexOf(AndroidPluginsDir); settings.packageDir = startOfPath < 0 ? "" : path.Substring(startOfPath, path.Length - startOfPath);; } if (!previousPackageDir.Equals(settings.packageDir)) { settings.packageDir = ValidatePackageDir(settings.packageDir); } GUILayout.EndHorizontal(); settings.packageDir = EditorGUILayout.TextField(settings.packageDir); } GUILayout.BeginHorizontal(); GUILayout.Label("Explode AARs", EditorStyles.boldLabel); settings.explodeAars = EditorGUILayout.Toggle(settings.explodeAars); GUILayout.EndHorizontal(); if (settings.explodeAars) { GUILayout.Label("AARs will be exploded (unpacked) when ${applicationId} " + "variable replacement is required in an AAR's " + "AndroidManifest.xml or a single target ABI is selected " + "without a compatible build system."); } else { GUILayout.Label("AAR explosion will be disabled in exported Gradle builds " + "(Unity 5.5 and above). You will need to set " + "android.defaultConfig.applicationId to your bundle ID in your " + "build.gradle to generate a functional APK."); } EditorGUI.EndDisabledGroup(); EditorGUI.BeginDisabledGroup(settings.enableAutoResolution); GUILayout.BeginHorizontal(); GUILayout.Label("Auto-Resolution Disabled Warning", EditorStyles.boldLabel); settings.autoResolutionDisabledWarning = EditorGUILayout.Toggle(settings.autoResolutionDisabledWarning); GUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); GUILayout.BeginHorizontal(); GUILayout.Label("Verbose Logging", EditorStyles.boldLabel); settings.verboseLogging = EditorGUILayout.Toggle(settings.verboseLogging); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Use project settings", EditorStyles.boldLabel); settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); GUILayout.EndHorizontal(); GUILayout.Space(10); if (GUILayout.Button("Reset to Defaults")) { // Load default settings into the dialog but preserve the state in the user's // saved preferences. var backupSettings = new Settings(); RestoreDefaultSettings(); LoadSettings(); backupSettings.Save(); } GUILayout.BeginHorizontal(); bool closeWindow = GUILayout.Button("Cancel"); bool ok = GUILayout.Button("OK"); closeWindow |= ok; if (ok) { settings.Save(); PlayServicesResolver.OnSettingsChanged(); } if (closeWindow) { Close(); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); }
/// <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) { PlayServicesResolver.Log(String.Format("ProcessAar {0} {1} antProject={2}", dir, aarFile, antProject), level: LogLevel.Verbose); abi = null; string workingDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(aarFile)); FileUtils.DeleteExistingFileOrDirectory(workingDir); Directory.CreateDirectory(workingDir); if (!ExtractAar(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); } 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) { FileUtils.CopyDirectory(jniLibDir, nativeLibsDir); FileUtils.DeleteExistingFileOrDirectory(jniLibDir); } // 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)) { FileUtils.DeleteExistingFileOrDirectory(directory); } } 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. 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 }); // 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> /// Inject / update dependencies in the gradle template file. /// </summary> /// <param name="dependencies">Dependencies to inject.</param> /// <returns>true if successful, false otherwise.</returns> public static bool InjectDependencies(ICollection <Dependency> dependencies) { var fileDescription = String.Format("gradle template {0}", GradleTemplatePath); PlayServicesResolver.Log(String.Format("Reading {0}", fileDescription), level: LogLevel.Verbose); IEnumerable <string> lines; try { lines = File.ReadAllLines(GradleTemplatePath); } catch (Exception ex) { PlayServicesResolver.Log( String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), level: LogLevel.Error); return(false); } PlayServicesResolver.Log(String.Format("Searching for {0} in {1}", DependenciesToken, fileDescription), level: LogLevel.Verbose); // Determine whether dependencies should be injected. bool containsDeps = false; foreach (var line in lines) { if (line.Contains(DependenciesToken)) { containsDeps = true; break; } } // If a dependencies token isn't present report a warning and abort. if (!containsDeps) { PlayServicesResolver.Log( String.Format("No {0} token found in {1}, Android Resolver libraries will " + "not be added to the file.", DependenciesToken, fileDescription), level: LogLevel.Warning); return(true); } // Copy all srcaar files in the project to aar filenames so that they'll be included in // the Gradle build. if (!CopySrcAars(dependencies)) { return(false); } TextFileLineInjector[] injectors = new [] { new TextFileLineInjector(ReposInjectionLine, ReposStartLine, ReposEndLine, PlayServicesResolver.GradleMavenReposLines(dependencies), "Repos", fileDescription), new TextFileLineInjector(DependenciesToken, DependenciesStartLine, DependenciesEndLine, PlayServicesResolver.GradleDependenciesLines( dependencies, includeDependenciesBlock: false), "Dependencies", fileDescription) }; // Lines that will be written to the output file. var outputLines = new List <string>(); foreach (var line in lines) { var currentOutputLines = new List <string>(); foreach (var injector in injectors) { bool injectionApplied = false; currentOutputLines = injector.ProcessLine(line, out injectionApplied); if (injectionApplied || currentOutputLines.Count == 0) { break; } } outputLines.AddRange(currentOutputLines); } PlayServicesResolver.Log( String.Format("Writing updated {0}", fileDescription), level: LogLevel.Verbose); try { File.WriteAllText(GradleTemplatePath, String.Join("\n", outputLines.ToArray()) + "\n"); } catch (Exception ex) { PlayServicesResolver.Log( String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), level: LogLevel.Error); return(false); } return(true); }