Brak opisu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProjectGeneration.cs 34KB

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