forked from BilalY/Rasagar
1049 lines
42 KiB
C#
1049 lines
42 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Linq;
|
||
|
using System.Security;
|
||
|
using System.Text;
|
||
|
using Packages.Rider.Editor.Util;
|
||
|
using UnityEditor;
|
||
|
using UnityEditor.Compilation;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace Packages.Rider.Editor.ProjectGeneration
|
||
|
{
|
||
|
internal class ProjectGeneration : IGenerator
|
||
|
{
|
||
|
private enum ScriptingLanguage
|
||
|
{
|
||
|
None,
|
||
|
CSharp
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Map source extensions to ScriptingLanguages
|
||
|
/// </summary>
|
||
|
private static readonly Dictionary<string, ScriptingLanguage> k_BuiltinSupportedExtensions =
|
||
|
new Dictionary<string, ScriptingLanguage>
|
||
|
{
|
||
|
{ ".cs", ScriptingLanguage.CSharp },
|
||
|
{ ".uxml", ScriptingLanguage.None },
|
||
|
{ ".uss", ScriptingLanguage.None },
|
||
|
{ ".shader", ScriptingLanguage.None },
|
||
|
{ ".compute", ScriptingLanguage.None },
|
||
|
{ ".cginc", ScriptingLanguage.None },
|
||
|
{ ".hlsl", ScriptingLanguage.None },
|
||
|
{ ".glslinc", ScriptingLanguage.None },
|
||
|
{ ".template", ScriptingLanguage.None },
|
||
|
{ ".raytrace", ScriptingLanguage.None },
|
||
|
{ ".json", ScriptingLanguage.None},
|
||
|
{ ".rsp", ScriptingLanguage.None},
|
||
|
{ ".asmdef", ScriptingLanguage.None},
|
||
|
{ ".asmref", ScriptingLanguage.None},
|
||
|
{ ".xaml", ScriptingLanguage.None},
|
||
|
{ ".tt", ScriptingLanguage.None},
|
||
|
{ ".t4", ScriptingLanguage.None},
|
||
|
{ ".ttinclude", ScriptingLanguage.None}
|
||
|
};
|
||
|
|
||
|
private string[] m_ProjectSupportedExtensions = Array.Empty<string>();
|
||
|
|
||
|
// Note that ProjectDirectory can be assumed to be the result of Path.GetFullPath
|
||
|
public string ProjectDirectory { get; }
|
||
|
public string ProjectDirectoryWithSlash { get; }
|
||
|
|
||
|
private readonly string m_ProjectName;
|
||
|
private readonly IAssemblyNameProvider m_AssemblyNameProvider;
|
||
|
private readonly IFileIO m_FileIOProvider;
|
||
|
private readonly IGUIDGenerator m_GUIDGenerator;
|
||
|
|
||
|
private readonly Dictionary<string, string> m_ProjectGuids = new Dictionary<string, string>();
|
||
|
|
||
|
// If we have multiple projects, the same assembly references are reused for each. Caching the normalised paths and
|
||
|
// names is actually cheaper than recalculating each time, in terms of both time and memory allocations
|
||
|
private readonly Dictionary<string, string> m_NormalisedPaths = new Dictionary<string, string>();
|
||
|
private readonly Dictionary<string, string> m_AssemblyNames = new Dictionary<string, string>();
|
||
|
|
||
|
internal static bool isRiderProjectGeneration; // workaround to https://github.cds.internal.unity3d.com/unity/com.unity.ide.rider/issues/28
|
||
|
|
||
|
IAssemblyNameProvider IGenerator.AssemblyNameProvider => m_AssemblyNameProvider;
|
||
|
|
||
|
public ProjectGeneration()
|
||
|
: this(Directory.GetParent(Application.dataPath).FullName) { }
|
||
|
|
||
|
public ProjectGeneration(string projectDirectory)
|
||
|
: this(projectDirectory, new AssemblyNameProvider(), new FileIOProvider(), new GUIDProvider()) { }
|
||
|
|
||
|
public ProjectGeneration(string projectDirectory, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIoProvider, IGUIDGenerator guidGenerator)
|
||
|
{
|
||
|
ProjectDirectory = Path.GetFullPath(projectDirectory.NormalizePath());
|
||
|
ProjectDirectoryWithSlash = ProjectDirectory + Path.DirectorySeparatorChar;
|
||
|
m_ProjectName = Path.GetFileName(ProjectDirectory);
|
||
|
m_AssemblyNameProvider = assemblyNameProvider;
|
||
|
m_FileIOProvider = fileIoProvider;
|
||
|
m_GUIDGenerator = guidGenerator;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Syncs the scripting solution if any affected files are relevant.
|
||
|
/// </summary>
|
||
|
/// <returns>
|
||
|
/// Whether the solution was synced.
|
||
|
/// </returns>
|
||
|
/// <param name='affectedFiles'>
|
||
|
/// A set of files whose status has changed
|
||
|
/// </param>
|
||
|
/// <param name="reimportedFiles">
|
||
|
/// A set of files that got reimported
|
||
|
/// </param>
|
||
|
/// <param name="checkProjectFiles">
|
||
|
/// Check if project files were changed externally
|
||
|
/// </param>
|
||
|
public bool SyncIfNeeded(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles, bool checkProjectFiles = false)
|
||
|
{
|
||
|
SetupSupportedExtensions();
|
||
|
|
||
|
PackageManagerTracker.SyncIfNeeded(checkProjectFiles);
|
||
|
|
||
|
if (HasFilesBeenModified(affectedFiles, reimportedFiles) || RiderScriptEditorData.instance.hasChanges
|
||
|
|| RiderScriptEditorData.instance.HasChangesInCompilationDefines()
|
||
|
|| (checkProjectFiles && LastWriteTracker.HasLastWriteTimeChanged()))
|
||
|
{
|
||
|
Sync();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private bool HasFilesBeenModified(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles)
|
||
|
{
|
||
|
return affectedFiles.Any(ShouldFileBePartOfSolution) || reimportedFiles.Any(ShouldSyncOnReimportedAsset);
|
||
|
}
|
||
|
|
||
|
private static bool ShouldSyncOnReimportedAsset(string asset)
|
||
|
{
|
||
|
var extension = Path.GetExtension(asset);
|
||
|
return extension == ".asmdef" || extension == ".asmref" || Path.GetFileName(asset) == "csc.rsp";
|
||
|
}
|
||
|
|
||
|
public void Sync()
|
||
|
{
|
||
|
SetupSupportedExtensions();
|
||
|
var types = GetAssetPostprocessorTypes();
|
||
|
isRiderProjectGeneration = true;
|
||
|
var externalCodeAlreadyGeneratedProjects = OnPreGeneratingCSProjectFiles(types);
|
||
|
isRiderProjectGeneration = false;
|
||
|
if (!externalCodeAlreadyGeneratedProjects)
|
||
|
{
|
||
|
GenerateAndWriteSolutionAndProjects(types);
|
||
|
}
|
||
|
|
||
|
OnGeneratedCSProjectFiles(types);
|
||
|
m_AssemblyNameProvider.ResetCaches();
|
||
|
m_AssemblyNames.Clear();
|
||
|
m_NormalisedPaths.Clear();
|
||
|
m_ProjectGuids.Clear();
|
||
|
_buffer = null;
|
||
|
RiderScriptEditorData.instance.hasChanges = false;
|
||
|
RiderScriptEditorData.instance.InvalidateSavedCompilationDefines();
|
||
|
}
|
||
|
|
||
|
public bool HasSolutionBeenGenerated()
|
||
|
{
|
||
|
return m_FileIOProvider.Exists(SolutionFile());
|
||
|
}
|
||
|
|
||
|
private void SetupSupportedExtensions()
|
||
|
{
|
||
|
var extensions = m_AssemblyNameProvider.ProjectSupportedExtensions;
|
||
|
m_ProjectSupportedExtensions = new string[extensions.Length];
|
||
|
for (var i = 0; i < extensions.Length; i++)
|
||
|
{
|
||
|
m_ProjectSupportedExtensions[i] = "." + extensions[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool ShouldFileBePartOfSolution(string file)
|
||
|
{
|
||
|
// Exclude files coming from packages except if they are internalized.
|
||
|
if (m_AssemblyNameProvider.IsInternalizedPackagePath(file))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return HasValidExtension(file);
|
||
|
}
|
||
|
|
||
|
public bool HasValidExtension(string file)
|
||
|
{
|
||
|
// Dll's are not scripts but still need to be included..
|
||
|
if (file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||
|
return true;
|
||
|
|
||
|
var extension = Path.GetExtension(file);
|
||
|
return IsSupportedExtension(extension);
|
||
|
}
|
||
|
|
||
|
private bool IsSupportedExtension(string extension)
|
||
|
{
|
||
|
return k_BuiltinSupportedExtensions.ContainsKey(extension) || m_ProjectSupportedExtensions.Contains(extension);
|
||
|
}
|
||
|
|
||
|
private class AssemblyUsage
|
||
|
{
|
||
|
private readonly HashSet<string> m_ProjectAssemblies = new HashSet<string>();
|
||
|
private readonly HashSet<string> m_PrecompiledAssemblies = new HashSet<string>();
|
||
|
|
||
|
public void AddProjectAssembly(Assembly assembly)
|
||
|
{
|
||
|
m_ProjectAssemblies.Add(assembly.name);
|
||
|
}
|
||
|
|
||
|
public void AddPrecompiledAssembly(Assembly assembly)
|
||
|
{
|
||
|
m_PrecompiledAssemblies.Add(assembly.name);
|
||
|
}
|
||
|
|
||
|
public bool IsProjectAssembly(Assembly assembly) => m_ProjectAssemblies.Contains(assembly.name);
|
||
|
public bool IsPrecompiledAssembly(Assembly assembly) => m_PrecompiledAssemblies.Contains(assembly.name);
|
||
|
}
|
||
|
|
||
|
private void GenerateAndWriteSolutionAndProjects(Type[] types)
|
||
|
{
|
||
|
// Only synchronize islands that have associated source files and ones that we actually want in the project.
|
||
|
// This also filters out DLLs coming from .asmdef files in packages.
|
||
|
|
||
|
// Get all of the assemblies that Unity will compile from source. This includes Assembly-CSharp, all user assembly
|
||
|
// definitions, and all packages. Not all of the returned assemblies will require project files - by default,
|
||
|
// registry, git and local tarball packages are pre-compiled by Unity and will not require a project. This can be
|
||
|
// changed by the user in the External Tools settings page.
|
||
|
// Each assembly instance contains source files, output path, defines, compiler options and references. There
|
||
|
// will be `compiledAssemblyReferences`, which are DLLs, such as UnityEngine.dll, and assembly references, which
|
||
|
// are references to other assemblies that Unity will compile from source. Again, these assemblies might be
|
||
|
// projects, or pre-compiled by Unity, depending on the options selected by the user.
|
||
|
var allAssemblies = m_AssemblyNameProvider.GetAllAssemblies();
|
||
|
var assemblyUsage = new AssemblyUsage();
|
||
|
foreach (var assembly in allAssemblies)
|
||
|
{
|
||
|
if (assembly.sourceFiles.Any(ShouldFileBePartOfSolution))
|
||
|
assemblyUsage.AddProjectAssembly(assembly);
|
||
|
else
|
||
|
assemblyUsage.AddPrecompiledAssembly(assembly);
|
||
|
}
|
||
|
|
||
|
// Get additional assets (other than source files) that we want to add to the projects, e.g. shaders, asmdef, etc.
|
||
|
var additionalAssetsByAssembly = GetAdditionalAssets();
|
||
|
|
||
|
var projectParts = new List<ProjectPart>();
|
||
|
var assemblyNamesWithSource = new HashSet<string>();
|
||
|
foreach (var assembly in allAssemblies)
|
||
|
{
|
||
|
if (!assemblyUsage.IsProjectAssembly(assembly))
|
||
|
continue;
|
||
|
|
||
|
// TODO: Will this check ever be true? Player assemblies don't have the same name as editor assemblies, right?
|
||
|
if (assemblyNamesWithSource.Contains(assembly.name))
|
||
|
projectParts.Add(new ProjectPart(assembly.name, assembly, new List<string>())); // do not add asset project parts to both editor and player projects
|
||
|
else
|
||
|
{
|
||
|
additionalAssetsByAssembly.TryGetValue(assembly.name, out var additionalAssetsForProject);
|
||
|
projectParts.Add(new ProjectPart(assembly.name, assembly, additionalAssetsForProject));
|
||
|
assemblyNamesWithSource.Add(assembly.name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If there are any assets that should be in a separate assembly, but that assembly folder doesn't contain any
|
||
|
// source files, we'll have orphaned assets. Create a project for these assemblies, with references based on the
|
||
|
// Rider package assembly
|
||
|
// TODO: Would this produce the same results if we removed the check for ShouldFileBePartOfSolution above?
|
||
|
// I suspect the only difference would be output path and references, and potentially simplify things
|
||
|
var executingAssemblyName = typeof(ProjectGeneration).Assembly.GetName().Name;
|
||
|
var riderAssembly = m_AssemblyNameProvider.GetNamedAssembly(executingAssemblyName);
|
||
|
string[] coreReferences = null;
|
||
|
foreach (var pair in additionalAssetsByAssembly)
|
||
|
{
|
||
|
var assembly = pair.Key;
|
||
|
var additionalAssets = pair.Value;
|
||
|
|
||
|
if (!assemblyNamesWithSource.Contains(assembly))
|
||
|
{
|
||
|
if (coreReferences == null)
|
||
|
{
|
||
|
coreReferences = riderAssembly?.compiledAssemblyReferences.Where(a =>
|
||
|
a.EndsWith("UnityEditor.dll", StringComparison.Ordinal) ||
|
||
|
a.EndsWith("UnityEngine.dll", StringComparison.Ordinal) ||
|
||
|
a.EndsWith("UnityEngine.CoreModule.dll", StringComparison.Ordinal)).ToArray();
|
||
|
}
|
||
|
|
||
|
projectParts.Add(AddProjectPart(assembly, riderAssembly, coreReferences, additionalAssets));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var stringBuilder = new StringBuilder();
|
||
|
SyncSolution(stringBuilder, projectParts, types);
|
||
|
stringBuilder.Clear();
|
||
|
|
||
|
foreach (var projectPart in projectParts)
|
||
|
{
|
||
|
SyncProject(stringBuilder, projectPart, assemblyUsage, types);
|
||
|
stringBuilder.Clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static ProjectPart AddProjectPart(string assemblyName, Assembly riderAssembly, string[] coreReferences,
|
||
|
List<string> additionalAssets)
|
||
|
{
|
||
|
Assembly assembly = null;
|
||
|
if (riderAssembly != null)
|
||
|
{
|
||
|
// We want to add those references, so that Rider would detect Unity path and version and provide rich features for shader files
|
||
|
// Note that output path will be Library/ScriptAssemblies
|
||
|
assembly = new Assembly(assemblyName, riderAssembly.outputPath, Array.Empty<string>(),
|
||
|
new []{"UNITY_EDITOR"},
|
||
|
Array.Empty<Assembly>(),
|
||
|
coreReferences,
|
||
|
riderAssembly.flags);
|
||
|
}
|
||
|
return new ProjectPart(assemblyName, assembly, additionalAssets);
|
||
|
}
|
||
|
|
||
|
private Dictionary<string, List<string>> GetAdditionalAssets()
|
||
|
{
|
||
|
var assemblyDllNames = new FilePathTrie<string>();
|
||
|
var interestingAssets = new List<string>();
|
||
|
foreach (var assetPath in m_AssemblyNameProvider.GetAllAssetPaths())
|
||
|
{
|
||
|
if (m_AssemblyNameProvider.IsInternalizedPackagePath(assetPath))
|
||
|
continue;
|
||
|
|
||
|
// Find all the .asmdef and .asmref files. Then get the assembly for a file in the same folder. Anything in that
|
||
|
// folder or below will be in the same assembly (unless there's another nested .asmdef, obvs)
|
||
|
if (assetPath.EndsWith(".asmdef", StringComparison.OrdinalIgnoreCase)
|
||
|
|| assetPath.EndsWith(".asmref", StringComparison.OrdinalIgnoreCase))
|
||
|
{
|
||
|
// This call is very expensive when working with a very large project (e.g. called for 50,000+ assets), hence
|
||
|
// the approach of working with assembly definition root folders. We don't need a real script file to get the
|
||
|
// assembly DLL name
|
||
|
var assemblyDllName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(assetPath + ".cs");
|
||
|
assemblyDllNames.Insert(Path.GetDirectoryName(assetPath), assemblyDllName);
|
||
|
}
|
||
|
|
||
|
interestingAssets.Add(assetPath);
|
||
|
}
|
||
|
|
||
|
const string fallbackAssemblyDllName = "Assembly-CSharp.dll";
|
||
|
|
||
|
var assetsByAssemblyDll = new Dictionary<string, List<string>>();
|
||
|
foreach (var asset in interestingAssets)
|
||
|
{
|
||
|
// TODO: Can we remove folders from generated projects?
|
||
|
// Why do we add them? We get an asset for every folder, including intermediate folders. We add folders that
|
||
|
// contain assets that we don't add to project files, so they appear empty. Adding them to the project file does
|
||
|
// not give us anything special - they appear as a folder in the Solution Explorer, so we can right click and
|
||
|
// add a file, but we could also "Show All Files" and do the same. Equally, Rider defaults to the Unity Explorer
|
||
|
// view, which shows all files and folders by default.
|
||
|
// We gain nothing by adding folders, and for very large projects, it can be very expensive to discover what
|
||
|
// project they should be added to, since most paths will be _above_ asmdef files, or inside Assets (which
|
||
|
// requires the full expensive check due to Editor, Resources, etc.)
|
||
|
// (E.g. an example large project with 45,600 assets, 5,000 are folders and only 2,500 are useful assets)
|
||
|
if (AssetDatabase.IsValidFolder(asset))
|
||
|
{
|
||
|
// var assemblyDllName = assemblyDllNames.FindClosestMatch(asset);
|
||
|
// if (string.IsNullOrEmpty(assemblyDllName))
|
||
|
// {
|
||
|
// // Can't find it in trie (Assembly-CSharp and related projects don't have .asmdef files)
|
||
|
// assemblyDllName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath($"{asset}/asset.cs");
|
||
|
// }
|
||
|
// if (string.IsNullOrEmpty(assemblyDllName))
|
||
|
// assemblyDllName = fallbackAssemblyDllName;
|
||
|
//
|
||
|
// if (!stringBuilders.TryGetValue(assemblyDllName, out var projectBuilder))
|
||
|
// {
|
||
|
// projectBuilder = new StringBuilder();
|
||
|
// stringBuilders[assemblyDllName] = projectBuilder;
|
||
|
//}
|
||
|
//
|
||
|
// projectBuilder.Append(" <Folder Include=\"")
|
||
|
// .Append(m_FileIOProvider.EscapedRelativePathFor(asset, ProjectDirectoryWithSlash))
|
||
|
// .Append("\" />")
|
||
|
// .AppendLine();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!asset.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) && IsSupportedExtension(Path.GetExtension(asset)))
|
||
|
{
|
||
|
var assemblyDllName = assemblyDllNames.FindClosestMatch(asset);
|
||
|
if (string.IsNullOrEmpty(assemblyDllName))
|
||
|
{
|
||
|
// Can't find it in trie (Assembly-CSharp and related projects don't have .asmdef files)
|
||
|
assemblyDllName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath($"{asset}.cs");
|
||
|
}
|
||
|
if (string.IsNullOrEmpty(assemblyDllName))
|
||
|
assemblyDllName = fallbackAssemblyDllName;
|
||
|
|
||
|
if (!assetsByAssemblyDll.TryGetValue(assemblyDllName, out var assets))
|
||
|
{
|
||
|
assets = new List<string>();
|
||
|
assetsByAssemblyDll[assemblyDllName] = assets;
|
||
|
}
|
||
|
|
||
|
assets.Add(m_FileIOProvider.EscapedRelativePathFor(asset, ProjectDirectoryWithSlash));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var assetsByAssemblyName = new Dictionary<string, List<string>>(assetsByAssemblyDll.Count);
|
||
|
foreach (var entry in assetsByAssemblyDll)
|
||
|
{
|
||
|
var assemblyName = FileSystemUtil.FileNameWithoutExtension(entry.Key);
|
||
|
assetsByAssemblyName[assemblyName] = entry.Value;
|
||
|
}
|
||
|
|
||
|
return assetsByAssemblyName;
|
||
|
}
|
||
|
|
||
|
private void SyncProject(StringBuilder stringBuilder, ProjectPart island, AssemblyUsage assemblyUsage, Type[] types)
|
||
|
{
|
||
|
SyncProjectFileIfNotChanged(
|
||
|
ProjectFile(island),
|
||
|
ProjectText(stringBuilder, island, assemblyUsage),
|
||
|
types);
|
||
|
}
|
||
|
|
||
|
private void SyncProjectFileIfNotChanged(string path, string newContents, Type[] types)
|
||
|
{
|
||
|
if (Path.GetExtension(path) == ".csproj")
|
||
|
{
|
||
|
newContents = OnGeneratedCSProject(path, newContents, types);
|
||
|
}
|
||
|
|
||
|
SyncFileIfNotChanged(path, newContents);
|
||
|
}
|
||
|
|
||
|
private void SyncSolutionFileIfNotChanged(string path, string newContents, Type[] types)
|
||
|
{
|
||
|
newContents = OnGeneratedSlnSolution(path, newContents, types);
|
||
|
|
||
|
SyncFileIfNotChanged(path, newContents);
|
||
|
}
|
||
|
|
||
|
private static void OnGeneratedCSProjectFiles(Type[] types)
|
||
|
{
|
||
|
foreach (var type in types)
|
||
|
{
|
||
|
var method = type.GetMethod("OnGeneratedCSProjectFiles",
|
||
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
|
||
|
System.Reflection.BindingFlags.Static);
|
||
|
if (method == null)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
Debug.LogWarning("OnGeneratedCSProjectFiles is not supported.");
|
||
|
// RIDER-51958
|
||
|
//method.Invoke(null, args);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static Type[] GetAssetPostprocessorTypes()
|
||
|
{
|
||
|
return TypeCache.GetTypesDerivedFrom<AssetPostprocessor>().ToArray(); // doesn't find types from EditorPlugin, which is fine
|
||
|
}
|
||
|
|
||
|
private static bool OnPreGeneratingCSProjectFiles(Type[] types)
|
||
|
{
|
||
|
var result = false;
|
||
|
foreach (var type in types)
|
||
|
{
|
||
|
var args = new object[0];
|
||
|
var method = type.GetMethod("OnPreGeneratingCSProjectFiles",
|
||
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
|
||
|
System.Reflection.BindingFlags.Static);
|
||
|
if (method == null)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var returnValue = method.Invoke(null, args);
|
||
|
if (method.ReturnType == typeof(bool))
|
||
|
{
|
||
|
result |= (bool)returnValue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private static string OnGeneratedCSProject(string path, string content, Type[] types)
|
||
|
{
|
||
|
foreach (var type in types)
|
||
|
{
|
||
|
var args = new[] { path, content };
|
||
|
var method = type.GetMethod("OnGeneratedCSProject",
|
||
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
|
||
|
System.Reflection.BindingFlags.Static);
|
||
|
if (method == null)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var returnValue = method.Invoke(null, args);
|
||
|
if (method.ReturnType == typeof(string))
|
||
|
{
|
||
|
content = (string)returnValue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return content;
|
||
|
}
|
||
|
|
||
|
private static string OnGeneratedSlnSolution(string path, string content, Type[] types)
|
||
|
{
|
||
|
foreach (var type in types)
|
||
|
{
|
||
|
var args = new[] { path, content };
|
||
|
var method = type.GetMethod("OnGeneratedSlnSolution",
|
||
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
|
||
|
System.Reflection.BindingFlags.Static);
|
||
|
if (method == null)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var returnValue = method.Invoke(null, args);
|
||
|
if (method.ReturnType == typeof(string))
|
||
|
{
|
||
|
content = (string)returnValue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return content;
|
||
|
}
|
||
|
|
||
|
private void SyncFileIfNotChanged(string path, string newContents)
|
||
|
{
|
||
|
if (HasChanged(path, newContents))
|
||
|
m_FileIOProvider.WriteAllText(path, newContents);
|
||
|
}
|
||
|
|
||
|
private static char[] _buffer = null;
|
||
|
|
||
|
private bool HasChanged(string path, string newContents)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (!m_FileIOProvider.Exists(path))
|
||
|
return true;
|
||
|
|
||
|
const int bufferSize = 100 * 1024; // 100kb - big enough to read most project files in a single read
|
||
|
|
||
|
if (_buffer == null)
|
||
|
_buffer = new char[bufferSize];
|
||
|
|
||
|
using (var reader = m_FileIOProvider.GetReader(path))
|
||
|
{
|
||
|
int read, offset = 0;
|
||
|
do
|
||
|
{
|
||
|
read = reader.ReadBlock(_buffer, 0, _buffer.Length);
|
||
|
for (var i = 0; i < read; i++)
|
||
|
{
|
||
|
if (_buffer[i] != newContents[offset + i])
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
offset += read;
|
||
|
} while (read > 0);
|
||
|
|
||
|
var isSame = offset == newContents.Length;
|
||
|
return !isSame;
|
||
|
}
|
||
|
}
|
||
|
catch (Exception exception)
|
||
|
{
|
||
|
Debug.LogException(exception);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private string ProjectText(StringBuilder projectBuilder, ProjectPart assembly, AssemblyUsage assemblyUsage)
|
||
|
{
|
||
|
var responseFilesData = assembly.GetResponseFileData(m_AssemblyNameProvider, ProjectDirectory);
|
||
|
|
||
|
ProjectHeader(projectBuilder, assembly, responseFilesData);
|
||
|
|
||
|
projectBuilder.AppendLine(" <ItemGroup>");
|
||
|
foreach (var file in assembly.SourceFiles)
|
||
|
{
|
||
|
var fullFile = m_FileIOProvider.EscapedRelativePathFor(file, ProjectDirectory);
|
||
|
projectBuilder.Append(" <Compile Include=\"").Append(fullFile).AppendLine("\" />");
|
||
|
}
|
||
|
|
||
|
foreach (var additionalAsset in (IEnumerable<string>)assembly.AdditionalAssets ?? Array.Empty<string>())
|
||
|
projectBuilder.Append(" <None Include=\"").Append(additionalAsset).AppendLine("\" />");
|
||
|
|
||
|
var binaryReferences = new HashSet<string>(assembly.CompiledAssemblyReferences);
|
||
|
foreach (var responseFileData in responseFilesData)
|
||
|
binaryReferences.UnionWith(responseFileData.FullPathReferences);
|
||
|
foreach (var assemblyReference in assembly.AssemblyReferences)
|
||
|
{
|
||
|
if (assemblyUsage.IsPrecompiledAssembly(assemblyReference))
|
||
|
binaryReferences.Add(assemblyReference.outputPath);
|
||
|
}
|
||
|
|
||
|
foreach (var reference in binaryReferences)
|
||
|
{
|
||
|
var escapedFullPath = GetNormalisedAssemblyPath(reference);
|
||
|
var assemblyName = GetAssemblyNameFromPath(reference);
|
||
|
projectBuilder
|
||
|
.Append(" <Reference Include=\"").Append(assemblyName).AppendLine("\">")
|
||
|
.Append(" <HintPath>").Append(escapedFullPath).AppendLine("</HintPath>")
|
||
|
.AppendLine(" </Reference>");
|
||
|
}
|
||
|
|
||
|
if (0 < assembly.AssemblyReferences.Length)
|
||
|
{
|
||
|
projectBuilder
|
||
|
.AppendLine(" </ItemGroup>")
|
||
|
.AppendLine(" <ItemGroup>");
|
||
|
|
||
|
foreach (var reference in assembly.AssemblyReferences)
|
||
|
{
|
||
|
if (assemblyUsage.IsProjectAssembly(reference))
|
||
|
{
|
||
|
var name = m_AssemblyNameProvider.GetProjectName(reference.name, reference.defines);
|
||
|
projectBuilder
|
||
|
.Append(" <ProjectReference Include=\"").Append(name).AppendLine(".csproj\">")
|
||
|
.Append(" <Project>{").Append(ProjectGuid(name)).AppendLine("}</Project>")
|
||
|
.Append(" <Name>").Append(name).AppendLine("</Name>")
|
||
|
.AppendLine(" </ProjectReference>");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
projectBuilder
|
||
|
.AppendLine(" </ItemGroup>")
|
||
|
.AppendLine(" <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />")
|
||
|
.AppendLine(
|
||
|
" <!-- To modify your build process, add your task inside one of the targets below and uncomment it.")
|
||
|
.AppendLine(" Other similar extension points exist, see Microsoft.Common.targets.")
|
||
|
.AppendLine(" <Target Name=\"BeforeBuild\">")
|
||
|
.AppendLine(" </Target>")
|
||
|
.AppendLine(" <Target Name=\"AfterBuild\">")
|
||
|
.AppendLine(" </Target>")
|
||
|
.AppendLine(" -->")
|
||
|
.AppendLine("</Project>");
|
||
|
|
||
|
return projectBuilder.ToString();
|
||
|
}
|
||
|
|
||
|
private string ProjectFile(ProjectPart projectPart)
|
||
|
{
|
||
|
return Path.Combine(ProjectDirectory, $"{m_AssemblyNameProvider.GetProjectName(projectPart.Name, projectPart.Defines)}.csproj");
|
||
|
}
|
||
|
|
||
|
public string SolutionFile()
|
||
|
{
|
||
|
return Path.Combine(ProjectDirectory, $"{m_ProjectName}.sln");
|
||
|
}
|
||
|
|
||
|
private void ProjectHeader(StringBuilder stringBuilder, ProjectPart assembly, List<ResponseFileData> responseFilesData)
|
||
|
{
|
||
|
var responseFilesDataArgs = GetOtherArgumentsFromResponseFilesData(responseFilesData);
|
||
|
stringBuilder
|
||
|
.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
|
||
|
.AppendLine(
|
||
|
"<Project ToolsVersion=\"4.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">")
|
||
|
.AppendLine(" <PropertyGroup>")
|
||
|
.Append(" <LangVersion>").Append(GetLangVersion(responseFilesDataArgs["langversion"], assembly)).AppendLine("</LangVersion>")
|
||
|
.AppendLine(
|
||
|
" <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package</_TargetFrameworkDirectories>")
|
||
|
.AppendLine(
|
||
|
" <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package</_FullFrameworkReferenceAssemblyPaths>")
|
||
|
.AppendLine(" <DisableHandlePackageFileConflicts>true</DisableHandlePackageFileConflicts>");
|
||
|
|
||
|
var rulesetPaths = GetRoslynAnalyzerRulesetPaths(assembly, responseFilesDataArgs);
|
||
|
foreach (var path in rulesetPaths)
|
||
|
stringBuilder.Append(" <CodeAnalysisRuleSet>").Append(path).AppendLine("</CodeAnalysisRuleSet>");
|
||
|
|
||
|
stringBuilder
|
||
|
.AppendLine(" </PropertyGroup>")
|
||
|
.AppendLine(" <PropertyGroup>")
|
||
|
.AppendLine(" <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>")
|
||
|
.AppendLine(" <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>")
|
||
|
.AppendLine(" <ProductVersion>10.0.20506</ProductVersion>")
|
||
|
.AppendLine(" <SchemaVersion>2.0</SchemaVersion>")
|
||
|
.Append(" <RootNamespace>").Append(assembly.RootNamespace).AppendLine("</RootNamespace>")
|
||
|
.Append(" <ProjectGuid>{").Append(ProjectGuid(m_AssemblyNameProvider.GetProjectName(assembly.Name, assembly.Defines))).AppendLine("}</ProjectGuid>")
|
||
|
.AppendLine(
|
||
|
" <ProjectTypeGuids>{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>")
|
||
|
.AppendLine(" <OutputType>Library</OutputType>")
|
||
|
.AppendLine(" <AppDesignerFolder>Properties</AppDesignerFolder>")
|
||
|
.Append(" <AssemblyName>").Append(assembly.Name).AppendLine("</AssemblyName>")
|
||
|
.AppendLine(" <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>")
|
||
|
.AppendLine(" <FileAlignment>512</FileAlignment>")
|
||
|
.AppendLine(" <BaseDirectory>.</BaseDirectory>")
|
||
|
.AppendLine(" </PropertyGroup>")
|
||
|
.AppendLine(" <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">")
|
||
|
.AppendLine(" <DebugSymbols>true</DebugSymbols>")
|
||
|
.AppendLine(" <DebugType>full</DebugType>")
|
||
|
.AppendLine(" <Optimize>false</Optimize>")
|
||
|
.Append(" <OutputPath>").Append(assembly.OutputPath).AppendLine("</OutputPath>");
|
||
|
|
||
|
var defines = new HashSet<string>(assembly.Defines);
|
||
|
foreach (var responseFileData in responseFilesData)
|
||
|
defines.UnionWith(responseFileData.Defines);
|
||
|
stringBuilder
|
||
|
.Append(" <DefineConstants>").CompatibleAppendJoin(';', defines).AppendLine("</DefineConstants>")
|
||
|
.AppendLine(" <ErrorReport>prompt</ErrorReport>");
|
||
|
|
||
|
var warningLevel = responseFilesDataArgs["warn"].Concat(responseFilesDataArgs["w"]).Distinct().FirstOrDefault();
|
||
|
stringBuilder
|
||
|
.Append(" <WarningLevel>").Append(!string.IsNullOrWhiteSpace(warningLevel) ? warningLevel : "4").AppendLine("</WarningLevel>")
|
||
|
.Append(" <NoWarn>").CompatibleAppendJoin(',', GetNoWarn(responseFilesDataArgs["nowarn"].ToList())).AppendLine("</NoWarn>")
|
||
|
.Append(" <AllowUnsafeBlocks>").Append(assembly.CompilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe)).AppendLine("</AllowUnsafeBlocks>");
|
||
|
|
||
|
AppendWarningAsError(stringBuilder, responseFilesDataArgs["warnaserror"],
|
||
|
responseFilesDataArgs["warnaserror-"], responseFilesDataArgs["warnaserror+"]);
|
||
|
|
||
|
// TODO: Can we have multiple documentation files in a project file?
|
||
|
foreach (var docFile in responseFilesDataArgs["doc"])
|
||
|
stringBuilder.Append(" <DocumentationFile>").Append(docFile).AppendLine("</DocumentationFile>");
|
||
|
|
||
|
var nullable = responseFilesDataArgs["nullable"].FirstOrDefault();
|
||
|
if (!string.IsNullOrEmpty(nullable))
|
||
|
stringBuilder.Append(" <Nullable>").Append(nullable).AppendLine("</Nullable>");
|
||
|
|
||
|
stringBuilder
|
||
|
.AppendLine(" </PropertyGroup>")
|
||
|
.AppendLine(" <PropertyGroup>")
|
||
|
.AppendLine(" <NoConfig>true</NoConfig>")
|
||
|
.AppendLine(" <NoStdLib>true</NoStdLib>")
|
||
|
.AppendLine(" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>")
|
||
|
.AppendLine(" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>")
|
||
|
.AppendLine(" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>")
|
||
|
.AppendLine(" </PropertyGroup>");
|
||
|
|
||
|
var analyzers = GetRoslynAnalyzers(assembly, responseFilesDataArgs);
|
||
|
if (analyzers.Length > 0)
|
||
|
{
|
||
|
stringBuilder.AppendLine(" <ItemGroup>");
|
||
|
foreach (var analyzer in analyzers)
|
||
|
stringBuilder.AppendLine($" <Analyzer Include=\"{analyzer.NormalizePath()}\" />");
|
||
|
stringBuilder.AppendLine(" </ItemGroup>");
|
||
|
}
|
||
|
|
||
|
var additionalFiles = GetRoslynAdditionalFiles(assembly, responseFilesDataArgs);
|
||
|
if (additionalFiles.Length > 0)
|
||
|
{
|
||
|
stringBuilder.AppendLine(" <ItemGroup>");
|
||
|
foreach (var additionalFile in additionalFiles)
|
||
|
stringBuilder.AppendLine($" <AdditionalFiles Include=\"{additionalFile}\" />");
|
||
|
stringBuilder.AppendLine(" </ItemGroup>");
|
||
|
}
|
||
|
|
||
|
var configFile = GetGlobalAnalyzerConfigFile(assembly);
|
||
|
if (!string.IsNullOrEmpty(configFile))
|
||
|
{
|
||
|
stringBuilder
|
||
|
.AppendLine(" <ItemGroup>")
|
||
|
.Append(" <GlobalAnalyzerConfigFiles Include=\"").Append(configFile).AppendLine("\" />")
|
||
|
.AppendLine(" </ItemGroup>");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static string GetGlobalAnalyzerConfigFile(ProjectPart assembly)
|
||
|
{
|
||
|
var configFile = string.Empty;
|
||
|
#if UNITY_2021_3 // https://github.com/JetBrains/resharper-unity/issues/2401
|
||
|
var type = assembly.CompilerOptions.GetType();
|
||
|
var propertyInfo = type.GetProperty("AnalyzerConfigPath");
|
||
|
if (propertyInfo != null && propertyInfo.GetValue(assembly.CompilerOptions) is string value)
|
||
|
{
|
||
|
configFile = value;
|
||
|
}
|
||
|
#elif UNITY_2022_2_OR_NEWER
|
||
|
configFile = assembly.CompilerOptions.AnalyzerConfigPath; // https://docs.unity3d.com/2021.3/Documentation/ScriptReference/Compilation.ScriptCompilerOptions.AnalyzerConfigPath.html
|
||
|
#endif
|
||
|
|
||
|
|
||
|
return configFile;
|
||
|
}
|
||
|
|
||
|
private static string[] GetRoslynAdditionalFiles(ProjectPart assembly, ILookup<string, string> otherResponseFilesData)
|
||
|
{
|
||
|
var additionalFilePathsFromCompilationPipeline = Array.Empty<string>();
|
||
|
#if UNITY_2021_3 // https://github.com/JetBrains/resharper-unity/issues/2401
|
||
|
var type = assembly.CompilerOptions.GetType();
|
||
|
var propertyInfo = type.GetProperty("RoslynAdditionalFilePaths");
|
||
|
if (propertyInfo != null && propertyInfo.GetValue(assembly.CompilerOptions) is string[] value)
|
||
|
{
|
||
|
additionalFilePathsFromCompilationPipeline = value;
|
||
|
}
|
||
|
#elif UNITY_2022_2_OR_NEWER // https://docs.unity3d.com/2021.3/Documentation/ScriptReference/Compilation.ScriptCompilerOptions.RoslynAdditionalFilePaths.html
|
||
|
additionalFilePathsFromCompilationPipeline = assembly.CompilerOptions.RoslynAdditionalFilePaths;
|
||
|
#endif
|
||
|
return otherResponseFilesData["additionalfile"]
|
||
|
.SelectMany(x=>x.Split(';'))
|
||
|
.Concat(additionalFilePathsFromCompilationPipeline)
|
||
|
.Distinct().ToArray();
|
||
|
}
|
||
|
|
||
|
string[] GetRoslynAnalyzers(ProjectPart assembly, ILookup<string, string> otherResponseFilesData)
|
||
|
{
|
||
|
#if UNITY_2020_2_OR_NEWER
|
||
|
return otherResponseFilesData["analyzer"].Concat(otherResponseFilesData["a"])
|
||
|
.SelectMany(x=>x.Split(';'))
|
||
|
#if !ROSLYN_ANALYZER_FIX
|
||
|
.Concat(m_AssemblyNameProvider.GetRoslynAnalyzerPaths())
|
||
|
#else
|
||
|
.Concat(assembly.CompilerOptions.RoslynAnalyzerDllPaths)
|
||
|
#endif
|
||
|
.Select(GetNormalisedAssemblyPath)
|
||
|
.Distinct()
|
||
|
.ToArray();
|
||
|
#else
|
||
|
return otherResponseFilesData["analyzer"].Concat(otherResponseFilesData["a"])
|
||
|
.SelectMany(x=>x.Split(';'))
|
||
|
.Distinct()
|
||
|
.Select(GetNormalisedAssemblyPath)
|
||
|
.ToArray();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
private IEnumerable<string> GetRoslynAnalyzerRulesetPaths(ProjectPart assembly, ILookup<string, string> otherResponseFilesData)
|
||
|
{
|
||
|
var paths = new HashSet<string>(otherResponseFilesData["ruleset"]);
|
||
|
#if UNITY_2020_2_OR_NEWER
|
||
|
if (!string.IsNullOrEmpty(assembly.CompilerOptions.RoslynAnalyzerRulesetPath))
|
||
|
paths.Add(assembly.CompilerOptions.RoslynAnalyzerRulesetPath);
|
||
|
#endif
|
||
|
|
||
|
return paths.Select(GetNormalisedAssemblyPath);
|
||
|
}
|
||
|
|
||
|
private static void AppendWarningAsError(StringBuilder stringBuilder,
|
||
|
IEnumerable<string> args, IEnumerable<string> argsMinus, IEnumerable<string> argsPlus)
|
||
|
{
|
||
|
var treatWarningsAsErrors = false;
|
||
|
var warningIds = new List<string>();
|
||
|
var notWarningIds = new List<string>(argsMinus);
|
||
|
|
||
|
foreach (var s in args)
|
||
|
{
|
||
|
if (s == "+" || s == "") treatWarningsAsErrors = true;
|
||
|
else if (s == "-") treatWarningsAsErrors = false;
|
||
|
else warningIds.Add(s);
|
||
|
}
|
||
|
|
||
|
warningIds.AddRange(argsPlus);
|
||
|
|
||
|
stringBuilder.Append(" <TreatWarningsAsErrors>").Append(treatWarningsAsErrors) .AppendLine("</TreatWarningsAsErrors>");
|
||
|
if (warningIds.Count > 0)
|
||
|
stringBuilder.Append(" <WarningsAsErrors>").CompatibleAppendJoin(';', warningIds).AppendLine("</WarningsAsErrors>");
|
||
|
if (notWarningIds.Count > 0)
|
||
|
stringBuilder.Append(" <WarningsNotAsErrors>").CompatibleAppendJoin(';', notWarningIds) .AppendLine("</WarningsNotAsErrors>");
|
||
|
}
|
||
|
|
||
|
private void SyncSolution(StringBuilder stringBuilder, List<ProjectPart> islands, Type[] types)
|
||
|
{
|
||
|
SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(stringBuilder, islands), types);
|
||
|
}
|
||
|
|
||
|
private string SolutionText(StringBuilder stringBuilder, List<ProjectPart> islands)
|
||
|
{
|
||
|
stringBuilder
|
||
|
.AppendLine()
|
||
|
.AppendLine("Microsoft Visual Studio Solution File, Format Version 11.00")
|
||
|
.AppendLine("# Visual Studio 2010");
|
||
|
foreach (var island in islands)
|
||
|
{
|
||
|
var projectName = m_AssemblyNameProvider.GetProjectName(island.Name, island.Defines);
|
||
|
|
||
|
// GUID is for C# class libraries
|
||
|
stringBuilder
|
||
|
.Append("Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"")
|
||
|
.Append(island.Name)
|
||
|
.Append("\", \"")
|
||
|
.Append(projectName)
|
||
|
.Append(".csproj\", \"{")
|
||
|
.Append(ProjectGuid(projectName))
|
||
|
.AppendLine("}\"")
|
||
|
.AppendLine("EndProject");
|
||
|
}
|
||
|
|
||
|
stringBuilder.AppendLine("Global")
|
||
|
.AppendLine("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution")
|
||
|
.AppendLine("\t\tDebug|Any CPU = Debug|Any CPU")
|
||
|
.AppendLine("\tEndGlobalSection")
|
||
|
.AppendLine("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
|
||
|
|
||
|
foreach (var island in islands)
|
||
|
{
|
||
|
var projectGuid = ProjectGuid(m_AssemblyNameProvider.GetProjectName(island.Name, island.Defines));
|
||
|
|
||
|
stringBuilder
|
||
|
.Append("\t\t{").Append(projectGuid).AppendLine("}.Debug|Any CPU.ActiveCfg = Debug|Any CPU")
|
||
|
.Append("\t\t{").Append(projectGuid).AppendLine("}.Debug|Any CPU.Build.0 = Debug|Any CPU");
|
||
|
}
|
||
|
|
||
|
stringBuilder.AppendLine("\tEndGlobalSection")
|
||
|
.AppendLine("\tGlobalSection(SolutionProperties) = preSolution")
|
||
|
.AppendLine("\t\tHideSolutionNode = FALSE")
|
||
|
.AppendLine("\tEndGlobalSection")
|
||
|
.AppendLine("EndGlobal");
|
||
|
|
||
|
return stringBuilder.ToString();
|
||
|
}
|
||
|
|
||
|
private static ILookup<string, string> GetOtherArgumentsFromResponseFilesData(List<ResponseFileData> responseFilesData)
|
||
|
{
|
||
|
var paths = responseFilesData.SelectMany(x =>
|
||
|
{
|
||
|
return x.OtherArguments
|
||
|
.Where(a => a.StartsWith("/", StringComparison.Ordinal) || a.StartsWith("-", StringComparison.Ordinal))
|
||
|
.Select(b =>
|
||
|
{
|
||
|
var index = b.IndexOf(":", StringComparison.Ordinal);
|
||
|
if (index > 0 && b.Length > index)
|
||
|
{
|
||
|
var key = b.Substring(1, index - 1);
|
||
|
return new KeyValuePair<string, string>(key, b.Substring(index + 1));
|
||
|
}
|
||
|
|
||
|
const string warnaserror = "warnaserror";
|
||
|
if (b.Substring(1).StartsWith(warnaserror, StringComparison.Ordinal))
|
||
|
{
|
||
|
return new KeyValuePair<string, string>(warnaserror, b.Substring(warnaserror.Length + 1));
|
||
|
}
|
||
|
const string nullable = "nullable";
|
||
|
if (b.Substring(1).StartsWith(nullable, StringComparison.Ordinal))
|
||
|
{
|
||
|
var res = b.Substring(nullable.Length + 1);
|
||
|
if (string.IsNullOrWhiteSpace(res) || res.Equals("+"))
|
||
|
res = "enable";
|
||
|
else if (res.Equals("-"))
|
||
|
res = "disable";
|
||
|
return new KeyValuePair<string, string>(nullable, res);
|
||
|
}
|
||
|
|
||
|
return default;
|
||
|
});
|
||
|
})
|
||
|
.Distinct()
|
||
|
.ToLookup(o => o.Key, pair => pair.Value);
|
||
|
return paths;
|
||
|
}
|
||
|
|
||
|
private string GetLangVersion(IEnumerable<string> langVersionList, ProjectPart assembly)
|
||
|
{
|
||
|
var langVersion = langVersionList.FirstOrDefault();
|
||
|
if (!string.IsNullOrWhiteSpace(langVersion))
|
||
|
return langVersion;
|
||
|
#if UNITY_2020_2_OR_NEWER
|
||
|
return assembly.CompilerOptions.LanguageVersion;
|
||
|
#else
|
||
|
return "latest";
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
public static IEnumerable<string> GetNoWarn(List<string> codes)
|
||
|
{
|
||
|
#if UNITY_2019_4 || UNITY_2020_1 // RIDER-77206 Unity 2020.1.3 'PlayerSettings' does not contain a definition for 'suppressCommonWarnings'
|
||
|
var type = typeof(PlayerSettings);
|
||
|
var propertyInfo = type.GetProperty("suppressCommonWarnings");
|
||
|
if (propertyInfo != null && propertyInfo.GetValue(null) is bool && (bool)propertyInfo.GetValue(null))
|
||
|
{
|
||
|
codes.AddRange(new[] {"0169", "0649"});
|
||
|
}
|
||
|
#elif UNITY_2020_2_OR_NEWER
|
||
|
if (PlayerSettings.suppressCommonWarnings)
|
||
|
codes.AddRange(new[] {"0169", "0649"});
|
||
|
#endif
|
||
|
|
||
|
return codes.Distinct();
|
||
|
}
|
||
|
|
||
|
private string ProjectGuid(string name)
|
||
|
{
|
||
|
if (!m_ProjectGuids.TryGetValue(name, out var guid))
|
||
|
{
|
||
|
guid = m_GUIDGenerator.ProjectGuid(m_ProjectName + name);
|
||
|
m_ProjectGuids.Add(name, guid);
|
||
|
}
|
||
|
|
||
|
return guid;
|
||
|
}
|
||
|
|
||
|
private string GetNormalisedAssemblyPath(string path)
|
||
|
{
|
||
|
if (!m_NormalisedPaths.TryGetValue(path, out var normalisedPath))
|
||
|
{
|
||
|
normalisedPath = Path.IsPathRooted(path) ? path : Path.GetFullPath(path);
|
||
|
normalisedPath = SecurityElement.Escape(normalisedPath).NormalizePath();
|
||
|
m_NormalisedPaths.Add(path, normalisedPath);
|
||
|
}
|
||
|
|
||
|
return normalisedPath;
|
||
|
}
|
||
|
|
||
|
private string GetAssemblyNameFromPath(string path)
|
||
|
{
|
||
|
if (!m_AssemblyNames.TryGetValue(path, out var name))
|
||
|
{
|
||
|
name = FileSystemUtil.FileNameWithoutExtension(path);
|
||
|
m_AssemblyNames.Add(path, name);
|
||
|
}
|
||
|
|
||
|
return name;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal class FilePathTrie<TData>
|
||
|
{
|
||
|
private static readonly char[] Separators = { '\\', '/' };
|
||
|
|
||
|
private readonly TrieNode m_Root = new TrieNode();
|
||
|
|
||
|
private class TrieNode
|
||
|
{
|
||
|
public Dictionary<string, TrieNode> Children;
|
||
|
public TData Data;
|
||
|
}
|
||
|
|
||
|
public void Insert(string filePath, TData data)
|
||
|
{
|
||
|
var parts = filePath.Split(Separators);
|
||
|
|
||
|
var node = m_Root;
|
||
|
foreach (var part in parts)
|
||
|
{
|
||
|
if (node.Children == null)
|
||
|
node.Children = new Dictionary<string, TrieNode>(StringComparer.OrdinalIgnoreCase);
|
||
|
// ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd
|
||
|
if (!node.Children.ContainsKey(part))
|
||
|
node.Children[part] = new TrieNode();
|
||
|
|
||
|
node = node.Children[part];
|
||
|
}
|
||
|
|
||
|
node.Data = data;
|
||
|
}
|
||
|
|
||
|
public TData FindClosestMatch(string filePath)
|
||
|
{
|
||
|
var parts = filePath.Split(Separators);
|
||
|
|
||
|
var node = m_Root;
|
||
|
foreach (var part in parts)
|
||
|
{
|
||
|
if (node.Children != null && node.Children.TryGetValue(part, out var next))
|
||
|
node = next;
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return node.Data;
|
||
|
}
|
||
|
}
|
||
|
}
|