ProjectGeneration.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Security;
  6. using System.Text;
  7. using Packages.Rider.Editor.Util;
  8. using UnityEditor;
  9. using UnityEditor.Compilation;
  10. using UnityEditorInternal;
  11. using UnityEngine;
  12. namespace Packages.Rider.Editor.ProjectGeneration
  13. {
  14. internal class ProjectGeneration : IGenerator
  15. {
  16. private enum ScriptingLanguage
  17. {
  18. None,
  19. CSharp
  20. }
  21. public static readonly string MSBuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003";
  22. /// <summary>
  23. /// Map source extensions to ScriptingLanguages
  24. /// </summary>
  25. private static readonly Dictionary<string, ScriptingLanguage> k_BuiltinSupportedExtensions =
  26. new Dictionary<string, ScriptingLanguage>
  27. {
  28. { "cs", ScriptingLanguage.CSharp },
  29. { "uxml", ScriptingLanguage.None },
  30. { "uss", ScriptingLanguage.None },
  31. { "shader", ScriptingLanguage.None },
  32. { "compute", ScriptingLanguage.None },
  33. { "cginc", ScriptingLanguage.None },
  34. { "hlsl", ScriptingLanguage.None },
  35. { "glslinc", ScriptingLanguage.None },
  36. { "template", ScriptingLanguage.None },
  37. { "raytrace", ScriptingLanguage.None },
  38. { "json", ScriptingLanguage.None},
  39. { "rsp", ScriptingLanguage.None},
  40. { "asmdef", ScriptingLanguage.None},
  41. { "asmref", ScriptingLanguage.None},
  42. { "xaml", ScriptingLanguage.None},
  43. { "tt", ScriptingLanguage.None},
  44. { "t4", ScriptingLanguage.None},
  45. { "ttinclude", ScriptingLanguage.None}
  46. };
  47. private string m_SolutionProjectEntryTemplate = string.Join(Environment.NewLine,
  48. @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""",
  49. @"EndProject").Replace(" ", "\t");
  50. private string m_SolutionProjectConfigurationTemplate = string.Join(Environment.NewLine,
  51. @" {{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU",
  52. @" {{{0}}}.Debug|Any CPU.Build.0 = Debug|Any CPU").Replace(" ", "\t");
  53. private string[] m_ProjectSupportedExtensions = new string[0];
  54. public string ProjectDirectory { get; }
  55. private readonly string m_ProjectName;
  56. private readonly IAssemblyNameProvider m_AssemblyNameProvider;
  57. private readonly IFileIO m_FileIOProvider;
  58. private readonly IGUIDGenerator m_GUIDGenerator;
  59. internal static bool isRiderProjectGeneration; // workaround to https://github.cds.internal.unity3d.com/unity/com.unity.ide.rider/issues/28
  60. private const string k_ToolsVersion = "4.0";
  61. private const string k_ProductVersion = "10.0.20506";
  62. private const string k_BaseDirectory = ".";
  63. private const string k_TargetLanguageVersion = "latest";
  64. IAssemblyNameProvider IGenerator.AssemblyNameProvider => m_AssemblyNameProvider;
  65. public ProjectGeneration()
  66. : this(Directory.GetParent(Application.dataPath).FullName) { }
  67. public ProjectGeneration(string tempDirectory)
  68. : this(tempDirectory, new AssemblyNameProvider(), new FileIOProvider(), new GUIDProvider()) { }
  69. public ProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIoProvider, IGUIDGenerator guidGenerator)
  70. {
  71. ProjectDirectory = tempDirectory.NormalizePath();
  72. m_ProjectName = Path.GetFileName(ProjectDirectory);
  73. m_AssemblyNameProvider = assemblyNameProvider;
  74. m_FileIOProvider = fileIoProvider;
  75. m_GUIDGenerator = guidGenerator;
  76. }
  77. /// <summary>
  78. /// Syncs the scripting solution if any affected files are relevant.
  79. /// </summary>
  80. /// <returns>
  81. /// Whether the solution was synced.
  82. /// </returns>
  83. /// <param name='affectedFiles'>
  84. /// A set of files whose status has changed
  85. /// </param>
  86. /// <param name="reimportedFiles">
  87. /// A set of files that got reimported
  88. /// </param>
  89. /// <param name="checkProjectFiles">
  90. /// Check if project files were changed externally
  91. /// </param>
  92. public bool SyncIfNeeded(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles, bool checkProjectFiles = false)
  93. {
  94. SetupSupportedExtensions();
  95. if (HasFilesBeenModified(affectedFiles, reimportedFiles) || RiderScriptEditorData.instance.hasChanges
  96. || RiderScriptEditorData.instance.HasChangesInCompilationDefines()
  97. || (checkProjectFiles && LastWriteTracker.HasLastWriteTimeChanged()))
  98. {
  99. Sync();
  100. RiderScriptEditorData.instance.hasChanges = false;
  101. RiderScriptEditorData.instance.InvalidateSavedCompilationDefines();
  102. return true;
  103. }
  104. return false;
  105. }
  106. private bool HasFilesBeenModified(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles)
  107. {
  108. return affectedFiles.Any(ShouldFileBePartOfSolution) || reimportedFiles.Any(ShouldSyncOnReimportedAsset);
  109. }
  110. private static bool ShouldSyncOnReimportedAsset(string asset)
  111. {
  112. var extension = Path.GetExtension(asset);
  113. return extension == ".asmdef" || extension == ".asmref" || Path.GetFileName(asset) == "csc.rsp";
  114. }
  115. public void Sync()
  116. {
  117. SetupSupportedExtensions();
  118. var types = GetAssetPostprocessorTypes();
  119. isRiderProjectGeneration = true;
  120. var externalCodeAlreadyGeneratedProjects = OnPreGeneratingCSProjectFiles(types);
  121. isRiderProjectGeneration = false;
  122. if (!externalCodeAlreadyGeneratedProjects)
  123. {
  124. GenerateAndWriteSolutionAndProjects(types);
  125. }
  126. OnGeneratedCSProjectFiles(types);
  127. m_AssemblyNameProvider.ResetPackageInfoCache();
  128. m_AssemblyNameProvider.ResetAssembliesCache();
  129. }
  130. public bool HasSolutionBeenGenerated()
  131. {
  132. return m_FileIOProvider.Exists(SolutionFile());
  133. }
  134. private void SetupSupportedExtensions()
  135. {
  136. m_ProjectSupportedExtensions = m_AssemblyNameProvider.ProjectSupportedExtensions;
  137. }
  138. private bool ShouldFileBePartOfSolution(string file)
  139. {
  140. // Exclude files coming from packages except if they are internalized.
  141. if (m_AssemblyNameProvider.IsInternalizedPackagePath(file))
  142. {
  143. return false;
  144. }
  145. return HasValidExtension(file);
  146. }
  147. public bool HasValidExtension(string file)
  148. {
  149. var extension = Path.GetExtension(file);
  150. // Dll's are not scripts but still need to be included..
  151. if (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase))
  152. return true;
  153. return IsSupportedExtension(extension);
  154. }
  155. private bool IsSupportedExtension(string extension)
  156. {
  157. extension = extension.TrimStart('.');
  158. return k_BuiltinSupportedExtensions.ContainsKey(extension) || m_ProjectSupportedExtensions.Contains(extension);
  159. }
  160. public void GenerateAndWriteSolutionAndProjects(Type[] types)
  161. {
  162. // Only synchronize islands that have associated source files and ones that we actually want in the project.
  163. // This also filters out DLLs coming from .asmdef files in packages.
  164. var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution).ToArray();
  165. var assemblyNames = new HashSet<string>(assemblies.Select(a => a.name));
  166. var allAssetProjectParts = GenerateAllAssetProjectParts();
  167. var projectParts = new List<ProjectPart>();
  168. foreach (var assembly in assemblies)
  169. {
  170. allAssetProjectParts.TryGetValue(assembly.name, out var additionalAssetsForProject);
  171. projectParts.Add(new ProjectPart(assembly.name, assembly, additionalAssetsForProject));
  172. }
  173. var executingAssemblyName = typeof(ProjectGeneration).Assembly.GetName().Name;
  174. var riderAssembly = m_AssemblyNameProvider.GetAssemblies(_ => true).FirstOrDefault(a=>a.name == executingAssemblyName);
  175. var projectPartsWithoutAssembly = allAssetProjectParts.Where(a => !assemblyNames.Contains(a.Key));
  176. projectParts.AddRange(projectPartsWithoutAssembly.Select(allAssetProjectPart =>
  177. AddProjectPart(allAssetProjectPart.Key, riderAssembly, allAssetProjectPart.Value)));
  178. if (!projectParts.Any()) // just an empty project, when there are no files at all
  179. {
  180. var assetProjectPart = $" <Folder Include=\"Assets\"/>{Environment.NewLine}";
  181. projectParts.Add(AddProjectPart("Assembly-CSharp", riderAssembly, assetProjectPart));
  182. }
  183. SyncSolution(projectParts.ToArray(), types);
  184. foreach (var projectPart in projectParts)
  185. {
  186. SyncProject(projectPart, types);
  187. }
  188. }
  189. private static ProjectPart AddProjectPart(string assemblyName, Assembly riderAssembly, string assetProjectPart)
  190. {
  191. Assembly assembly = null;
  192. if (riderAssembly != null)
  193. // We want to add those references, so that Rider would detect Unity path and version and provide rich features for shader files
  194. assembly = new Assembly(assemblyName, riderAssembly.outputPath, Array.Empty<string>(),
  195. new []{"UNITY_EDITOR"},
  196. Array.Empty<Assembly>(),
  197. riderAssembly.compiledAssemblyReferences.Where(a =>
  198. a.EndsWith("UnityEditor.dll", StringComparison.Ordinal) || a.EndsWith("UnityEngine.dll", StringComparison.Ordinal) ||
  199. a.EndsWith("UnityEngine.CoreModule.dll", StringComparison.Ordinal)).ToArray(), riderAssembly.flags);
  200. return new ProjectPart(assemblyName, assembly, assetProjectPart);
  201. }
  202. private Dictionary<string, string> GenerateAllAssetProjectParts()
  203. {
  204. var stringBuilders = new Dictionary<string, StringBuilder>();
  205. foreach (var asset in m_AssemblyNameProvider.GetAllAssetPaths())
  206. {
  207. // Exclude files coming from packages except if they are internalized.
  208. if (m_AssemblyNameProvider.IsInternalizedPackagePath(asset))
  209. {
  210. continue;
  211. }
  212. var extension = Path.GetExtension(asset);
  213. if (IsSupportedExtension(extension) && !extension.Equals(".cs", StringComparison.OrdinalIgnoreCase))
  214. {
  215. // Find assembly the asset belongs to by adding script extension and using compilation pipeline.
  216. var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".cs");
  217. if (string.IsNullOrEmpty(assemblyName))
  218. {
  219. continue;
  220. }
  221. assemblyName = FileSystemUtil.FileNameWithoutExtension(assemblyName);
  222. if (!stringBuilders.TryGetValue(assemblyName, out var projectBuilder))
  223. {
  224. projectBuilder = new StringBuilder();
  225. stringBuilders[assemblyName] = projectBuilder;
  226. }
  227. projectBuilder.Append(" <None Include=\"").Append(m_FileIOProvider.EscapedRelativePathFor(asset, ProjectDirectory)).Append("\" />")
  228. .Append(Environment.NewLine);
  229. }
  230. }
  231. var result = new Dictionary<string, string>();
  232. foreach (var entry in stringBuilders)
  233. result[entry.Key] = entry.Value.ToString();
  234. return result;
  235. }
  236. private void SyncProject(
  237. ProjectPart island,
  238. Type[] types)
  239. {
  240. SyncProjectFileIfNotChanged(
  241. ProjectFile(island),
  242. ProjectText(island),
  243. types);
  244. }
  245. private void SyncProjectFileIfNotChanged(string path, string newContents, Type[] types)
  246. {
  247. if (Path.GetExtension(path) == ".csproj")
  248. {
  249. newContents = OnGeneratedCSProject(path, newContents, types);
  250. }
  251. SyncFileIfNotChanged(path, newContents);
  252. }
  253. private void SyncSolutionFileIfNotChanged(string path, string newContents, Type[] types)
  254. {
  255. newContents = OnGeneratedSlnSolution(path, newContents, types);
  256. SyncFileIfNotChanged(path, newContents);
  257. }
  258. private static void OnGeneratedCSProjectFiles(Type[] types)
  259. {
  260. foreach (var type in types)
  261. {
  262. var method = type.GetMethod("OnGeneratedCSProjectFiles",
  263. System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
  264. System.Reflection.BindingFlags.Static);
  265. if (method == null)
  266. {
  267. continue;
  268. }
  269. Debug.LogWarning("OnGeneratedCSProjectFiles is not supported.");
  270. // RIDER-51958
  271. //method.Invoke(null, args);
  272. }
  273. }
  274. public static Type[] GetAssetPostprocessorTypes()
  275. {
  276. return TypeCache.GetTypesDerivedFrom<AssetPostprocessor>().ToArray(); // doesn't find types from EditorPlugin, which is fine
  277. }
  278. private static bool OnPreGeneratingCSProjectFiles(Type[] types)
  279. {
  280. var result = false;
  281. foreach (var type in types)
  282. {
  283. var args = new object[0];
  284. var method = type.GetMethod("OnPreGeneratingCSProjectFiles",
  285. System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
  286. System.Reflection.BindingFlags.Static);
  287. if (method == null)
  288. {
  289. continue;
  290. }
  291. var returnValue = method.Invoke(null, args);
  292. if (method.ReturnType == typeof(bool))
  293. {
  294. result |= (bool)returnValue;
  295. }
  296. }
  297. return result;
  298. }
  299. private static string OnGeneratedCSProject(string path, string content, Type[] types)
  300. {
  301. foreach (var type in types)
  302. {
  303. var args = new[] { path, content };
  304. var method = type.GetMethod("OnGeneratedCSProject",
  305. System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
  306. System.Reflection.BindingFlags.Static);
  307. if (method == null)
  308. {
  309. continue;
  310. }
  311. var returnValue = method.Invoke(null, args);
  312. if (method.ReturnType == typeof(string))
  313. {
  314. content = (string)returnValue;
  315. }
  316. }
  317. return content;
  318. }
  319. private static string OnGeneratedSlnSolution(string path, string content, Type[] types)
  320. {
  321. foreach (var type in types)
  322. {
  323. var args = new[] { path, content };
  324. var method = type.GetMethod("OnGeneratedSlnSolution",
  325. System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
  326. System.Reflection.BindingFlags.Static);
  327. if (method == null)
  328. {
  329. continue;
  330. }
  331. var returnValue = method.Invoke(null, args);
  332. if (method.ReturnType == typeof(string))
  333. {
  334. content = (string)returnValue;
  335. }
  336. }
  337. return content;
  338. }
  339. private void SyncFileIfNotChanged(string path, string newContents)
  340. {
  341. try
  342. {
  343. if (m_FileIOProvider.Exists(path) && newContents == m_FileIOProvider.ReadAllText(path))
  344. {
  345. return;
  346. }
  347. }
  348. catch (Exception exception)
  349. {
  350. Debug.LogException(exception);
  351. }
  352. m_FileIOProvider.WriteAllText(path, newContents);
  353. }
  354. private string ProjectText(ProjectPart assembly)
  355. {
  356. var responseFilesData = assembly.ParseResponseFileData(m_AssemblyNameProvider, ProjectDirectory).ToList();
  357. var projectBuilder = new StringBuilder(ProjectHeader(assembly, responseFilesData));
  358. foreach (var file in assembly.SourceFiles)
  359. {
  360. var fullFile = m_FileIOProvider.EscapedRelativePathFor(file, ProjectDirectory);
  361. projectBuilder.Append(" <Compile Include=\"").Append(fullFile).Append("\" />").Append(Environment.NewLine);
  362. }
  363. projectBuilder.Append(assembly.AssetsProjectPart);
  364. var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r));
  365. var internalAssemblyReferences = assembly.AssemblyReferences
  366. .Where(reference => !reference.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath);
  367. var allReferences =
  368. assembly.CompiledAssemblyReferences
  369. .Union(responseRefs)
  370. .Union(internalAssemblyReferences).ToArray();
  371. foreach (var reference in allReferences)
  372. {
  373. var fullReference = Path.IsPathRooted(reference) ? reference : Path.Combine(ProjectDirectory, reference);
  374. AppendReference(fullReference, projectBuilder);
  375. }
  376. if (0 < assembly.AssemblyReferences.Length)
  377. {
  378. projectBuilder.Append(" </ItemGroup>").Append(Environment.NewLine);
  379. projectBuilder.Append(" <ItemGroup>").Append(Environment.NewLine);
  380. foreach (var reference in assembly.AssemblyReferences.Where(i => i.sourceFiles.Any(ShouldFileBePartOfSolution)))
  381. {
  382. var name = m_AssemblyNameProvider.GetProjectName(reference.name, reference.defines);
  383. projectBuilder.Append(" <ProjectReference Include=\"").Append(name).Append(GetProjectExtension()).Append("\">").Append(Environment.NewLine);
  384. projectBuilder.Append(" <Project>{").Append(ProjectGuid(name)).Append("}</Project>").Append(Environment.NewLine);
  385. projectBuilder.Append(" <Name>").Append(name).Append("</Name>").Append(Environment.NewLine);
  386. projectBuilder.Append(" </ProjectReference>").Append(Environment.NewLine);
  387. }
  388. }
  389. projectBuilder.Append(ProjectFooter());
  390. return projectBuilder.ToString();
  391. }
  392. private static void AppendReference(string fullReference, StringBuilder projectBuilder)
  393. {
  394. var escapedFullPath = SecurityElement.Escape(fullReference);
  395. escapedFullPath = escapedFullPath.NormalizePath();
  396. projectBuilder.Append(" <Reference Include=\"").Append(FileSystemUtil.FileNameWithoutExtension(escapedFullPath))
  397. .Append("\">").Append(Environment.NewLine);
  398. projectBuilder.Append(" <HintPath>").Append(escapedFullPath).Append("</HintPath>").Append(Environment.NewLine);
  399. projectBuilder.Append(" </Reference>").Append(Environment.NewLine);
  400. }
  401. private string ProjectFile(ProjectPart projectPart)
  402. {
  403. return Path.Combine(ProjectDirectory, $"{m_AssemblyNameProvider.GetProjectName(projectPart.Name, projectPart.Defines)}.csproj");
  404. }
  405. public string SolutionFile()
  406. {
  407. return Path.Combine(ProjectDirectory, $"{m_ProjectName}.sln");
  408. }
  409. private string ProjectHeader(
  410. ProjectPart assembly,
  411. List<ResponseFileData> responseFilesData
  412. )
  413. {
  414. var otherResponseFilesData = GetOtherArgumentsFromResponseFilesData(responseFilesData);
  415. var arguments = new object[]
  416. {
  417. k_ToolsVersion,
  418. k_ProductVersion,
  419. ProjectGuid(m_AssemblyNameProvider.GetProjectName(assembly.Name, assembly.Defines)),
  420. InternalEditorUtility.GetEngineAssemblyPath(),
  421. InternalEditorUtility.GetEditorAssemblyPath(),
  422. string.Join(";", assembly.Defines.Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray()),
  423. MSBuildNamespaceUri,
  424. assembly.Name,
  425. assembly.OutputPath,
  426. assembly.RootNamespace,
  427. assembly.CompilerOptions.ApiCompatibilityLevel,
  428. GenerateLangVersion(otherResponseFilesData["langversion"], assembly),
  429. k_BaseDirectory,
  430. assembly.CompilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe),
  431. GenerateNoWarn(otherResponseFilesData["nowarn"].Distinct().ToList()),
  432. GenerateAnalyserItemGroup(RetrieveRoslynAnalyzers(assembly, otherResponseFilesData)),
  433. GenerateAnalyserAdditionalFiles(otherResponseFilesData["additionalfile"].SelectMany(x=>x.Split(';')).Distinct().ToArray()),
  434. GenerateRoslynAnalyzerRulesetPath(assembly, otherResponseFilesData),
  435. GenerateWarningLevel(otherResponseFilesData["warn"].Concat(otherResponseFilesData["w"]).Distinct()),
  436. GenerateWarningAsError(otherResponseFilesData["warnaserror"], otherResponseFilesData["warnaserror-"], otherResponseFilesData["warnaserror+"]),
  437. GenerateDocumentationFile(otherResponseFilesData["doc"].ToArray()),
  438. GenerateNullable(otherResponseFilesData["nullable"])
  439. };
  440. try
  441. {
  442. return string.Format(GetProjectHeaderTemplate(), arguments);
  443. }
  444. catch (Exception)
  445. {
  446. throw new NotSupportedException(
  447. "Failed creating c# project because the c# project header did not have the correct amount of arguments, which is " +
  448. arguments.Length);
  449. }
  450. }
  451. string[] RetrieveRoslynAnalyzers(ProjectPart assembly, ILookup<string, string> otherResponseFilesData)
  452. {
  453. #if UNITY_2020_2_OR_NEWER
  454. return otherResponseFilesData["analyzer"].Concat(otherResponseFilesData["a"])
  455. .SelectMany(x=>x.Split(';'))
  456. #if !ROSLYN_ANALYZER_FIX
  457. .Concat(m_AssemblyNameProvider.GetRoslynAnalyzerPaths())
  458. #else
  459. .Concat(assembly.CompilerOptions.RoslynAnalyzerDllPaths)
  460. #endif
  461. .Select(MakeAbsolutePath)
  462. .Distinct()
  463. .ToArray();
  464. #else
  465. return otherResponseFilesData["analyzer"].Concat(otherResponseFilesData["a"])
  466. .SelectMany(x=>x.Split(';'))
  467. .Distinct()
  468. .Select(MakeAbsolutePath)
  469. .ToArray();
  470. #endif
  471. }
  472. private static string MakeAbsolutePath(string path)
  473. {
  474. return Path.IsPathRooted(path) ? path : Path.GetFullPath(path);
  475. }
  476. private string GenerateRoslynAnalyzerRulesetPath(ProjectPart assembly, ILookup<string, string> otherResponseFilesData)
  477. {
  478. #if UNITY_2020_2_OR_NEWER
  479. return GenerateAnalyserRuleSet(otherResponseFilesData["ruleset"].Append(assembly.CompilerOptions.RoslynAnalyzerRulesetPath).Where(a=>!string.IsNullOrEmpty(a)).Distinct().Select(x => MakeAbsolutePath(x).NormalizePath()).ToArray());
  480. #else
  481. return GenerateAnalyserRuleSet(otherResponseFilesData["ruleset"].Distinct().Select(x => MakeAbsolutePath(x).NormalizePath()).ToArray());
  482. #endif
  483. }
  484. private string GenerateNullable(IEnumerable<string> enumerable)
  485. {
  486. var val = enumerable.FirstOrDefault();
  487. if (string.IsNullOrWhiteSpace(val))
  488. return string.Empty;
  489. return $"{Environment.NewLine} <Nullable>{val}</Nullable>";
  490. }
  491. private static string GenerateDocumentationFile(string[] paths)
  492. {
  493. if (!paths.Any())
  494. return String.Empty;
  495. return $"{Environment.NewLine}{string.Join(Environment.NewLine, paths.Select(a => $" <DocumentationFile>{a}</DocumentationFile>"))}";
  496. }
  497. private static string GenerateWarningAsError(IEnumerable<string> args, IEnumerable<string> argsMinus, IEnumerable<string> argsPlus)
  498. {
  499. var returnValue = String.Empty;
  500. var allWarningsAsErrors = false;
  501. var warningIds = new List<string>();
  502. foreach (var s in args)
  503. {
  504. if (s == "+" || s == string.Empty) allWarningsAsErrors = true;
  505. else if (s == "-") allWarningsAsErrors = false;
  506. else
  507. {
  508. warningIds.Add(s);
  509. }
  510. }
  511. warningIds.AddRange(argsPlus);
  512. returnValue += $@" <TreatWarningsAsErrors>{allWarningsAsErrors}</TreatWarningsAsErrors>";
  513. if (warningIds.Any())
  514. {
  515. returnValue += $"{Environment.NewLine} <WarningsAsErrors>{string.Join(";", warningIds)}</WarningsAsErrors>";
  516. }
  517. if (argsMinus.Any())
  518. returnValue += $"{Environment.NewLine} <WarningsNotAsErrors>{string.Join(";", argsMinus)}</WarningsNotAsErrors>";
  519. return $"{Environment.NewLine}{returnValue}";
  520. }
  521. private static string GenerateWarningLevel(IEnumerable<string> warningLevel)
  522. {
  523. var level = warningLevel.FirstOrDefault();
  524. if (!string.IsNullOrWhiteSpace(level))
  525. return level;
  526. return 4.ToString();
  527. }
  528. private static string GetSolutionText()
  529. {
  530. return string.Join(Environment.NewLine,
  531. @"",
  532. @"Microsoft Visual Studio Solution File, Format Version {0}",
  533. @"# Visual Studio {1}",
  534. @"{2}",
  535. @"Global",
  536. @" GlobalSection(SolutionConfigurationPlatforms) = preSolution",
  537. @" Debug|Any CPU = Debug|Any CPU",
  538. @" EndGlobalSection",
  539. @" GlobalSection(ProjectConfigurationPlatforms) = postSolution",
  540. @"{3}",
  541. @" EndGlobalSection",
  542. @" GlobalSection(SolutionProperties) = preSolution",
  543. @" HideSolutionNode = FALSE",
  544. @" EndGlobalSection",
  545. @"EndGlobal",
  546. @"").Replace(" ", "\t");
  547. }
  548. private static string GetProjectFooterTemplate()
  549. {
  550. return string.Join(Environment.NewLine,
  551. @" </ItemGroup>",
  552. @" <Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />",
  553. @" <!-- To modify your build process, add your task inside one of the targets below and uncomment it.",
  554. @" Other similar extension points exist, see Microsoft.Common.targets.",
  555. @" <Target Name=""BeforeBuild"">",
  556. @" </Target>",
  557. @" <Target Name=""AfterBuild"">",
  558. @" </Target>",
  559. @" -->",
  560. @"</Project>",
  561. @"");
  562. }
  563. private static string GetProjectHeaderTemplate()
  564. {
  565. var header = new[]
  566. {
  567. @"<?xml version=""1.0"" encoding=""utf-8""?>",
  568. @"<Project ToolsVersion=""{0}"" DefaultTargets=""Build"" xmlns=""{6}"">",
  569. @" <PropertyGroup>",
  570. @" <LangVersion>{11}</LangVersion>",
  571. @" <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package</_TargetFrameworkDirectories>",
  572. @" <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package</_FullFrameworkReferenceAssemblyPaths>",
  573. @" <DisableHandlePackageFileConflicts>true</DisableHandlePackageFileConflicts>{17}",
  574. @" </PropertyGroup>",
  575. @" <PropertyGroup>",
  576. @" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>",
  577. @" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>",
  578. @" <ProductVersion>{1}</ProductVersion>",
  579. @" <SchemaVersion>2.0</SchemaVersion>",
  580. @" <RootNamespace>{9}</RootNamespace>",
  581. @" <ProjectGuid>{{{2}}}</ProjectGuid>",
  582. @" <ProjectTypeGuids>{{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1}};{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}</ProjectTypeGuids>",
  583. @" <OutputType>Library</OutputType>",
  584. @" <AppDesignerFolder>Properties</AppDesignerFolder>",
  585. @" <AssemblyName>{7}</AssemblyName>",
  586. @" <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>",
  587. @" <FileAlignment>512</FileAlignment>",
  588. @" <BaseDirectory>{12}</BaseDirectory>",
  589. @" </PropertyGroup>",
  590. @" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">",
  591. @" <DebugSymbols>true</DebugSymbols>",
  592. @" <DebugType>full</DebugType>",
  593. @" <Optimize>false</Optimize>",
  594. @" <OutputPath>{8}</OutputPath>",
  595. @" <DefineConstants>{5}</DefineConstants>",
  596. @" <ErrorReport>prompt</ErrorReport>",
  597. @" <WarningLevel>{18}</WarningLevel>",
  598. @" <NoWarn>{14}</NoWarn>",
  599. @" <AllowUnsafeBlocks>{13}</AllowUnsafeBlocks>{19}{20}{21}",
  600. @" </PropertyGroup>"
  601. };
  602. var forceExplicitReferences = new[]
  603. {
  604. @" <PropertyGroup>",
  605. @" <NoConfig>true</NoConfig>",
  606. @" <NoStdLib>true</NoStdLib>",
  607. @" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>",
  608. @" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>",
  609. @" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>",
  610. @" </PropertyGroup>"
  611. };
  612. var footer = new[]
  613. {
  614. @"{15}{16} <ItemGroup>",
  615. @""
  616. };
  617. var pieces = header.Concat(forceExplicitReferences).Concat(footer).ToArray();
  618. return string.Join(Environment.NewLine, pieces);
  619. }
  620. private void SyncSolution(ProjectPart[] islands, Type[] types)
  621. {
  622. SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(islands), types);
  623. }
  624. private string SolutionText(ProjectPart[] islands)
  625. {
  626. var fileversion = "11.00";
  627. var vsversion = "2010";
  628. var projectEntries = GetProjectEntries(islands);
  629. var projectConfigurations = string.Join(Environment.NewLine,
  630. islands.Select(i => GetProjectActiveConfigurations(ProjectGuid(m_AssemblyNameProvider.GetProjectName(i.Name, i.Defines)))).ToArray());
  631. return string.Format(GetSolutionText(), fileversion, vsversion, projectEntries, projectConfigurations);
  632. }
  633. private static string GenerateAnalyserItemGroup(string[] paths)
  634. {
  635. // <ItemGroup>
  636. // <Analyzer Include="..\packages\Comments_analyser.1.0.6626.21356\analyzers\dotnet\cs\Comments_analyser.dll" />
  637. // <Analyzer Include="..\packages\UnityEngineAnalyzer.1.0.0.0\analyzers\dotnet\cs\UnityEngineAnalyzer.dll" />
  638. // </ItemGroup>
  639. if (!paths.Any())
  640. return string.Empty;
  641. var analyserBuilder = new StringBuilder();
  642. analyserBuilder.AppendLine(" <ItemGroup>");
  643. foreach (var path in paths)
  644. {
  645. analyserBuilder.AppendLine($" <Analyzer Include=\"{path.NormalizePath()}\" />");
  646. }
  647. analyserBuilder.AppendLine(" </ItemGroup>");
  648. return analyserBuilder.ToString();
  649. }
  650. private static ILookup<string, string> GetOtherArgumentsFromResponseFilesData(List<ResponseFileData> responseFilesData)
  651. {
  652. var paths = responseFilesData.SelectMany(x =>
  653. {
  654. return x.OtherArguments
  655. .Where(a => a.StartsWith("/", StringComparison.Ordinal) || a.StartsWith("-", StringComparison.Ordinal))
  656. .Select(b =>
  657. {
  658. var index = b.IndexOf(":", StringComparison.Ordinal);
  659. if (index > 0 && b.Length > index)
  660. {
  661. var key = b.Substring(1, index - 1);
  662. return new KeyValuePair<string, string>(key, b.Substring(index + 1));
  663. }
  664. const string warnaserror = "warnaserror";
  665. if (b.Substring(1).StartsWith(warnaserror, StringComparison.Ordinal))
  666. {
  667. return new KeyValuePair<string, string>(warnaserror, b.Substring(warnaserror.Length + 1));
  668. }
  669. const string nullable = "nullable";
  670. if (b.Substring(1).StartsWith(nullable, StringComparison.Ordinal))
  671. {
  672. var res = b.Substring(nullable.Length + 1);
  673. if (string.IsNullOrWhiteSpace(res) || res.Equals("+"))
  674. res = "enable";
  675. else if (res.Equals("-"))
  676. res = "disable";
  677. return new KeyValuePair<string, string>(nullable, res);
  678. }
  679. return default;
  680. });
  681. })
  682. .Distinct()
  683. .ToLookup(o => o.Key, pair => pair.Value);
  684. return paths;
  685. }
  686. private string GenerateLangVersion(IEnumerable<string> langVersionList, ProjectPart assembly)
  687. {
  688. var langVersion = langVersionList.FirstOrDefault();
  689. if (!string.IsNullOrWhiteSpace(langVersion))
  690. return langVersion;
  691. #if UNITY_2020_2_OR_NEWER
  692. return assembly.CompilerOptions.LanguageVersion;
  693. #else
  694. return k_TargetLanguageVersion;
  695. #endif
  696. }
  697. private static string GenerateAnalyserRuleSet(string[] paths)
  698. {
  699. //<CodeAnalysisRuleSet>..\path\to\myrules.ruleset</CodeAnalysisRuleSet>
  700. if (!paths.Any())
  701. return string.Empty;
  702. return $"{Environment.NewLine}{string.Join(Environment.NewLine, paths.Select(a => $" <CodeAnalysisRuleSet>{a}</CodeAnalysisRuleSet>"))}";
  703. }
  704. private static string GenerateAnalyserAdditionalFiles(string[] paths)
  705. {
  706. if (!paths.Any())
  707. return string.Empty;
  708. var analyserBuilder = new StringBuilder();
  709. analyserBuilder.AppendLine(" <ItemGroup>");
  710. foreach (var path in paths)
  711. {
  712. analyserBuilder.AppendLine($" <AdditionalFiles Include=\"{path}\" />");
  713. }
  714. analyserBuilder.AppendLine(" </ItemGroup>");
  715. return analyserBuilder.ToString();
  716. }
  717. public static string GenerateNoWarn(List<string> codes)
  718. {
  719. #if UNITY_2020_1 // RIDER-77206 Unity 2020.1.3 'PlayerSettings' does not contain a definition for 'suppressCommonWarnings'
  720. var type = typeof(PlayerSettings);
  721. var propertyInfo = type.GetProperty("suppressCommonWarnings");
  722. if (propertyInfo != null && propertyInfo.GetValue(null) is bool && (bool)propertyInfo.GetValue(null))
  723. {
  724. codes.AddRange(new[] {"0169", "0649"});
  725. }
  726. #elif UNITY_2020_2_OR_NEWER
  727. if (PlayerSettings.suppressCommonWarnings)
  728. codes.AddRange(new[] {"0169", "0649"});
  729. #endif
  730. if (!codes.Any())
  731. return string.Empty;
  732. return string.Join(",", codes.Distinct());
  733. }
  734. private string GetProjectEntries(ProjectPart[] islands)
  735. {
  736. var projectEntries = islands.Select(i => string.Format(
  737. m_SolutionProjectEntryTemplate,
  738. SolutionGuidGenerator.GuidForSolution(),
  739. i.Name,
  740. Path.GetFileName(ProjectFile(i)),
  741. ProjectGuid(m_AssemblyNameProvider.GetProjectName(i.Name, i.Defines))
  742. ));
  743. return string.Join(Environment.NewLine, projectEntries.ToArray());
  744. }
  745. /// <summary>
  746. /// Generate the active configuration string for a given project guid
  747. /// </summary>
  748. private string GetProjectActiveConfigurations(string projectGuid)
  749. {
  750. return string.Format(
  751. m_SolutionProjectConfigurationTemplate,
  752. projectGuid);
  753. }
  754. private static string ProjectFooter()
  755. {
  756. return GetProjectFooterTemplate();
  757. }
  758. private static string GetProjectExtension()
  759. {
  760. return ".csproj";
  761. }
  762. private string ProjectGuid(string name)
  763. {
  764. return m_GUIDGenerator.ProjectGuid(m_ProjectName + name);
  765. }
  766. }
  767. }