first commit

This commit is contained in:
Chris
2025-03-12 14:22:16 -04:00
commit 0ad0c01249
1999 changed files with 189708 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
using System;
using System.IO;
using UnityEditor;
using UnityEditor.Build;
#pragma warning disable CS0618
namespace SingularityGroup.HotReload.Editor {
public class BuildGenerateBuildInfo : IPreprocessBuild, IPostprocessBuild {
public int callbackOrder => 10;
public void OnPreprocessBuild(BuildTarget target, string path) {
try {
if (!HotReloadBuildHelper.IncludeInThisBuild()) {
return;
}
// write BuildInfo json into the StreamingAssets directory
GenerateBuildInfo(BuildInfo.GetStoredPath(), target);
} catch (BuildFailedException) {
throw;
} catch (Exception e) {
throw new BuildFailedException(e);
}
}
private static void GenerateBuildInfo(string buildFilePath, BuildTarget buildTarget) {
var buildInfo = BuildInfoHelper.GenerateBuildInfoMainThread(buildTarget);
// write to StreamingAssets
// create StreamingAssets folder if not exists (in-case project has no StreamingAssets files)
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(buildFilePath));
File.WriteAllText(buildFilePath, buildInfo.ToJson());
}
public void OnPostprocessBuild(BuildTarget target, string path) {
try {
File.Delete(BuildInfo.GetStoredPath());
} catch {
// ignore
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 178df48ca88b4cddac448a49196b49bf
timeCreated: 1682338738

View File

@@ -0,0 +1,106 @@
using System;
using System.IO;
using UnityEditor;
namespace SingularityGroup.HotReload.Editor {
internal static class HotReloadBuildHelper {
/// <summary>
/// Should HotReload runtime be included in the current build?
/// </summary>
public static bool IncludeInThisBuild() {
return IsAllBuildSettingsSupported();
}
/// <summary>
/// Get scripting backend for the current platform.
/// </summary>
/// <returns>Scripting backend</returns>
public static ScriptingImplementation GetCurrentScriptingBackend() {
#pragma warning disable CS0618
return PlayerSettings.GetScriptingBackend(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget));
#pragma warning restore CS0618
}
public static ManagedStrippingLevel GetCurrentStrippingLevel() {
#pragma warning disable CS0618
return PlayerSettings.GetManagedStrippingLevel(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget));
#pragma warning restore CS0618
}
public static void SetCurrentScriptingBackend(ScriptingImplementation to) {
#pragma warning disable CS0618
// only set it if default is not correct (avoid changing ProjectSettings when not needed)
if (GetCurrentScriptingBackend() != to) {
PlayerSettings.SetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup, to);
}
#pragma warning restore CS0618
}
public static void SetCurrentStrippingLevel(ManagedStrippingLevel to) {
#pragma warning disable CS0618
// only set it if default is not correct (avoid changing ProjectSettings when not needed)
if (GetCurrentStrippingLevel() != to) {
PlayerSettings.SetManagedStrippingLevel(EditorUserBuildSettings.selectedBuildTargetGroup, to);
}
#pragma warning restore CS0618
}
/// Is the current build target supported?
/// main thread only
public static bool IsBuildTargetSupported() {
var buildTarget = EditorUserBuildSettings.selectedBuildTargetGroup;
return Array.IndexOf(unsupportedBuildTargets, buildTarget) == -1;
}
/// Are all the settings supported?
/// main thread only
static bool IsAllBuildSettingsSupported() {
if (!IsBuildTargetSupported()) {
return false;
}
// need way to give it settings object, dont want to give serializedobject
var options = HotReloadSettingsEditor.LoadSettingsOrDefault();
var so = new SerializedObject(options);
// check all projeect options
foreach (var option in HotReloadSettingsTab.allOptions) {
var projectOption = option as ProjectOptionBase;
if (projectOption != null) {
// if option is required, build can't use hot reload
if (projectOption.IsRequiredForBuild() && !projectOption.GetValue(so)) {
return false;
}
}
}
return GetCurrentScriptingBackend() == ScriptingImplementation.Mono2x
&& GetCurrentStrippingLevel() == ManagedStrippingLevel.Disabled
&& EditorUserBuildSettings.development;
}
/// <summary>
/// Some platforms are not supported because they don't have Mono scripting backend.
/// </summary>
/// <remarks>
/// Only list the platforms that definately don't have Mono scripting.
/// </remarks>
private static readonly BuildTargetGroup[] unsupportedBuildTargets = new [] {
BuildTargetGroup.iOS, // mono support was removed many years ago
BuildTargetGroup.WebGL, // has never had mono
};
public static bool IsMonoSupported(BuildTargetGroup buildTarget) {
// "When a platform can support both backends, Mono is the default. For more information, see Scripting restrictions."
// Unity docs https://docs.unity3d.com/Manual/Mono.html (2019.4/2020.3/2021.3)
#pragma warning disable CS0618 // obsolete since 2023
var defaultScripting = PlayerSettings.GetDefaultScriptingBackend(buildTarget);
#pragma warning restore CS0618
if (defaultScripting == ScriptingImplementation.Mono2x) {
return Array.IndexOf(unsupportedBuildTargets, buildTarget) == -1;
}
// default scripting was not Mono, so the platform doesn't support Mono at all.
return false;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b9aa611f02544b609c5b29f9d1409d6e
timeCreated: 1674041425

View File

@@ -0,0 +1,133 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor.Android;
using UnityEditor.Build;
namespace SingularityGroup.HotReload.Editor {
#pragma warning disable CS0618
/// <remarks>
/// <para>
/// This class sets option in the AndroidManifest that you choose in HotReload build settings.
/// </para>
/// <para>
/// - To connect to the HotReload server through the local network, we need to permit access to http://192...<br/>
/// - Starting with Android 9, insecure http requests are not allowed by default and must be whitelisted
/// </para>
/// </remarks>
internal class PostbuildModifyAndroidManifest : IPostGenerateGradleAndroidProject {
#pragma warning restore CS0618
public int callbackOrder => 10;
private const string manifestFileName = "AndroidManifest.xml";
public void OnPostGenerateGradleAndroidProject(string path) {
try {
if (!HotReloadBuildHelper.IncludeInThisBuild()) {
return;
}
// Note: in future we may support users with custom configuration for usesCleartextTraffic
#if UNITY_2022_1_OR_NEWER
// Unity 2022 or newer → do nothing, we rely on Unity option to control the flag
#else
// Unity 2021 or older → put manifest flag in if Unity is making a Development Build
var manifestFilePath = FindAndroidManifest(path);
if (manifestFilePath == null) {
throw new BuildFailedException($"[{CodePatcher.TAG}] Unable to find {manifestFileName}");
}
SetUsesCleartextTraffic(manifestFilePath);
#endif
} catch (BuildFailedException) {
throw;
} catch (Exception e) {
throw new BuildFailedException(e);
}
}
/// identifier that is used in the deeplink uri scheme
/// (initially tried Application.identifier, but that was giving unexpected results based on PlayerSettings)
// SG-29580
// Something to uniqly identify the application, but it must be something which is highly likely
// to be the same at build time (studio might have logic to set e.g. product name to MyGameProd or MyGameTest)
public static string ApplicationIdentiferSlug => "app";
/*
public static string ApplicationIdentiferSlug => Regex.Replace(ApplicationIdentifer, @"[^a-zA-Z0-9\.\-]", "")
.Replace("..", ".") // happens if your companyname in Unity ends with a dot
.ToLowerInvariant();
private static void AddDeeplinkForwarder(string manifestFilePath) {
// add the hotreload-${identifier} uri scheme to the AndroidManifest.xml file
// it should be added as part of an intent-filter for the activity "com.singularitygroup.deeplinkforwarder.DeepLinkForwarderActivity"
var contents = File.ReadAllText(manifestFilePath);
if (contents.Contains("android:name=\"com.singularitygroup.deeplinkforwarder.DeepLinkForwarderActivity\"")) {
// user has already set this themselves, don't replace it
return;
}
//note: not using android:host or any other data attr because android still shows a chooser for all ur hotreload apps
// Therefore must use a unique uri scheme to ensure only one app can handle it.
var activityWithIntentFilter = @"
<activity android:name=""com.singularitygroup.deeplinkforwarder.DeepLinkForwarderActivity"">
<intent-filter>
<action android:name=""android.intent.action.VIEW"" />
<category android:name=""android.intent.category.DEFAULT"" />
<category android:name=""android.intent.category.BROWSABLE"" />
<data android:scheme=""hotreload-" + ApplicationIdentiferSlug + @""" />
</intent-filter>
</activity>";
var newContents = Regex.Replace(contents,
@"</application>",
activityWithIntentFilter + "\n </application>"
);
File.WriteAllText(manifestFilePath, newContents);
}
*/
// Assume unityLibraryPath is to {gradleProject}/unityLibrary/ which is roughly the same across Unity versions 2018/2019/2020/2021/2022
private static string FindAndroidManifest(string unityLibraryPath) {
// find the AndroidManifest.xml file which we can edit
var dir = new DirectoryInfo(unityLibraryPath);
var manifestFilePath = Path.Combine(dir.FullName, "src", "main", manifestFileName);
if (File.Exists(manifestFilePath)) {
return manifestFilePath;
}
Log.Info("Did not find {0} at {1}, searching for manifest file inside {2}", manifestFileName, manifestFilePath, dir.FullName);
var manifestFiles = dir.GetFiles(manifestFileName, SearchOption.AllDirectories);
if (manifestFiles.Length == 0) {
return null;
}
foreach (var file in manifestFiles) {
if (file.FullName.Contains("src")) {
// good choice
return file.FullName;
}
}
// fallback to the first file found
return manifestFiles[0].FullName;
}
/// <summary>
/// Set option android:usesCleartextTraffic="true"
/// </summary>
/// <param name="manifestFilePath">Absolute filepath to the unityLibrary AndroidManifest.xml file</param>
private static void SetUsesCleartextTraffic(string manifestFilePath) {
// Ideally we would create or modify a "Network Security Configuration file" to permit access to local ip addresses
// https://developer.android.com/training/articles/security-config#manifest
// but that becomes difficult when the user has their own configuration file - would need to search for it and it may be inside an aar.
var contents = File.ReadAllText(manifestFilePath);
if (contents.Contains("android:usesCleartextTraffic=")) {
// user has already set this themselves, don't replace it
return;
}
var newContents = Regex.Replace(contents,
@"<application\s",
"<application android:usesCleartextTraffic=\"true\" "
);
newContents += $"\n<!-- [{CodePatcher.TAG}] Added android:usesCleartextTraffic=\"true\" to permit connecting to the Hot Reload http server running on your machine. -->";
newContents += $"\n<!-- [{CodePatcher.TAG}] This change only happens in Unity development builds. You can disable this in the Hot Reload settings window. -->";
File.WriteAllText(manifestFilePath, newContents);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1949292efc07445ea4c040d544e2d369
timeCreated: 1675441886

View File

@@ -0,0 +1,26 @@
using System;
using SingularityGroup.HotReload.Editor.Cli;
using UnityEditor;
using UnityEditor.Build;
namespace SingularityGroup.HotReload.Editor {
#pragma warning disable CS0618
class PostbuildSendProjectState : IPostprocessBuild {
#pragma warning restore CS0618
public int callbackOrder => 9999;
public void OnPostprocessBuild(BuildTarget target, string path) {
try {
if (!HotReloadBuildHelper.IncludeInThisBuild()) {
return;
}
// after build passes, need to send again because EditorApplication.delayCall isn't called.
var buildInfo = BuildInfoHelper.GenerateBuildInfoMainThread();
HotReloadCli.PrepareBuildInfo(buildInfo);
} catch (BuildFailedException) {
throw;
} catch (Exception e) {
throw new BuildFailedException(e);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3b27b9eab16f78f448477e546fd5eb97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,60 @@
using System;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
/// <summary>Includes HotReload Resources only in development builds</summary>
/// <remarks>
/// This build script ensures that HotReload Resources are not included in release builds.
/// <para>
/// When HotReload is enabled:<br/>
/// - include HotReloadSettingsObject in development Android builds.<br/>
/// - exclude HotReloadSettingsObject from the build.<br/>
/// When HotReload is disabled:<br/>
/// - excludes HotReloadSettingsObject from the build.<br/>
/// </para>
/// </remarks>
#pragma warning disable CS0618
internal class PrebuildIncludeResources : IPreprocessBuild, IPostprocessBuild {
#pragma warning restore CS0618
public int callbackOrder => 10;
// Preprocess warnings don't show up in console
bool warnSettingsNotSupported;
public void OnPreprocessBuild(BuildTarget target, string path) {
try {
if (HotReloadBuildHelper.IncludeInThisBuild()) {
// move scriptable object into Resources/ folder
HotReloadSettingsEditor.AddOrRemoveFromBuild(true);
} else {
// make sure HotReload resources are not in the build
HotReloadSettingsEditor.AddOrRemoveFromBuild(false);
var options = HotReloadSettingsEditor.LoadSettingsOrDefault();
var so = new SerializedObject(options);
if (IncludeInBuildOption.I.GetValue(so)) {
warnSettingsNotSupported = true;
}
}
} catch (BuildFailedException) {
throw;
} catch (Exception ex) {
throw new BuildFailedException(ex);
}
}
public void OnPostprocessBuild(BuildTarget target, string path) {
if (warnSettingsNotSupported) {
Debug.LogWarning("Hot Reload was not included in the build because one or more build settings were not supported.");
}
}
// Do nothing in post build. settings asset will be dirty if build fails, so not worth fixing just for successful builds.
// [PostProcessBuild]
// private static void PostBuild(BuildTarget target, string pathToBuiltProject) {
// }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a63dd2d10359e94a8a3c24bf59164fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: