public override BuiltNativeProgram DeployTo(NPath targetDirectory, Dictionary <IDeployable, IDeployable> alreadyDeployed = null) { // This is complementary target, library should be deployed to the corresponding folder of the main target // see comment in https://github.com/Unity-Technologies/dots/blob/master/TinySamples/Packages/com.unity.dots.runtime/bee%7E/BuildProgramSources/DotsConfigs.cs // DotsConfigs.MakeConfigs() method for details. var gradleProjectPath = AndroidApkToolchain.ExportProject ? targetDirectory.Combine(m_gameName) : Path.Parent.Parent.Combine("gradle"); var libDirectory = gradleProjectPath.Combine("src/main/jniLibs").Combine(m_libPath); // Deployables with type DeployableFile are deployed with main target Deployables = Deployables.Where(f => !(f is DeployableFile)).ToArray(); var result = base.DeployTo(libDirectory, alreadyDeployed); // Required to make sure that main target Gradle project depends on this lib and this lib is deployed before packaging step Backend.Current.AddDependency(gradleProjectPath.Combine("build.gradle"), result.Path); return(result); }
private NPath PackageApp(NPath buildPath, NPath mainLibPath) { var pbxPath = GenerateXCodeProject(mainLibPath); m_projectFiles.Add(mainLibPath); m_projectFiles.AddRange(Deployables.Select(d => d.Path)); var deployedPath = buildPath.Combine($"{m_gameName}{(IOSAppToolchain.ExportProject ? "" : ".app")}"); if (IOSAppToolchain.ExportProject) { Backend.Current.AddAction( actionName: "Open Xcode project folder", targetFiles: new[] { deployedPath }, inputs: m_projectFiles.ToArray(), executableStringFor: $"open {deployedPath}", commandLineArguments: Array.Empty <string>(), allowUnexpectedOutput: true, allowUnwrittenOutputFiles: true ); } else { var configuration = m_config == DotsConfiguration.Release ? "Release" : "Debug"; var xcodeprojPath = pbxPath.Parent; var outputPath = xcodeprojPath.Parent.Combine("app"); var target = IOSAppToolchain.Config.TargetSettings.SdkVersion == iOSSdkVersion.DeviceSDK ? "iphoneos" : "iphonesimulator"; var appPath = outputPath.Combine("Build", "Products", $"{configuration}-{target}", $"{TinyProjectName}.app"); var appBinaryPath = appPath.Combine("Tiny-iPhone"); var destination = IOSAppToolchain.Config.TargetSettings.SdkVersion == iOSSdkVersion.DeviceSDK ? "generic/platform=iOS": "platform=iOS Simulator,name=iPhone 11"; var xcodeArguments = new List <string> { $"-project {xcodeprojPath.InQuotes()}", $"-configuration {configuration}", $"-derivedDataPath {outputPath.InQuotes()}", $"-destination \"{destination}\"", $"-scheme \"{TinyProjectName}\"", "-allowProvisioningUpdates" }; if (!BuildConfiguration.HasComponent <iOSSigningSettings>()) { var devTeam = Environment.GetEnvironmentVariable("UNITY_TINY_IOS_DEVELOPMENT_TEAM"); if (devTeam != null) { xcodeArguments.Add($"DEVELOPMENT_TEAM={devTeam}"); } var signIdentity = Environment.GetEnvironmentVariable("UNITY_TINY_IOS_SIGN_IDENTITY"); if (signIdentity != null) { xcodeArguments.Add($"CODE_SIGN_IDENTITY=\"{signIdentity}\""); } var provProfile = Environment.GetEnvironmentVariable("UNITY_TINY_IOS_PROVISIONING_PROFILE"); if (provProfile != null) { xcodeArguments.Add($"PROVISIONING_PROFILE_SPECIFIER={provProfile}"); } } Backend.Current.AddAction( actionName: "Build Xcode project", targetFiles: new[] { appBinaryPath }, inputs: m_projectFiles.ToArray(), executableStringFor: IOSAppToolchain.XcodeBuildPath.InQuotes(), commandLineArguments: xcodeArguments.ToArray(), allowUnexpectedOutput: true, allowUnwrittenOutputFiles: true ); m_projectFiles.Add(appBinaryPath); Backend.Current.AddAction( actionName: "Copy application to output folder", targetFiles: new[] { deployedPath }, inputs: m_projectFiles.ToArray(), executableStringFor: $"rm -rf {deployedPath} && cp -R {appPath} {deployedPath}", commandLineArguments: Array.Empty <string>(), allowUnexpectedOutput: true, allowUnwrittenOutputFiles: true ); } return(deployedPath); }
private void GenerateGradleProject(NPath gradleProjectPath) { var gradleSrcPath = AsmDefConfigFile.AsmDefDescriptionFor("Unity.Build.Android.DotsRuntime").Path.Parent.Combine("AndroidProjectTemplate~/"); var hasGradleDependencies = false; var gradleDependencies = new StringBuilder(); gradleDependencies.AppendLine(" dependencies {"); var hasKotlin = false; foreach (var d in Deployables.Where(d => (d is DeployableFile))) { var f = d as DeployableFile; if (f.Path.Extension == "aar" || f.Path.Extension == "jar") { gradleDependencies.AppendLine($" compile(name:'{f.Path.FileNameWithoutExtension}', ext:'{f.Path.Extension}')"); hasGradleDependencies = true; } else if (f.Path.Extension == "kt") { hasKotlin = true; } } if (hasGradleDependencies) { gradleDependencies.AppendLine(" }"); } else { gradleDependencies.Clear(); } var kotlinClassPath = hasKotlin ? " classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.11'" : ""; var kotlinPlugin = hasKotlin ? "apply plugin: 'kotlin-android'" : ""; var loadLibraries = new StringBuilder(); bool useStaticLib = Deployables.FirstOrDefault(l => l.ToString().Contains("lib_unity_tiny_android.so")) == default(IDeployable); if (useStaticLib) { loadLibraries.AppendLine($" System.loadLibrary(\"{m_gameName}\");"); } else { var rx = new Regex(@".*lib([\w\d_]+)\.so", RegexOptions.Compiled); foreach (var l in Deployables) { var match = rx.Match(l.ToString()); if (match.Success) { loadLibraries.AppendLine($" System.loadLibrary(\"{match.Groups[1].Value}\");"); } } } String abiFilters = ""; if (AndroidApkToolchain.Config.Architectures.Architectures == AndroidArchitecture.ARM64) { abiFilters = "'arm64-v8a'"; } else if (AndroidApkToolchain.Config.Architectures.Architectures == AndroidArchitecture.ARMv7) { abiFilters = "'armeabi-v7a'"; } else if (AndroidApkToolchain.IsFatApk) { abiFilters = "'armeabi-v7a', 'arm64-v8a'"; } else // shouldn't happen { Console.WriteLine($"Tiny android toolchain doesn't support {AndroidApkToolchain.Config.Architectures.Architectures.ToString()} architectures"); } // Android docs say "density" value was added in API level 17, but it doesn't compile with target SDK level lower than 24. string configChanges = ((int)AndroidApkToolchain.Config.APILevels.ResolvedTargetAPILevel > 23) ? AndroidConfigChanges + "|density" : AndroidConfigChanges; var useKeystore = BuildConfiguration.HasComponent <AndroidKeystore>(); var renderOutsideSafeArea = BuildConfiguration.HasComponent <AndroidRenderOutsideSafeArea>(); var icons = AndroidApkToolchain.Config.Icons; var hasBackground = icons.Icons.Any(i => !String.IsNullOrEmpty(i.Background)); var hasCustomIcons = hasBackground || icons.Icons.Any(i => !String.IsNullOrEmpty(i.Foreground) || !String.IsNullOrEmpty(i.Legacy)); var version = AndroidApkToolchain.Config.Settings.Version; var versionFieldCount = version.Revision > 0 ? 4 : 3; var maxRatio = AndroidApkToolchain.Config.AspectRatio.GetMaxAspectRatio(AndroidApkToolchain.Config.APILevels.ResolvedTargetAPILevel); var additionalApplicationMetadata = ""; var additionalPermissions = ""; var additionalFeatures = ""; if (!String.IsNullOrEmpty(maxRatio)) { additionalApplicationMetadata += GetMetaDataString("android.max_aspect", maxRatio); } if (BuildConfiguration.HasComponent <ARCoreSettings>()) { additionalPermissions += GetPermissionString("android.permission.CAMERA"); if (AndroidApkToolchain.Config.ARCore.Requirement == Requirement.Optional) { additionalApplicationMetadata += "\n" + GetMetaDataString("com.google.ar.core", "optional"); } else { additionalApplicationMetadata += "\n" + GetMetaDataString("com.google.ar.core", "required"); additionalFeatures += GetFeatureString("android.hardware.camera.ar", true); } if (AndroidApkToolchain.Config.ARCore.DepthSupport == Requirement.Required) { additionalFeatures += "\n" + GetFeatureString("com.google.ar.core.depth", true); } } var templateStrings = new Dictionary <string, string> { { "**LOADLIBRARIES**", loadLibraries.ToString() }, { "**PACKAGENAME**", AndroidApkToolchain.Config.Identifier.PackageName }, { "**PRODUCTNAME**", AndroidApkToolchain.Config.Settings.ProductName }, { "**VERSIONNAME**", version.ToString(versionFieldCount) }, { "**VERSIONCODE**", AndroidApkToolchain.Config.VersionCode.VersionCode.ToString() }, { "**ORIENTATION**", GetOrientationAttr() }, { "**INSTALLLOCATION**", AndroidApkToolchain.Config.InstallLocation?.PreferredInstallLocationAsString() }, { "**CUTOUTMODE**", AndroidRenderOutsideSafeArea.CutoutMode(renderOutsideSafeArea) }, { "**NOTCHCONFIG**", AndroidRenderOutsideSafeArea.NotchConfig(renderOutsideSafeArea) }, { "**NOTCHSUPPORT**", AndroidRenderOutsideSafeArea.NotchSupport(renderOutsideSafeArea) }, { "**GAMENAME**", m_gameName }, { "**MINSDKVERSION**", ((int)AndroidApkToolchain.Config.APILevels.MinAPILevel).ToString() }, { "**TARGETSDKVERSION**", ((int)AndroidApkToolchain.Config.APILevels.ResolvedTargetAPILevel).ToString() }, { "**CONFIGCHANGES**", configChanges }, { "**ACTIVITY_ASPECT**", String.IsNullOrEmpty(maxRatio) ? "" : $"android:maxAspectRatio=\"{maxRatio}\"" }, { "**ADDITIONAL_APPLICATION_METADATA**", additionalApplicationMetadata }, { "**ADDITIONAL_PERMISSIONS**", additionalPermissions }, { "**ADDITIONAL_FEATURES**", additionalFeatures }, { "**ABIFILTERS**", abiFilters }, { "**SIGN**", AndroidApkToolchain.Config.Keystore.GetSigningConfigs(useKeystore) }, { "**SIGNCONFIG**", AndroidApkToolchain.Config.Keystore.GetSigningConfig(useKeystore) }, { "**DEPENDENCIES**", gradleDependencies.ToString() }, { "**KOTLINCLASSPATH**", kotlinClassPath }, { "**KOTLINPLUGIN**", kotlinPlugin }, { "**ALLOWED_PORTRAIT**", AndroidApkToolchain.AllowedOrientationPortrait ? "true" : "false" }, { "**ALLOWED_REVERSE_PORTRAIT**", AndroidApkToolchain.AllowedOrientationReversePortrait ? "true" : "false" }, { "**ALLOWED_LANDSCAPE**", AndroidApkToolchain.AllowedOrientationLandscape ? "true" : "false" }, { "**ALLOWED_REVERSE_LANDSCAPE**", AndroidApkToolchain.AllowedOrientationReverseLandscape ? "true" : "false" }, { "**BACKGROUND_PATH**", hasBackground ? "mipmap" : "drawable" } }; // copy icon files if (hasCustomIcons) { for (int i = 0; i < icons.Icons.Length; ++i) { var dpi = ((ScreenDPI)i).ToString().ToLower(); if (AndroidApkToolchain.Config.APILevels.TargetSDKSupportsAdaptiveIcons) { CopyIcon(gradleProjectPath, dpi, "ic_launcher_foreground.png", icons.Icons[i].Foreground); CopyIcon(gradleProjectPath, dpi, "ic_launcher_background.png", icons.Icons[i].Background); } CopyIcon(gradleProjectPath, dpi, "app_icon.png", icons.Icons[i].Legacy); } } // copy and patch project files var apiRx = new Regex(@".+res[\\|\/].+-v([0-9]+)$", RegexOptions.Compiled); foreach (var r in gradleSrcPath.Files(true)) { if ((hasCustomIcons && r.HasDirectory("mipmap-mdpi")) || (hasBackground && r.HasDirectory("drawable"))) // skipping icons files if there are custom ones { continue; } if (!AndroidApkToolchain.Config.APILevels.TargetSDKSupportsAdaptiveIcons && r.FileName.StartsWith("ic_launcher_")) { continue; } var match = apiRx.Match(r.Parent.ToString()); if (match.Success) { var api = Int32.Parse(match.Groups[1].Value); if (api > (int)AndroidApkToolchain.Config.APILevels.ResolvedTargetAPILevel) { continue; } } var destPath = gradleProjectPath.Combine(r.RelativeTo(gradleSrcPath)); if (r.Extension == "template") { destPath = destPath.ChangeExtension(""); var code = r.ReadAllText(); foreach (var t in templateStrings) { if (code.IndexOf(t.Key) != -1) { code = code.Replace(t.Key, t.Value); } } Backend.Current.AddWriteTextAction(destPath, code); } else { destPath = CopyTool.Instance().Setup(destPath, r); } m_projectFiles.Add(destPath); } var localProperties = new StringBuilder(); localProperties.AppendLine($"sdk.dir={new NPath(AndroidApkToolchain.Config.ExternalTools.SdkPath).ToString()}"); localProperties.AppendLine($"ndk.dir={new NPath(AndroidApkToolchain.Config.ExternalTools.NdkPath).ToString()}"); var localPropertiesPath = gradleProjectPath.Combine("local.properties"); Backend.Current.AddWriteTextAction(localPropertiesPath, localProperties.ToString()); m_projectFiles.Add(localPropertiesPath); }