com.elestrago.unity.generated-code-dump 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/CHANGELOG.md.meta +7 -0
- package/Documentation~/api.md +583 -0
- package/Documentation~/examples/dump-in-batch-mode.md +43 -0
- package/Documentation~/examples/dump-now.md +29 -0
- package/Documentation~/examples/read-the-dump.md +63 -0
- package/Documentation~/examples/turn-on-the-dump.md +33 -0
- package/Documentation~/examples.md +24 -0
- package/Documentation~/manual.md +618 -0
- package/Editor/AssemblyInfo.cs +3 -0
- package/Editor/AssemblyInfo.cs.meta +2 -0
- package/Editor/GeneratedCodeDump.Editor.asmdef +16 -0
- package/Editor/GeneratedCodeDump.Editor.asmdef.meta +7 -0
- package/Editor/GeneratedCodeDumpArgs.cs +113 -0
- package/Editor/GeneratedCodeDumpArgs.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpFiles.cs +120 -0
- package/Editor/GeneratedCodeDumpFiles.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpFinisher.cs +103 -0
- package/Editor/GeneratedCodeDumpFinisher.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpIdentity.cs +40 -0
- package/Editor/GeneratedCodeDumpIdentity.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpJobs.cs +242 -0
- package/Editor/GeneratedCodeDumpJobs.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpMigration.cs +112 -0
- package/Editor/GeneratedCodeDumpMigration.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpNames.cs +104 -0
- package/Editor/GeneratedCodeDumpNames.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpOutput.cs +449 -0
- package/Editor/GeneratedCodeDumpOutput.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpPathBudget.cs +67 -0
- package/Editor/GeneratedCodeDumpPathBudget.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpPaths.cs +145 -0
- package/Editor/GeneratedCodeDumpPaths.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpProcess.cs +119 -0
- package/Editor/GeneratedCodeDumpProcess.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpRsp.cs +107 -0
- package/Editor/GeneratedCodeDumpRsp.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpSession.cs +65 -0
- package/Editor/GeneratedCodeDumpSession.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpSettings.cs +111 -0
- package/Editor/GeneratedCodeDumpSettings.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpSettingsFile.cs +231 -0
- package/Editor/GeneratedCodeDumpSettingsFile.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpSettingsProvider.cs +93 -0
- package/Editor/GeneratedCodeDumpSettingsProvider.cs.meta +2 -0
- package/Editor/GeneratedCodeDumpSettingsWatcher.cs +199 -0
- package/Editor/GeneratedCodeDumpSettingsWatcher.cs.meta +2 -0
- package/Editor/GeneratedCodeDumper.cs +689 -0
- package/Editor/GeneratedCodeDumper.cs.meta +2 -0
- package/Editor/GeneratedCodeMenuItems.cs +46 -0
- package/Editor/GeneratedCodeMenuItems.cs.meta +2 -0
- package/Editor.meta +8 -0
- package/LICENSE.md +21 -0
- package/LICENSE.md.meta +7 -0
- package/README.md +164 -0
- package/README.md.meta +7 -0
- package/package.json +31 -0
- package/package.json.meta +7 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Text;
|
|
3
|
+
|
|
4
|
+
namespace GeneratedCodeDump.Editor
|
|
5
|
+
{
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// How long a dump path may get on one OS: the whole file path, a folder path and one name. Windows
|
|
8
|
+
/// counts UTF-16 characters (MAX_PATH 260 with the NUL, and 12 fewer for a folder); macOS and Linux count
|
|
9
|
+
/// UTF-8 bytes (PATH_MAX 1024 on macOS, used for Linux too).
|
|
10
|
+
/// </summary>
|
|
11
|
+
/// <remarks>
|
|
12
|
+
/// Passed in rather than read, so tests can use small budgets. <see cref="Current"/> uses no Unity API,
|
|
13
|
+
/// so any thread may read it.
|
|
14
|
+
/// </remarks>
|
|
15
|
+
internal readonly struct GeneratedCodeDumpPathBudget
|
|
16
|
+
{
|
|
17
|
+
internal static readonly GeneratedCodeDumpPathBudget Windows = new GeneratedCodeDumpPathBudget(259, 247, 255, false);
|
|
18
|
+
internal static readonly GeneratedCodeDumpPathBudget Unix = new GeneratedCodeDumpPathBudget(1023, 1023, 255, true);
|
|
19
|
+
|
|
20
|
+
internal static GeneratedCodeDumpPathBudget Current =>
|
|
21
|
+
Environment.OSVersion.Platform == PlatformID.Win32NT ? Windows : Unix;
|
|
22
|
+
|
|
23
|
+
/// <summary>
|
|
24
|
+
/// Longest full path of a file.
|
|
25
|
+
/// </summary>
|
|
26
|
+
internal int File { get; }
|
|
27
|
+
|
|
28
|
+
/// <summary>
|
|
29
|
+
/// Longest full path of a folder.
|
|
30
|
+
/// </summary>
|
|
31
|
+
internal int Folder { get; }
|
|
32
|
+
|
|
33
|
+
/// <summary>
|
|
34
|
+
/// Longest single file or folder name.
|
|
35
|
+
/// </summary>
|
|
36
|
+
internal int Name { get; }
|
|
37
|
+
|
|
38
|
+
/// <summary>
|
|
39
|
+
/// True when lengths are counted in UTF-8 bytes, false for UTF-16 characters.
|
|
40
|
+
/// </summary>
|
|
41
|
+
internal bool Utf8Bytes { get; }
|
|
42
|
+
|
|
43
|
+
internal GeneratedCodeDumpPathBudget(int file, int folder, int name, bool utf8Bytes)
|
|
44
|
+
{
|
|
45
|
+
File = file;
|
|
46
|
+
Folder = folder;
|
|
47
|
+
Name = name;
|
|
48
|
+
Utf8Bytes = utf8Bytes;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
internal int Length(string text)
|
|
52
|
+
{
|
|
53
|
+
return Utf8Bytes ? Encoding.UTF8.GetByteCount(text) : text.Length;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// <summary>
|
|
57
|
+
/// True when <paramref name="folder"/> (a full folder path) and <paramref name="fileName"/> in it are
|
|
58
|
+
/// both within the budget: the folder, the whole path with one separator, and the file name.
|
|
59
|
+
/// </summary>
|
|
60
|
+
internal bool Fits(string folder, string fileName)
|
|
61
|
+
{
|
|
62
|
+
var folderLength = Length(folder);
|
|
63
|
+
var nameLength = Length(fileName);
|
|
64
|
+
return folderLength <= Folder && folderLength + 1 + nameLength <= File && nameLength <= Name;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
using System.Globalization;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.Text;
|
|
4
|
+
using UnityEngine;
|
|
5
|
+
|
|
6
|
+
namespace GeneratedCodeDump.Editor
|
|
7
|
+
{
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// Every path used by the generated-code dump. Constants are relative to the project root.
|
|
10
|
+
/// </summary>
|
|
11
|
+
/// <remarks>
|
|
12
|
+
/// <see cref="ProjectRoot"/> reads <see cref="Application.dataPath"/>, which only works on the main
|
|
13
|
+
/// thread, so the dumper reads it once from its static constructor and every thread uses the cached value.
|
|
14
|
+
/// </remarks>
|
|
15
|
+
internal static class GeneratedCodeDumpPaths
|
|
16
|
+
{
|
|
17
|
+
/// <summary>
|
|
18
|
+
/// The dump: <c><Root>/<Assembly>/<GeneratorAssembly>/<hint></c>, next to
|
|
19
|
+
/// <see cref="WorkFolder"/>.
|
|
20
|
+
/// </summary>
|
|
21
|
+
internal const string Root = "Library/" + GeneratedCodeDumpIdentity.LibraryFolderName;
|
|
22
|
+
|
|
23
|
+
internal const string WorkFolder = Root + "/.work";
|
|
24
|
+
internal const string JobsFile = WorkFolder + "/jobs.txt";
|
|
25
|
+
internal const string SessionMarkerFile = WorkFolder + "/enabled-session";
|
|
26
|
+
|
|
27
|
+
/// <summary>
|
|
28
|
+
/// The per-user switch. UserSettings/ is Unity's per-user folder: it survives deleting Library/, and
|
|
29
|
+
/// Unity's standard .gitignore ignores it.
|
|
30
|
+
/// </summary>
|
|
31
|
+
internal const string SettingsFile = "UserSettings/GeneratedCodeDumpSettings.json";
|
|
32
|
+
|
|
33
|
+
/// <summary>
|
|
34
|
+
/// The package's Library/ folder up to 1.0.0; <see cref="GeneratedCodeDumpMigration"/> removes it.
|
|
35
|
+
/// </summary>
|
|
36
|
+
internal const string LegacyRoot = "Library/" + GeneratedCodeDumpIdentity.PackageName;
|
|
37
|
+
|
|
38
|
+
internal const string LegacySettingsFile = LegacyRoot + "/Settings.json";
|
|
39
|
+
internal const string BeeArtifactsFolder = "Library/Bee/artifacts";
|
|
40
|
+
|
|
41
|
+
internal const string ReplayFileName = "replay.rsp";
|
|
42
|
+
internal const string ErrorLogFileName = "errors.sarif";
|
|
43
|
+
|
|
44
|
+
private static string _projectRoot;
|
|
45
|
+
private static string _scratchRoot;
|
|
46
|
+
|
|
47
|
+
internal static string ProjectRoot =>
|
|
48
|
+
_projectRoot ??= Path.GetDirectoryName(Path.GetFullPath(Application.dataPath));
|
|
49
|
+
|
|
50
|
+
internal static string ToFullPath(string projectRelativePath)
|
|
51
|
+
{
|
|
52
|
+
return Path.GetFullPath(Path.Combine(ProjectRoot, projectRelativePath));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// <summary>
|
|
56
|
+
/// Work folder of <paramref name="assembly"/>, holding <see cref="ReplayFileName"/> and
|
|
57
|
+
/// <see cref="ErrorLogFileName"/>.
|
|
58
|
+
/// </summary>
|
|
59
|
+
internal static string WorkDirectory(string assembly)
|
|
60
|
+
{
|
|
61
|
+
return WorkDirectory(ToFullPath(WorkFolder), assembly, GeneratedCodeDumpPathBudget.Current);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// <summary>
|
|
65
|
+
/// <c><workFolder>/<assembly></c> (made safe) when both its files fit
|
|
66
|
+
/// <paramref name="budget"/>, else <c><workFolder>/</c> and the assembly name cut like a dump
|
|
67
|
+
/// folder. Only counts lengths.
|
|
68
|
+
/// </summary>
|
|
69
|
+
internal static string WorkDirectory(string workFolder, string assembly, GeneratedCodeDumpPathBudget budget)
|
|
70
|
+
{
|
|
71
|
+
var full = Path.Combine(workFolder, GeneratedCodeDumpNames.SafeSegment(assembly));
|
|
72
|
+
if (budget.Fits(full, ErrorLogFileName) && budget.Fits(full, ReplayFileName))
|
|
73
|
+
return full;
|
|
74
|
+
|
|
75
|
+
return Path.Combine(workFolder, GeneratedCodeDumpNames.CutFolder(assembly));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// <summary>
|
|
79
|
+
/// <c><temp>/GenDump/</c>, shared by every project on this machine.
|
|
80
|
+
/// </summary>
|
|
81
|
+
internal static string ScratchBase => Path.Combine(Path.GetTempPath(), GeneratedCodeDumpIdentity.ScratchFolderName);
|
|
82
|
+
|
|
83
|
+
/// <summary>
|
|
84
|
+
/// <c><temp>/GCDump/</c>, the temp folder up to 1.0.0.
|
|
85
|
+
/// </summary>
|
|
86
|
+
internal static string LegacyScratchBase => Path.Combine(Path.GetTempPath(), GeneratedCodeDumpIdentity.LegacyScratchFolderName);
|
|
87
|
+
|
|
88
|
+
/// <summary>
|
|
89
|
+
/// Root of every dump job's compiler output of this project, in the system temp folder:
|
|
90
|
+
/// <c><temp>/GenDump/<project hash>/</c>. The compiler nests each generated file two generator
|
|
91
|
+
/// folders deep and those names can't be cut, so this stays short and independent of where the project
|
|
92
|
+
/// lives, for Windows' 260-character path limit.
|
|
93
|
+
/// </summary>
|
|
94
|
+
internal static string ScratchRoot => _scratchRoot ??= Path.Combine(ScratchBase, ProjectHash);
|
|
95
|
+
|
|
96
|
+
/// <summary>
|
|
97
|
+
/// This project's folder in <see cref="LegacyScratchBase"/>.
|
|
98
|
+
/// </summary>
|
|
99
|
+
internal static string LegacyScratchRoot => Path.Combine(LegacyScratchBase, ProjectHash);
|
|
100
|
+
|
|
101
|
+
/// <summary>
|
|
102
|
+
/// Folder of dump job <paramref name="jobId"/>: <see cref="GeneratedFilesDirectory"/> and
|
|
103
|
+
/// <see cref="ReplayOutputDirectory"/>.
|
|
104
|
+
/// </summary>
|
|
105
|
+
internal static string ScratchDirectory(long jobId)
|
|
106
|
+
{
|
|
107
|
+
return Path.Combine(ScratchRoot, jobId.ToString(CultureInfo.InvariantCulture));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// <summary>
|
|
111
|
+
/// The compiler's <c>-generatedfilesout</c> of dump job <paramref name="jobId"/>: nothing but generated files.
|
|
112
|
+
/// </summary>
|
|
113
|
+
internal static string GeneratedFilesDirectory(long jobId)
|
|
114
|
+
{
|
|
115
|
+
return Path.Combine(ScratchDirectory(jobId), "g");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// <summary>
|
|
119
|
+
/// The assemblies the replay of dump job <paramref name="jobId"/> compiles, as <c><Assembly>.dll</c>
|
|
120
|
+
/// and <c><Assembly>.ref.dll</c>.
|
|
121
|
+
/// </summary>
|
|
122
|
+
internal static string ReplayOutputDirectory(long jobId)
|
|
123
|
+
{
|
|
124
|
+
return Path.Combine(ScratchDirectory(jobId), "o");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// <summary>
|
|
128
|
+
/// 32-bit FNV-1a over the UTF-8 bytes of <paramref name="text"/>: the same on every machine and Unity
|
|
129
|
+
/// version, unlike a runtime string hash.
|
|
130
|
+
/// </summary>
|
|
131
|
+
internal static uint Fnv1a(string text)
|
|
132
|
+
{
|
|
133
|
+
var hash = 2166136261u;
|
|
134
|
+
foreach (var b in Encoding.UTF8.GetBytes(text ?? string.Empty))
|
|
135
|
+
{
|
|
136
|
+
hash ^= b;
|
|
137
|
+
hash *= 16777619u;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return hash;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private static string ProjectHash => Fnv1a(ProjectRoot).ToString("x8", CultureInfo.InvariantCulture);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Diagnostics;
|
|
3
|
+
|
|
4
|
+
namespace GeneratedCodeDump.Editor
|
|
5
|
+
{
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// Identifies processes by id and start time, so a process id the system reuses for another program is
|
|
8
|
+
/// never taken for a dump compiler or for this Editor. No UnityEditor use.
|
|
9
|
+
/// </summary>
|
|
10
|
+
internal static class GeneratedCodeDumpProcess
|
|
11
|
+
{
|
|
12
|
+
private const long StartToleranceTicks = 2 * TimeSpan.TicksPerSecond;
|
|
13
|
+
|
|
14
|
+
private static int _currentId;
|
|
15
|
+
private static long _currentStartUtcTicks;
|
|
16
|
+
|
|
17
|
+
/// <summary>
|
|
18
|
+
/// This Editor's process id.
|
|
19
|
+
/// </summary>
|
|
20
|
+
internal static int CurrentId
|
|
21
|
+
{
|
|
22
|
+
get
|
|
23
|
+
{
|
|
24
|
+
CaptureCurrent();
|
|
25
|
+
return _currentId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// <summary>
|
|
30
|
+
/// This Editor's start time in UTC ticks, or 0 when the system does not tell.
|
|
31
|
+
/// </summary>
|
|
32
|
+
internal static long CurrentStartUtcTicks
|
|
33
|
+
{
|
|
34
|
+
get
|
|
35
|
+
{
|
|
36
|
+
CaptureCurrent();
|
|
37
|
+
return _currentStartUtcTicks;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// <summary>
|
|
42
|
+
/// True when two recorded start times belong to the same process; an unknown (0) time matches any.
|
|
43
|
+
/// </summary>
|
|
44
|
+
internal static bool SameStart(long startUtcTicks, long otherStartUtcTicks)
|
|
45
|
+
{
|
|
46
|
+
return startUtcTicks == 0
|
|
47
|
+
|| otherStartUtcTicks == 0
|
|
48
|
+
|| Math.Abs(startUtcTicks - otherStartUtcTicks) < StartToleranceTicks;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
internal static long StartUtcTicks(Process process)
|
|
52
|
+
{
|
|
53
|
+
try
|
|
54
|
+
{
|
|
55
|
+
return process.StartTime.ToUniversalTime().Ticks;
|
|
56
|
+
}
|
|
57
|
+
catch (Exception)
|
|
58
|
+
{
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// <summary>
|
|
64
|
+
/// True while process <paramref name="processId"/> runs and is the one started at
|
|
65
|
+
/// <paramref name="startUtcTicks"/>.
|
|
66
|
+
/// </summary>
|
|
67
|
+
internal static bool IsRunning(int processId, long startUtcTicks)
|
|
68
|
+
{
|
|
69
|
+
if (processId <= 0)
|
|
70
|
+
return false;
|
|
71
|
+
|
|
72
|
+
try
|
|
73
|
+
{
|
|
74
|
+
using (var process = Process.GetProcessById(processId))
|
|
75
|
+
return !process.HasExited && SameStart(StartUtcTicks(process), startUtcTicks);
|
|
76
|
+
}
|
|
77
|
+
catch (ArgumentException)
|
|
78
|
+
{
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
catch (InvalidOperationException)
|
|
82
|
+
{
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// <summary>
|
|
88
|
+
/// Stops process <paramref name="processId"/> if it still runs and is the one started at
|
|
89
|
+
/// <paramref name="startUtcTicks"/>. Never throws.
|
|
90
|
+
/// </summary>
|
|
91
|
+
internal static void TryKill(int processId, long startUtcTicks)
|
|
92
|
+
{
|
|
93
|
+
try
|
|
94
|
+
{
|
|
95
|
+
using (var process = Process.GetProcessById(processId))
|
|
96
|
+
{
|
|
97
|
+
if (!process.HasExited && SameStart(StartUtcTicks(process), startUtcTicks))
|
|
98
|
+
process.Kill();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (Exception)
|
|
102
|
+
{
|
|
103
|
+
// Already gone, or not ours to stop.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private static void CaptureCurrent()
|
|
108
|
+
{
|
|
109
|
+
if (_currentId != 0)
|
|
110
|
+
return;
|
|
111
|
+
|
|
112
|
+
using (var process = Process.GetCurrentProcess())
|
|
113
|
+
{
|
|
114
|
+
_currentStartUtcTicks = StartUtcTicks(process);
|
|
115
|
+
_currentId = process.Id;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.IO;
|
|
4
|
+
using System.Linq;
|
|
5
|
+
|
|
6
|
+
namespace GeneratedCodeDump.Editor
|
|
7
|
+
{
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// Locates Unity's compiler response files: <c>Library/Bee/artifacts/<hash>.dag/<Assembly>.rsp</c>.
|
|
10
|
+
/// Stale build graphs can sit beside the current one, so the newest file wins. No UnityEditor use.
|
|
11
|
+
/// </summary>
|
|
12
|
+
internal static class GeneratedCodeDumpRsp
|
|
13
|
+
{
|
|
14
|
+
private const string DagPattern = "*.dag";
|
|
15
|
+
private const string RspExtension = ".rsp";
|
|
16
|
+
private const string ModuleFrameworkRspExtension = ".mvfrm.rsp";
|
|
17
|
+
|
|
18
|
+
/// <summary>
|
|
19
|
+
/// The newest <c>*.dag/<assembly>.rsp</c>, or null when there is none.
|
|
20
|
+
/// </summary>
|
|
21
|
+
internal static string Find(string beeArtifactsDir, string assembly)
|
|
22
|
+
{
|
|
23
|
+
if (string.IsNullOrEmpty(assembly) || !Directory.Exists(beeArtifactsDir))
|
|
24
|
+
return null;
|
|
25
|
+
|
|
26
|
+
var fileName = assembly + RspExtension;
|
|
27
|
+
if (fileName.EndsWith(ModuleFrameworkRspExtension, StringComparison.OrdinalIgnoreCase))
|
|
28
|
+
return null;
|
|
29
|
+
|
|
30
|
+
string newest = null;
|
|
31
|
+
var newestTime = DateTime.MinValue;
|
|
32
|
+
foreach (var dag in Directory.GetDirectories(beeArtifactsDir, DagPattern))
|
|
33
|
+
{
|
|
34
|
+
var candidate = Path.Combine(dag, fileName);
|
|
35
|
+
if (!File.Exists(candidate))
|
|
36
|
+
continue;
|
|
37
|
+
|
|
38
|
+
var time = File.GetLastWriteTimeUtc(candidate);
|
|
39
|
+
if (newest != null && time <= newestTime)
|
|
40
|
+
continue;
|
|
41
|
+
|
|
42
|
+
newest = candidate;
|
|
43
|
+
newestTime = time;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return newest;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// <summary>
|
|
50
|
+
/// Names of every assembly with a response file in the newest build graph (the one holding the
|
|
51
|
+
/// newest response file), sorted.
|
|
52
|
+
/// </summary>
|
|
53
|
+
internal static List<string> ListAssemblies(string beeArtifactsDir)
|
|
54
|
+
{
|
|
55
|
+
if (!Directory.Exists(beeArtifactsDir))
|
|
56
|
+
return new List<string>();
|
|
57
|
+
|
|
58
|
+
string[] newestFiles = null;
|
|
59
|
+
var newestTime = DateTime.MinValue;
|
|
60
|
+
foreach (var dag in Directory.GetDirectories(beeArtifactsDir, DagPattern))
|
|
61
|
+
{
|
|
62
|
+
var files = RspFiles(dag);
|
|
63
|
+
if (files.Length == 0)
|
|
64
|
+
continue;
|
|
65
|
+
|
|
66
|
+
var time = files.Max(File.GetLastWriteTimeUtc);
|
|
67
|
+
if (newestFiles != null && time <= newestTime)
|
|
68
|
+
continue;
|
|
69
|
+
|
|
70
|
+
newestFiles = files;
|
|
71
|
+
newestTime = time;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (newestFiles == null)
|
|
75
|
+
return new List<string>();
|
|
76
|
+
|
|
77
|
+
return newestFiles
|
|
78
|
+
.Select(Path.GetFileName)
|
|
79
|
+
.Select(name => name.Substring(0, name.Length - RspExtension.Length))
|
|
80
|
+
.OrderBy(name => name, StringComparer.Ordinal)
|
|
81
|
+
.ToList();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// <summary>
|
|
85
|
+
/// The arguments of <paramref name="rspPath"/>, one per line, followed by those of its companion
|
|
86
|
+
/// <c><Assembly>.rsp2</c> (per-package <c>/pathmap</c>) when there is one: Unity passes both to the
|
|
87
|
+
/// compiler, in that order.
|
|
88
|
+
/// </summary>
|
|
89
|
+
internal static List<string> ReadArguments(string rspPath)
|
|
90
|
+
{
|
|
91
|
+
var lines = File.ReadAllLines(rspPath).ToList();
|
|
92
|
+
var rsp2 = rspPath + "2";
|
|
93
|
+
if (File.Exists(rsp2))
|
|
94
|
+
lines.AddRange(File.ReadAllLines(rsp2));
|
|
95
|
+
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private static string[] RspFiles(string dag)
|
|
100
|
+
{
|
|
101
|
+
return Directory.GetFiles(dag)
|
|
102
|
+
.Where(file => file.EndsWith(RspExtension, StringComparison.OrdinalIgnoreCase))
|
|
103
|
+
.Where(file => !file.EndsWith(ModuleFrameworkRspExtension, StringComparison.OrdinalIgnoreCase))
|
|
104
|
+
.ToArray();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Globalization;
|
|
3
|
+
|
|
4
|
+
namespace GeneratedCodeDump.Editor
|
|
5
|
+
{
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// The session marker, <see cref="GeneratedCodeDumpPaths.SessionMarkerFile"/>: the process id and start
|
|
8
|
+
/// time of the Editor that last found the dump on. It tells a domain reload, which keeps the process, from
|
|
9
|
+
/// an Editor start or the setting being switched on, which both call for a full dump. No UnityEditor use.
|
|
10
|
+
/// </summary>
|
|
11
|
+
internal static class GeneratedCodeDumpSession
|
|
12
|
+
{
|
|
13
|
+
/// <summary>
|
|
14
|
+
/// Dump every assembly when the dump is on and either this Editor process has not seen it on yet, or
|
|
15
|
+
/// the dump folder, <see cref="GeneratedCodeDumpPaths.Root"/>, is missing.
|
|
16
|
+
/// </summary>
|
|
17
|
+
internal static bool ShouldDumpAll(bool enabled, bool markerIsThisProcess, bool rootExists)
|
|
18
|
+
{
|
|
19
|
+
return enabled && (!markerIsThisProcess || !rootExists);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
internal static string FormatMarker(int processId, long processStartUtcTicks)
|
|
23
|
+
{
|
|
24
|
+
return processId.ToString(CultureInfo.InvariantCulture) + " "
|
|
25
|
+
+ processStartUtcTicks.ToString(CultureInfo.InvariantCulture) + "\n";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// <summary>
|
|
29
|
+
/// Reads <c><process id> [<start ticks>]</c>; a missing start time reads as 0 (unknown).
|
|
30
|
+
/// </summary>
|
|
31
|
+
internal static bool TryParseMarker(string text, out int processId, out long processStartUtcTicks)
|
|
32
|
+
{
|
|
33
|
+
processId = 0;
|
|
34
|
+
processStartUtcTicks = 0;
|
|
35
|
+
if (string.IsNullOrEmpty(text))
|
|
36
|
+
return false;
|
|
37
|
+
|
|
38
|
+
var fields = text.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
|
39
|
+
if (fields.Length == 0 || fields.Length > 2)
|
|
40
|
+
return false;
|
|
41
|
+
|
|
42
|
+
if (!int.TryParse(fields[0], NumberStyles.None, CultureInfo.InvariantCulture, out processId) || processId <= 0)
|
|
43
|
+
return false;
|
|
44
|
+
|
|
45
|
+
if (fields.Length == 2 && !long.TryParse(fields[1], NumberStyles.None, CultureInfo.InvariantCulture, out processStartUtcTicks))
|
|
46
|
+
{
|
|
47
|
+
processId = 0;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// <summary>
|
|
55
|
+
/// True when <paramref name="markerText"/> names the process <paramref name="processId"/> started at
|
|
56
|
+
/// <paramref name="processStartUtcTicks"/>.
|
|
57
|
+
/// </summary>
|
|
58
|
+
internal static bool IsMarkerOf(string markerText, int processId, long processStartUtcTicks)
|
|
59
|
+
{
|
|
60
|
+
return TryParseMarker(markerText, out var markerId, out var markerStart)
|
|
61
|
+
&& markerId == processId
|
|
62
|
+
&& GeneratedCodeDumpProcess.SameStart(markerStart, processStartUtcTicks);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
|
|
5
|
+
namespace GeneratedCodeDump.Editor
|
|
6
|
+
{
|
|
7
|
+
/// <summary>
|
|
8
|
+
/// Whether the generated-code dump runs for this user in this project, stored as
|
|
9
|
+
/// <c>{"enabled": true}</c> in <see cref="GeneratedCodeDumpPaths.SettingsFile"/>. Under UserSettings/,
|
|
10
|
+
/// Unity's per-user folder, so it survives deleting Library/, Unity's standard .gitignore keeps it out of
|
|
11
|
+
/// version control, and each developer turns it on for themselves. Batch mode on the same machine follows
|
|
12
|
+
/// it too.
|
|
13
|
+
/// </summary>
|
|
14
|
+
/// <remarks>
|
|
15
|
+
/// Re-read whenever the file's write time or length changes, so an edit made outside the Editor applies
|
|
16
|
+
/// without a restart. A missing file, or one that is not valid, means off; an invalid one warns once per
|
|
17
|
+
/// version. Thread-safe: the settings watcher reads it off the main thread.
|
|
18
|
+
/// </remarks>
|
|
19
|
+
internal sealed class GeneratedCodeDumpSettings
|
|
20
|
+
{
|
|
21
|
+
private static GeneratedCodeDumpSettings _current;
|
|
22
|
+
|
|
23
|
+
private readonly object _sync = new object();
|
|
24
|
+
private readonly string _path;
|
|
25
|
+
private bool _enabled;
|
|
26
|
+
private long _readWriteTicks = -1;
|
|
27
|
+
private long _readLength = -1;
|
|
28
|
+
|
|
29
|
+
internal GeneratedCodeDumpSettings(string path)
|
|
30
|
+
{
|
|
31
|
+
_path = path;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// <summary>
|
|
35
|
+
/// The settings of this project, re-created after each domain reload.
|
|
36
|
+
/// </summary>
|
|
37
|
+
internal static GeneratedCodeDumpSettings Current =>
|
|
38
|
+
_current ??= new GeneratedCodeDumpSettings(GeneratedCodeDumpPaths.ToFullPath(GeneratedCodeDumpPaths.SettingsFile));
|
|
39
|
+
|
|
40
|
+
internal static bool IsEnabled => Current.Enabled;
|
|
41
|
+
|
|
42
|
+
/// <summary>
|
|
43
|
+
/// Re-reads the file when its write time or length changed since the last read. Setting it saves the
|
|
44
|
+
/// file atomically, creating its folder.
|
|
45
|
+
/// </summary>
|
|
46
|
+
internal bool Enabled
|
|
47
|
+
{
|
|
48
|
+
get
|
|
49
|
+
{
|
|
50
|
+
lock (_sync)
|
|
51
|
+
{
|
|
52
|
+
var file = new FileInfo(_path);
|
|
53
|
+
var writeTicks = file.Exists ? file.LastWriteTimeUtc.Ticks : 0;
|
|
54
|
+
var length = file.Exists ? file.Length : 0;
|
|
55
|
+
if (writeTicks == _readWriteTicks && length == _readLength)
|
|
56
|
+
return _enabled;
|
|
57
|
+
|
|
58
|
+
if (!TryRead(file.Exists, out var enabled))
|
|
59
|
+
return _enabled;
|
|
60
|
+
|
|
61
|
+
_enabled = enabled;
|
|
62
|
+
_readWriteTicks = writeTicks;
|
|
63
|
+
_readLength = length;
|
|
64
|
+
return _enabled;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
set
|
|
68
|
+
{
|
|
69
|
+
lock (_sync)
|
|
70
|
+
{
|
|
71
|
+
GeneratedCodeDumpFiles.WriteAtomic(_path, GeneratedCodeDumpSettingsFile.Format(value));
|
|
72
|
+
|
|
73
|
+
var file = new FileInfo(_path);
|
|
74
|
+
_enabled = value;
|
|
75
|
+
_readWriteTicks = file.LastWriteTimeUtc.Ticks;
|
|
76
|
+
_readLength = file.Length;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// False while the file cannot be opened, e.g. mid-write by another program: the next check retries.
|
|
82
|
+
// An invalid file is remembered as off by its version, so it warns once until it changes again.
|
|
83
|
+
private bool TryRead(bool exists, out bool enabled)
|
|
84
|
+
{
|
|
85
|
+
enabled = false;
|
|
86
|
+
if (!exists)
|
|
87
|
+
return true;
|
|
88
|
+
|
|
89
|
+
string json;
|
|
90
|
+
try
|
|
91
|
+
{
|
|
92
|
+
json = File.ReadAllText(_path);
|
|
93
|
+
}
|
|
94
|
+
catch (Exception exception) when (exception is IOException || exception is UnauthorizedAccessException)
|
|
95
|
+
{
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!GeneratedCodeDumpSettingsFile.TryParse(json, out enabled))
|
|
100
|
+
{
|
|
101
|
+
enabled = false;
|
|
102
|
+
Debug.LogWarning(
|
|
103
|
+
$"{GeneratedCodeDumpIdentity.LogPrefix}{_path} is not valid: expected {{\"{GeneratedCodeDumpSettingsFile.EnabledKey}\": true}} "
|
|
104
|
+
+ "or false. The dump is off."
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|