ProjectGeneration.cs 33 KB

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