Commit 9d03150c authored by UnityMethodPatcher CI's avatar UnityMethodPatcher CI
Browse files

[CI] Changes for 1-10-6

parent 22bfa514
Showing with 172 additions and 73 deletions
+172 -73
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Linq;
using System.Threading.Tasks;
using SingularityGroup.HotReload.Newtonsoft.Json;
using UnityEditor;
using Debug = UnityEngine.Debug;
namespace SingularityGroup.HotReload.Editor.Cli {
[InitializeOnLoad]
......@@ -73,6 +72,12 @@ namespace SingularityGroup.HotReload.Editor.Cli {
}
}
class Config {
#pragma warning disable CS0649
public bool useBuiltInProjectGeneration;
#pragma warning restore CS0649
}
static bool TryGetStartArgs(string dataPath, bool exposeServerToNetwork, out StartArgs args) {
string serverDir;
if(!CliUtils.TryFindServerDir(out serverDir)) {
......@@ -84,6 +89,12 @@ namespace SingularityGroup.HotReload.Editor.Cli {
return false;
}
Config config;
if (File.Exists(PackageConst.ConfigFileName)) {
config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(PackageConst.ConfigFileName));
} else {
config = new Config();
}
var hotReloadTmpDir = CliUtils.GetHotReloadTempDir();
var cliTempDir = CliUtils.GetCliTempDir();
// Versioned path so that we only need to extract the binary once. User can have multiple projects
......@@ -92,7 +103,24 @@ namespace SingularityGroup.HotReload.Editor.Cli {
Directory.CreateDirectory(executableTargetDir); // ensure exists
var executableSourceDir = Path.Combine(serverDir, controller.PlatformName);
var unityProjDir = Path.GetDirectoryName(dataPath);
var slnPath = ProjectGeneration.ProjectGeneration.GetSolutionFilePath(dataPath);
string slnPath;
if (config.useBuiltInProjectGeneration) {
var info = new DirectoryInfo(Path.GetFullPath("."));
slnPath = Path.Combine(Path.GetFullPath("."), info.Name + ".sln");
if (!File.Exists(slnPath)) {
Log.Warning($"Failed to start the Hot Reload Server. Cannot find solution file. Please disable \"useBuiltInProjectGeneration\" in settings to enable custom project generation.");
args = null;
return false;
}
Log.Info("Using default project generation. If you encounter any problem with Unity's default project generation consider disabling it to use custom project generation.");
try {
Directory.Delete(ProjectGeneration.ProjectGeneration.tempDir, true);
} catch(Exception ex) {
Log.Exception(ex);
}
} else {
slnPath = ProjectGeneration.ProjectGeneration.GetSolutionFilePath(dataPath);
}
if (!File.Exists(slnPath)) {
Log.Warning($"No .sln file found. Open any c# file to generate it so Hot Reload can work properly");
......@@ -119,18 +147,17 @@ namespace SingularityGroup.HotReload.Editor.Cli {
await ThreadUtility.SwitchToMainThread();
var dataPath = UnityHelper.DataPath;
if(!File.Exists(ProjectGeneration.ProjectGeneration.GetSolutionFilePath(dataPath))) {
await ProjectGeneration.ProjectGeneration.EnsureSlnAndCsprojFiles(dataPath);
}
await ProjectGeneration.ProjectGeneration.EnsureSlnAndCsprojFiles(dataPath);
await PrepareBuildInfoAsync();
PrepareSystemPathsFile();
}
internal static async Task PrepareBuildInfoAsync() {
await ThreadUtility.SwitchToMainThread();
var buildInfo = BuildInfoHelper.GenerateBuildInfoMainThread();
var buildInfoInput = await BuildInfoHelper.GetGenerateBuildInfoInput();
await Task.Run(() => {
try {
var buildInfo = BuildInfoHelper.GenerateBuildInfoThreaded(buildInfoInput);
PrepareBuildInfo(buildInfo);
} catch {
// ignore, we will warn when making a build
......
......@@ -41,7 +41,7 @@ namespace SingularityGroup.HotReload.Editor {
}
// main thread only
public static IEnumerable<string> GetOmittedProjects(string allDefineSymbols, bool verboseLogs = false) {
public static string[] GetOmittedProjects(string allDefineSymbols, bool verboseLogs = false) {
if (Cache.ommitedProjects != null) {
return Cache.ommitedProjects;
}
......
......@@ -7,47 +7,75 @@ using UnityEditor;
using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
static class BuildInfoHelper {
struct BuildInfoInput {
public readonly string allDefineSymbols;
public readonly BuildTarget activeBuildTarget;
public readonly string[] omittedProjects;
public readonly bool batchMode;
public static Task<BuildInfo> GenerateBuildInfoAsync() {
return Task.FromResult(GenerateBuildInfoMainThread(EditorUserBuildSettings.activeBuildTarget));
public BuildInfoInput(string allDefineSymbols, BuildTarget activeBuildTarget, string[] omittedProjects, bool batchMode) {
this.allDefineSymbols = allDefineSymbols;
this.activeBuildTarget = activeBuildTarget;
this.omittedProjects = omittedProjects;
this.batchMode = batchMode;
}
}
static class BuildInfoHelper {
public static async Task<BuildInfoInput> GetGenerateBuildInfoInput() {
var buildTarget = EditorUserBuildSettings.activeBuildTarget;
var activeDefineSymbols = EditorUserBuildSettings.activeScriptCompilationDefines;
var batchMode = Application.isBatchMode;
var allDefineSymbols = await Task.Run(() => {
return GetAllAndroidMonoBuildDefineSymbolsThreaded(activeDefineSymbols);
});
// cached so unexpensive most of the time
var omittedProjects = AssemblyOmission.GetOmittedProjects(allDefineSymbols);
public static BuildInfo GenerateBuildInfoMainThread() => GenerateBuildInfoMainThread(EditorUserBuildSettings.activeBuildTarget);
return new BuildInfoInput(
allDefineSymbols: allDefineSymbols,
activeBuildTarget: buildTarget,
omittedProjects: omittedProjects,
batchMode: batchMode
);
}
public static BuildInfo GenerateBuildInfoMainThread() {
return GenerateBuildInfoMainThread(EditorUserBuildSettings.activeBuildTarget);
}
public static BuildInfo GenerateBuildInfoMainThread(BuildTarget buildTarget) {
string[] activeDefineSymbols = EditorUserBuildSettings.activeScriptCompilationDefines;
return GenerateBuildInfo(activeDefineSymbols, buildTarget);
var allDefineSymbols = GetAllAndroidMonoBuildDefineSymbolsThreaded(EditorUserBuildSettings.activeScriptCompilationDefines);
return GenerateBuildInfoThreaded(new BuildInfoInput(
allDefineSymbols: allDefineSymbols,
activeBuildTarget: buildTarget,
omittedProjects: AssemblyOmission.GetOmittedProjects(allDefineSymbols),
batchMode: Application.isBatchMode
));
}
/// <param name="activeDefineSymbols">Obtain from Unity main-thread only api EditorUserBuildSettings.activeScriptCompilationDefines</param>
/// <param name="activeBuildTarget"></param>
private static BuildInfo GenerateBuildInfo(string[] activeDefineSymbols, BuildTarget activeBuildTarget) {
public static BuildInfo GenerateBuildInfoThreaded(BuildInfoInput input) {
var omittedProjectRegex = String.Join("|", input.omittedProjects.Select(name => Regex.Escape(name)));
var shortCommitHash = GitUtil.GetShortCommitHashOrFallback();
// expected to be separated by semi-colon ';'
// Assumption your editor is on selected platform which will be connecting to HR server.
// E.g. if you have an Android build, we assume your editor is set to Android platform.
var allDefineSymbols = GetAllAndroidMonoBuildDefineSymbols(activeDefineSymbols);
var omittedProjectRegex = String.Join("|", AssemblyOmission.GetOmittedProjects(allDefineSymbols).Select(name => Regex.Escape(name)));
var hostname = IsHumanControllingUs() ? IpHelper.GetIpAddress() : null;
var buildInfo = new BuildInfo {
var hostname = IsHumanControllingUs(input.batchMode) ? IpHelper.GetIpAddress() : null;
// Note: add a string to uniquely identify the Unity project. Could use filepath to /MyProject/Assets/ (editor Application.dataPath)
// or application identifier (com.company.appname).
// Do this when supporting multiple projects: SG-28807
// The matching code is in Runtime assembly which compares server response with built BuildInfo.
return new BuildInfo {
projectIdentifier = "SG-29580",
commitHash = shortCommitHash,
defineSymbols = allDefineSymbols,
defineSymbols = input.allDefineSymbols,
projectOmissionRegex = omittedProjectRegex,
buildMachineHostName = hostname,
buildMachinePort = RequestHelper.port,
activeBuildTarget = activeBuildTarget.ToString(),
activeBuildTarget = input.activeBuildTarget.ToString(),
};
// Note: add a string to uniquely identify the Unity project. Could use filepath to /MyProject/Assets/ (editor Application.dataPath)
// or application identifier (com.company.appname).
// Do this when supporting multiple projects: SG-28807
// The matching code is in Runtime assembly which compares server response with built BuildInfo.
return buildInfo;
}
public static bool IsHumanControllingUs() {
if (Application.isBatchMode) {
public static bool IsHumanControllingUs(bool batchMode) {
if (batchMode) {
return false;
}
......@@ -103,7 +131,7 @@ namespace SingularityGroup.HotReload.Editor {
// Hardcoding the differences was less effort and is less error prone.
// I also looked into it and tried all the Build interfaces like this one https://docs.unity3d.com/ScriptReference/Build.IPostBuildPlayerScriptDLLs.html
// and logging EditorUserBuildSettings.activeScriptCompilationDefines in the callbacks - result: all same like editor, so I agree that hardcode is best.
public static string GetAllAndroidMonoBuildDefineSymbols(string[] defineSymbols) {
public static string GetAllAndroidMonoBuildDefineSymbolsThreaded(string[] defineSymbols) {
var defines = new HashSet<string>(defineSymbols);
defines.ExceptWith(editorSymbolsToRemove);
defines.UnionWith(androidSymbolsToAdd);
......
......@@ -32,27 +32,32 @@ namespace SingularityGroup.HotReload.Editor {
public static async Task<DownloadResult> DownloadAsync(this HttpClient client, string requestUri, string destinationFilePath, IProgress<float> progress, CancellationToken cancellationToken = default(CancellationToken)) {
// Get the http headers first to examine the content length
using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) {
if(response.StatusCode != HttpStatusCode.OK) {
return new DownloadResult(response.StatusCode, response.ReasonPhrase);
using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false)) {
if (response.StatusCode != HttpStatusCode.OK) {
throw new DownloadException($"Download failed with status code {response.StatusCode} and reason {response.ReasonPhrase}");
}
var contentLength = response.Content.Headers.ContentLength;
if (!contentLength.HasValue) {
throw new DownloadException("Download failed: Content length unknown");
}
using (var fs = new FileStream(destinationFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
using (var download = await response.Content.ReadAsStreamAsync()) {
using (var download = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) {
// Ignore progress reporting when no progress reporter was
// passed or when the content length is unknown
if (progress == null || !contentLength.HasValue) {
await download.CopyToAsync(fs);
return new DownloadResult(HttpStatusCode.OK, null);
if (progress == null) {
await download.CopyToAsync(fs).ConfigureAwait(false);
} else {
// Convert absolute progress (bytes downloaded) into relative progress (0% - 99.9%)
var relativeProgress = new Progress<long>(totalBytes => progress.Report(Math.Min(99.9f, (float)totalBytes / contentLength.Value)));
// Use extension method to report progress while downloading
await download.CopyToAsync(fs, 81920, relativeProgress, cancellationToken).ConfigureAwait(false);
}
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
var relativeProgress = new Progress<long>(totalBytes => progress.Report((float)totalBytes / contentLength.Value));
// Use extension method to report progress while downloading
await download.CopyToAsync(fs, 81920, relativeProgress, cancellationToken);
progress.Report(1);
await fs.FlushAsync().ConfigureAwait(false);
if (fs.Length != contentLength.Value) {
throw new DownloadException("Download failed: download file is corrupted");
}
progress?.Report(1);
return new DownloadResult(HttpStatusCode.OK, null);
}
}
......@@ -79,5 +84,16 @@ namespace SingularityGroup.HotReload.Editor {
progress?.Report(totalBytesRead);
}
}
[Serializable]
public class DownloadException : ApplicationException {
public DownloadException(string message)
: base(message) {
}
public DownloadException(string message, Exception innerException)
: base(message, innerException) {
}
}
}
}
\ No newline at end of file
}
......@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using SingularityGroup.HotReload.DTO;
using SingularityGroup.HotReload.Editor.Cli;
using SingularityGroup.HotReload.Newtonsoft.Json;
using UnityEditor;
......@@ -65,23 +66,39 @@ namespace SingularityGroup.HotReload.Editor {
var tmpPath = CliUtils.GetTempDownloadFilePath("Server.tmp");
var attempt = 0;
bool sucess = false;
HashSet<string> errors = null;
while(!sucess) {
try {
if(File.Exists(targetPath)) {
if (File.Exists(targetPath)) {
Progress = 1f;
return;
}
var result = await DownloadUtility.DownloadFile(GetDownloadUrl(cliController), tmpPath, this, cancellationToken);
// Note: we are writing to temp file so if downloaded file is corrupted it will not cause issues until it's copied to target location
var result = await DownloadUtility.DownloadFile(GetDownloadUrl(cliController), tmpPath, this, cancellationToken).ConfigureAwait(false);
sucess = result.statusCode == HttpStatusCode.OK;
} catch {
//ignored
} catch (Exception e) {
var error = $"{e.GetType().Name}: {e.Message}";
errors = (errors ?? new HashSet<string>());
if (errors.Add(error)) {
Log.Warning($"Download attempt failed. If the issue persists please reach out to customer support for assistance. Exception: {error}");
}
}
if(!sucess) {
if (!sucess) {
await Task.Delay(ExponentialBackoff.GetTimeout(attempt), cancellationToken).ConfigureAwait(false);
}
Progress = 0;
attempt++;
}
if (errors?.Count > 0) {
var data = new EditorExtraData {
{ StatKey.Errors, new List<string>(errors) },
};
// sending telemetry requires server to be running so we only attempt after server is downloaded
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Editor, StatEventType.Download), data).Forget();
Log.Info("Download succeeded!");
}
const int ERROR_ALREADY_EXISTS = 0xB7;
try {
File.Move(tmpPath, targetPath);
......
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
......@@ -15,10 +13,11 @@ using UnityEditor;
using UnityEditor.Compilation;
using UnityEditor.PackageManager;
using UnityEditorInternal;
using UnityEngine;
using Assembly = UnityEditor.Compilation.Assembly;
using Debug = UnityEngine.Debug;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
#if UNITY_2019_1_OR_NEWER
using System.Reflection;
#endif
namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
class ProjectGeneration {
......@@ -33,6 +32,7 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
public HashSet<string> projectBlacklist;
public HashSet<string> polyfillSourceFiles;
public bool excludeAllAnalyzers;
public bool useBuiltInProjectGeneration;
}
public static readonly string MSBuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003";
......@@ -110,7 +110,7 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
public static bool IsSyncing => gate.CurrentCount == 0;
const string tempDir = PackageConst.LibraryCachePath + "/Solution";
internal const string tempDir = PackageConst.LibraryCachePath + "/Solution";
public static string GetUnityProjectDirectory(string dataPath) => new DirectoryInfo(dataPath).Parent.FullName;
public static string GetSolutionFilePath(string dataPath) => Path.Combine(tempDir, Path.GetFileName(GetUnityProjectDirectory(dataPath)) + ".sln");
......@@ -145,6 +145,12 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
}
public async Task Sync() {
await ThreadUtility.SwitchToThreadPool();
var config = LoadConfig();
if (config.useBuiltInProjectGeneration) {
return;
}
await ThreadUtility.SwitchToMainThread();
await gate.WaitAsync();
try {
......@@ -167,12 +173,23 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
BuildEditorAssemblies(),
BuildPostProcessors()
);
await GenerateAndWriteSolutionAndProjects();
await GenerateAndWriteSolutionAndProjects(config);
} finally {
gate.Release();
}
}
private Config LoadConfig() {
var configPath = Path.Combine(m_ProjectDirectory, PackageConst.ConfigFileName);
Config config;
if(File.Exists(configPath)) {
config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(configPath));
} else {
config = new Config();
}
return config;
}
private bool ShouldFileBePartOfSolution(string file) {
// Exclude files coming from packages except if they are internalized.
if (IsInternalizedPackagePath(file)) {
......@@ -197,16 +214,9 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
return k_BuiltinSupportedExtensions.ContainsKey(extension) || m_ProjectSupportedExtensions.Contains(extension);
}
public async Task GenerateAndWriteSolutionAndProjects() {
async Task GenerateAndWriteSolutionAndProjects(Config config) {
await ThreadUtility.SwitchToThreadPool();
var configPath = Path.Combine(m_ProjectDirectory, PackageConst.ConfigFileName);
Config config;
if(File.Exists(configPath)) {
config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(configPath));
} else {
config = new Config();
}
var projectExclusionRegex = config.projectExclusionRegex != null ? new Regex(config.projectExclusionRegex, RegexOptions.Compiled | RegexOptions.Singleline) : null;
var projectBlacklist = config.projectBlacklist ?? new HashSet<string>();
var polyfillSourceFiles = config.polyfillSourceFiles ?? new HashSet<string>();
......@@ -420,8 +430,9 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
}
string[] RetrieveRoslynAnalyzers(ProjectPart assembly, ILookup<string, string> otherResponseFilesData) {
var otherAnalyzers = otherResponseFilesData["a"] ?? Array.Empty<string>();
#if UNITY_2020_2_OR_NEWER
return otherResponseFilesData["analyzer"].Concat(otherResponseFilesData["a"])
return otherResponseFilesData["analyzer"].Concat(otherAnalyzers)
.SelectMany(x=>x.Split(';'))
// #if !ROSLYN_ANALYZER_FIX
// .Concat(GetRoslynAnalyzerPaths())
......@@ -432,7 +443,7 @@ namespace SingularityGroup.HotReload.Editor.ProjectGeneration {
.Distinct()
.ToArray();
#else
return otherResponseFilesData["analyzer"].Concat(otherResponseFilesData["a"])
return otherResponseFilesData["analyzer"].Concat(otherAnalyzers)
.SelectMany(x=>x.Split(';'))
.Distinct()
.Select(MakeAbsolutePath)
......
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
......@@ -9,10 +9,10 @@ namespace SingularityGroup.HotReload {
public static bool LoginNotRequired => IsAssetStoreBuild && !Application.HasProLicense();
public const string Version = "1.10.5";
public const string Version = "1.10.6";
// Never higher than Version
// Used for the download
public const string ServerVersion = "1.10.5";
public const string ServerVersion = "1.10.6";
public const string PackageName = "com.singularitygroup.hotreload";
public const string LibraryCachePath = "Library/" + PackageName;
public const string ConfigFileName = "hot-reload-config.json";
......
......@@ -12,7 +12,7 @@
"sg",
"singularity"
],
"version": "1.10.5",
"version": "1.10.6",
"dependencies": {
"com.unity.ugui": "1.0.0",
"com.unity.modules.unitywebrequest": "1.0.0"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment