暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

ProjectGeneration.cs 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Security;
  6. using System.Security.Cryptography;
  7. using SR = System.Reflection;
  8. using System.Text;
  9. using UnityEditor;
  10. using UnityEditor.Compilation;
  11. using UnityEngine;
  12. using UnityEngine.Profiling;
  13. namespace VSCodeEditor
  14. {
  15. public interface IGenerator
  16. {
  17. bool SyncIfNeeded(List<string> affectedFiles, string[] reimportedFiles);
  18. void Sync();
  19. string SolutionFile();
  20. string ProjectDirectory { get; }
  21. IAssemblyNameProvider AssemblyNameProvider { get; }
  22. void GenerateAll(bool generateAll);
  23. bool SolutionExists();
  24. }
  25. public class ProjectGeneration : IGenerator
  26. {
  27. enum ScriptingLanguage
  28. {
  29. None,
  30. CSharp
  31. }
  32. public static readonly string MSBuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003";
  33. const string k_WindowsNewline = "\r\n";
  34. const string k_SettingsJson = @"{
  35. ""files.exclude"":
  36. {
  37. ""**/.DS_Store"":true,
  38. ""**/.git"":true,
  39. ""**/.gitmodules"":true,
  40. ""**/*.booproj"":true,
  41. ""**/*.pidb"":true,
  42. ""**/*.suo"":true,
  43. ""**/*.user"":true,
  44. ""**/*.userprefs"":true,
  45. ""**/*.unityproj"":true,
  46. ""**/*.dll"":true,
  47. ""**/*.exe"":true,
  48. ""**/*.pdf"":true,
  49. ""**/*.mid"":true,
  50. ""**/*.midi"":true,
  51. ""**/*.wav"":true,
  52. ""**/*.gif"":true,
  53. ""**/*.ico"":true,
  54. ""**/*.jpg"":true,
  55. ""**/*.jpeg"":true,
  56. ""**/*.png"":true,
  57. ""**/*.psd"":true,
  58. ""**/*.tga"":true,
  59. ""**/*.tif"":true,
  60. ""**/*.tiff"":true,
  61. ""**/*.3ds"":true,
  62. ""**/*.3DS"":true,
  63. ""**/*.fbx"":true,
  64. ""**/*.FBX"":true,
  65. ""**/*.lxo"":true,
  66. ""**/*.LXO"":true,
  67. ""**/*.ma"":true,
  68. ""**/*.MA"":true,
  69. ""**/*.obj"":true,
  70. ""**/*.OBJ"":true,
  71. ""**/*.asset"":true,
  72. ""**/*.cubemap"":true,
  73. ""**/*.flare"":true,
  74. ""**/*.mat"":true,
  75. ""**/*.meta"":true,
  76. ""**/*.prefab"":true,
  77. ""**/*.unity"":true,
  78. ""build/"":true,
  79. ""Build/"":true,
  80. ""Library/"":true,
  81. ""library/"":true,
  82. ""obj/"":true,
  83. ""Obj/"":true,
  84. ""ProjectSettings/"":true,
  85. ""temp/"":true,
  86. ""Temp/"":true
  87. }
  88. }";
  89. /// <summary>
  90. /// Map source extensions to ScriptingLanguages
  91. /// </summary>
  92. static readonly Dictionary<string, ScriptingLanguage> k_BuiltinSupportedExtensions = new Dictionary<string, ScriptingLanguage>
  93. {
  94. { "cs", ScriptingLanguage.CSharp },
  95. { "uxml", ScriptingLanguage.None },
  96. { "uss", ScriptingLanguage.None },
  97. { "shader", ScriptingLanguage.None },
  98. { "compute", ScriptingLanguage.None },
  99. { "cginc", ScriptingLanguage.None },
  100. { "hlsl", ScriptingLanguage.None },
  101. { "glslinc", ScriptingLanguage.None },
  102. { "template", ScriptingLanguage.None },
  103. { "raytrace", ScriptingLanguage.None }
  104. };
  105. readonly string m_SolutionProjectEntryTemplate = string.Join("\r\n", @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""", @"EndProject").Replace(" ", "\t");
  106. readonly string m_SolutionProjectConfigurationTemplate = string.Join("\r\n", @" {{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", @" {{{0}}}.Debug|Any CPU.Build.0 = Debug|Any CPU").Replace(" ", "\t");
  107. static readonly string[] k_ReimportSyncExtensions = { ".dll", ".asmdef" };
  108. string[] m_ProjectSupportedExtensions = Array.Empty<string>();
  109. const string k_TargetLanguageVersion = "latest";
  110. public string ProjectDirectory { get; }
  111. IAssemblyNameProvider IGenerator.AssemblyNameProvider => m_AssemblyNameProvider;
  112. public void GenerateAll(bool generateAll)
  113. {
  114. m_AssemblyNameProvider.ToggleProjectGeneration(
  115. ProjectGenerationFlag.BuiltIn
  116. | ProjectGenerationFlag.Embedded
  117. | ProjectGenerationFlag.Git
  118. | ProjectGenerationFlag.Local
  119. #if UNITY_2019_3_OR_NEWER
  120. | ProjectGenerationFlag.LocalTarBall
  121. #endif
  122. | ProjectGenerationFlag.PlayerAssemblies
  123. | ProjectGenerationFlag.Registry
  124. | ProjectGenerationFlag.Unknown);
  125. }
  126. readonly string m_ProjectName;
  127. readonly IAssemblyNameProvider m_AssemblyNameProvider;
  128. readonly IFileIO m_FileIOProvider;
  129. readonly IGUIDGenerator m_GUIDProvider;
  130. const string k_ToolsVersion = "4.0";
  131. const string k_ProductVersion = "10.0.20506";
  132. const string k_BaseDirectory = ".";
  133. const string k_TargetFrameworkVersion = "v4.7.1";
  134. public ProjectGeneration(string tempDirectory)
  135. : this(tempDirectory, new AssemblyNameProvider(), new FileIOProvider(), new GUIDProvider()) { }
  136. public ProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIO, IGUIDGenerator guidGenerator)
  137. {
  138. ProjectDirectory = tempDirectory.NormalizePath();
  139. m_ProjectName = Path.GetFileName(ProjectDirectory);
  140. m_AssemblyNameProvider = assemblyNameProvider;
  141. m_FileIOProvider = fileIO;
  142. m_GUIDProvider = guidGenerator;
  143. }
  144. /// <summary>
  145. /// Syncs the scripting solution if any affected files are relevant.
  146. /// </summary>
  147. /// <returns>
  148. /// Whether the solution was synced.
  149. /// </returns>
  150. /// <param name='affectedFiles'>
  151. /// A set of files whose status has changed
  152. /// </param>
  153. /// <param name="reimportedFiles">
  154. /// A set of files that got reimported
  155. /// </param>
  156. public bool SyncIfNeeded(List<string> affectedFiles, string[] reimportedFiles)
  157. {
  158. Profiler.BeginSample("SolutionSynchronizerSync");
  159. SetupProjectSupportedExtensions();
  160. if (!HasFilesBeenModified(affectedFiles, reimportedFiles))
  161. {
  162. Profiler.EndSample();
  163. return false;
  164. }
  165. var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution);
  166. var allProjectAssemblies = RelevantAssembliesForMode(assemblies).ToList();
  167. SyncSolution(allProjectAssemblies);
  168. var allAssetProjectParts = GenerateAllAssetProjectParts();
  169. var affectedNames = affectedFiles.Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset)).Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => name.Split(new [] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]);
  170. var reimportedNames = reimportedFiles.Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset)).Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => name.Split(new [] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]);
  171. var affectedAndReimported = new HashSet<string>(affectedNames.Concat(reimportedNames));
  172. foreach (var assembly in allProjectAssemblies)
  173. {
  174. if (!affectedAndReimported.Contains(assembly.name))
  175. continue;
  176. SyncProject(assembly, allAssetProjectParts, ParseResponseFileData(assembly));
  177. }
  178. Profiler.EndSample();
  179. return true;
  180. }
  181. bool HasFilesBeenModified(List<string> affectedFiles, string[] reimportedFiles)
  182. {
  183. return affectedFiles.Any(ShouldFileBePartOfSolution) || reimportedFiles.Any(ShouldSyncOnReimportedAsset);
  184. }
  185. static bool ShouldSyncOnReimportedAsset(string asset)
  186. {
  187. return k_ReimportSyncExtensions.Contains(new FileInfo(asset).Extension);
  188. }
  189. private static IEnumerable<SR.MethodInfo> GetPostProcessorCallbacks(string name)
  190. {
  191. return TypeCache
  192. .GetTypesDerivedFrom<AssetPostprocessor>()
  193. .Select(t => t.GetMethod(name, SR.BindingFlags.Public | SR.BindingFlags.NonPublic | SR.BindingFlags.Static))
  194. .Where(m => m != null);
  195. }
  196. static void OnGeneratedCSProjectFiles()
  197. {
  198. foreach (var method in GetPostProcessorCallbacks(nameof(OnGeneratedCSProjectFiles)))
  199. {
  200. method.Invoke(null, Array.Empty<object>());
  201. }
  202. }
  203. private static string InvokeAssetPostProcessorGenerationCallbacks(string name, string path, string content)
  204. {
  205. foreach (var method in GetPostProcessorCallbacks(name))
  206. {
  207. var args = new[] { path, content };
  208. var returnValue = method.Invoke(null, args);
  209. if (method.ReturnType == typeof(string))
  210. {
  211. // We want to chain content update between invocations
  212. content = (string)returnValue;
  213. }
  214. }
  215. return content;
  216. }
  217. private static string OnGeneratedCSProject(string path, string content)
  218. {
  219. return InvokeAssetPostProcessorGenerationCallbacks(nameof(OnGeneratedCSProject), path, content);
  220. }
  221. private static string OnGeneratedSlnSolution(string path, string content)
  222. {
  223. return InvokeAssetPostProcessorGenerationCallbacks(nameof(OnGeneratedSlnSolution), path, content);
  224. }
  225. public void Sync()
  226. {
  227. SetupProjectSupportedExtensions();
  228. GenerateAndWriteSolutionAndProjects();
  229. OnGeneratedCSProjectFiles();
  230. }
  231. public bool SolutionExists()
  232. {
  233. return m_FileIOProvider.Exists(SolutionFile());
  234. }
  235. void SetupProjectSupportedExtensions()
  236. {
  237. m_ProjectSupportedExtensions = m_AssemblyNameProvider.ProjectSupportedExtensions;
  238. }
  239. bool ShouldFileBePartOfSolution(string file)
  240. {
  241. // Exclude files coming from packages except if they are internalized.
  242. if (m_AssemblyNameProvider.IsInternalizedPackagePath(file))
  243. {
  244. return false;
  245. }
  246. return HasValidExtension(file);
  247. }
  248. bool HasValidExtension(string file)
  249. {
  250. string extension = Path.GetExtension(file);
  251. // Dll's are not scripts but still need to be included..
  252. if (extension == ".dll")
  253. return true;
  254. if (file.ToLower().EndsWith(".asmdef"))
  255. return true;
  256. return IsSupportedExtension(extension);
  257. }
  258. bool IsSupportedExtension(string extension)
  259. {
  260. extension = extension.TrimStart('.');
  261. if (k_BuiltinSupportedExtensions.ContainsKey(extension))
  262. return true;
  263. if (m_ProjectSupportedExtensions.Contains(extension))
  264. return true;
  265. return false;
  266. }
  267. static ScriptingLanguage ScriptingLanguageFor(Assembly assembly)
  268. {
  269. return ScriptingLanguageFor(GetExtensionOfSourceFiles(assembly.sourceFiles));
  270. }
  271. static string GetExtensionOfSourceFiles(string[] files)
  272. {
  273. return files.Length > 0 ? GetExtensionOfSourceFile(files[0]) : "NA";
  274. }
  275. static string GetExtensionOfSourceFile(string file)
  276. {
  277. var ext = Path.GetExtension(file).ToLower();
  278. ext = ext.Substring(1); //strip dot
  279. return ext;
  280. }
  281. static ScriptingLanguage ScriptingLanguageFor(string extension)
  282. {
  283. return k_BuiltinSupportedExtensions.TryGetValue(extension.TrimStart('.'), out var result)
  284. ? result
  285. : ScriptingLanguage.None;
  286. }
  287. public void GenerateAndWriteSolutionAndProjects()
  288. {
  289. // Only synchronize assemblies that have associated source files and ones that we actually want in the project.
  290. // This also filters out DLLs coming from .asmdef files in packages.
  291. var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution).ToArray();
  292. var allAssetProjectParts = GenerateAllAssetProjectParts();
  293. SyncSolution(assemblies);
  294. var allProjectAssemblies = RelevantAssembliesForMode(assemblies).ToList();
  295. foreach (Assembly assembly in allProjectAssemblies)
  296. {
  297. var responseFileData = ParseResponseFileData(assembly);
  298. SyncProject(assembly, allAssetProjectParts, responseFileData);
  299. }
  300. WriteVSCodeSettingsFiles();
  301. }
  302. List<ResponseFileData> ParseResponseFileData(Assembly assembly)
  303. {
  304. var systemReferenceDirectories = CompilationPipeline.GetSystemAssemblyDirectories(assembly.compilerOptions.ApiCompatibilityLevel);
  305. Dictionary<string, ResponseFileData> responseFilesData = assembly.compilerOptions.ResponseFiles.ToDictionary(x => x, x => m_AssemblyNameProvider.ParseResponseFile(
  306. x,
  307. ProjectDirectory,
  308. systemReferenceDirectories
  309. ));
  310. Dictionary<string, ResponseFileData> responseFilesWithErrors = responseFilesData.Where(x => x.Value.Errors.Any())
  311. .ToDictionary(x => x.Key, x => x.Value);
  312. if (responseFilesWithErrors.Any())
  313. {
  314. foreach (var error in responseFilesWithErrors)
  315. foreach (var valueError in error.Value.Errors)
  316. {
  317. Debug.LogError($"{error.Key} Parse Error : {valueError}");
  318. }
  319. }
  320. return responseFilesData.Select(x => x.Value).ToList();
  321. }
  322. Dictionary<string, string> GenerateAllAssetProjectParts()
  323. {
  324. Dictionary<string, StringBuilder> stringBuilders = new Dictionary<string, StringBuilder>();
  325. foreach (string asset in m_AssemblyNameProvider.GetAllAssetPaths())
  326. {
  327. // Exclude files coming from packages except if they are internalized.
  328. // TODO: We need assets from the assembly API
  329. if (m_AssemblyNameProvider.IsInternalizedPackagePath(asset))
  330. {
  331. continue;
  332. }
  333. string extension = Path.GetExtension(asset);
  334. if (IsSupportedExtension(extension) && ScriptingLanguage.None == ScriptingLanguageFor(extension))
  335. {
  336. // Find assembly the asset belongs to by adding script extension and using compilation pipeline.
  337. var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset);
  338. if (string.IsNullOrEmpty(assemblyName))
  339. {
  340. continue;
  341. }
  342. assemblyName = Path.GetFileNameWithoutExtension(assemblyName);
  343. if (!stringBuilders.TryGetValue(assemblyName, out var projectBuilder))
  344. {
  345. projectBuilder = new StringBuilder();
  346. stringBuilders[assemblyName] = projectBuilder;
  347. }
  348. projectBuilder.Append(" <None Include=\"").Append(m_FileIOProvider.EscapedRelativePathFor(asset, ProjectDirectory)).Append("\" />").Append(k_WindowsNewline);
  349. }
  350. }
  351. var result = new Dictionary<string, string>();
  352. foreach (var entry in stringBuilders)
  353. result[entry.Key] = entry.Value.ToString();
  354. return result;
  355. }
  356. void SyncProject(
  357. Assembly assembly,
  358. Dictionary<string, string> allAssetsProjectParts,
  359. List<ResponseFileData> responseFilesData)
  360. {
  361. SyncProjectFileIfNotChanged(ProjectFile(assembly), ProjectText(assembly, allAssetsProjectParts, responseFilesData));
  362. }
  363. void SyncProjectFileIfNotChanged(string path, string newContents)
  364. {
  365. if (Path.GetExtension(path) == ".csproj")
  366. {
  367. newContents = OnGeneratedCSProject(path, newContents);
  368. }
  369. SyncFileIfNotChanged(path, newContents);
  370. }
  371. void SyncSolutionFileIfNotChanged(string path, string newContents)
  372. {
  373. newContents = OnGeneratedSlnSolution(path, newContents);
  374. SyncFileIfNotChanged(path, newContents);
  375. }
  376. void SyncFileIfNotChanged(string filename, string newContents)
  377. {
  378. try
  379. {
  380. if (m_FileIOProvider.Exists(filename) && newContents == m_FileIOProvider.ReadAllText(filename))
  381. {
  382. return;
  383. }
  384. }
  385. catch (Exception exception)
  386. {
  387. Debug.LogException(exception);
  388. }
  389. m_FileIOProvider.WriteAllText(filename, newContents);
  390. }
  391. string ProjectText(
  392. Assembly assembly,
  393. Dictionary<string, string> allAssetsProjectParts,
  394. List<ResponseFileData> responseFilesData)
  395. {
  396. var projectBuilder = new StringBuilder();
  397. ProjectHeader(assembly, responseFilesData, projectBuilder);
  398. var references = new List<string>();
  399. foreach (string file in assembly.sourceFiles)
  400. {
  401. var fullFile = m_FileIOProvider.EscapedRelativePathFor(file, ProjectDirectory);
  402. projectBuilder.Append(" <Compile Include=\"").Append(fullFile).Append("\" />").Append(k_WindowsNewline);
  403. }
  404. // Append additional non-script files that should be included in project generation.
  405. if (allAssetsProjectParts.TryGetValue(assembly.name, out var additionalAssetsForProject))
  406. projectBuilder.Append(additionalAssetsForProject);
  407. var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r));
  408. var internalAssemblyReferences = assembly.assemblyReferences
  409. .Where(i => !i.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath);
  410. var allReferences =
  411. assembly.compiledAssemblyReferences
  412. .Union(responseRefs)
  413. .Union(references)
  414. .Union(internalAssemblyReferences);
  415. foreach (var reference in allReferences)
  416. {
  417. string fullReference = Path.IsPathRooted(reference) ? reference : Path.Combine(ProjectDirectory, reference);
  418. AppendReference(fullReference, projectBuilder);
  419. }
  420. if (0 < assembly.assemblyReferences.Length)
  421. {
  422. projectBuilder.Append(" </ItemGroup>").Append(k_WindowsNewline);
  423. projectBuilder.Append(" <ItemGroup>").Append(k_WindowsNewline);
  424. foreach (Assembly reference in assembly.assemblyReferences.Where(i => i.sourceFiles.Any(ShouldFileBePartOfSolution)))
  425. {
  426. projectBuilder.Append(" <ProjectReference Include=\"").Append(reference.name).Append(GetProjectExtension()).Append("\">").Append(k_WindowsNewline);
  427. projectBuilder.Append(" <Project>{").Append(ProjectGuid(reference.name)).Append("}</Project>").Append(k_WindowsNewline);
  428. projectBuilder.Append(" <Name>").Append(reference.name).Append("</Name>").Append(k_WindowsNewline);
  429. projectBuilder.Append(" </ProjectReference>").Append(k_WindowsNewline);
  430. }
  431. }
  432. projectBuilder.Append(ProjectFooter());
  433. return projectBuilder.ToString();
  434. }
  435. static void AppendReference(string fullReference, StringBuilder projectBuilder)
  436. {
  437. var escapedFullPath = SecurityElement.Escape(fullReference);
  438. escapedFullPath = escapedFullPath.NormalizePath();
  439. projectBuilder.Append(" <Reference Include=\"").Append(Path.GetFileNameWithoutExtension(escapedFullPath)).Append("\">").Append(k_WindowsNewline);
  440. projectBuilder.Append(" <HintPath>").Append(escapedFullPath).Append("</HintPath>").Append(k_WindowsNewline);
  441. projectBuilder.Append(" </Reference>").Append(k_WindowsNewline);
  442. }
  443. public string ProjectFile(Assembly assembly)
  444. {
  445. var fileBuilder = new StringBuilder(assembly.name);
  446. fileBuilder.Append(".csproj");
  447. return Path.Combine(ProjectDirectory, fileBuilder.ToString());
  448. }
  449. public string SolutionFile()
  450. {
  451. return Path.Combine(ProjectDirectory, $"{m_ProjectName}.sln");
  452. }
  453. private void ProjectHeader(
  454. Assembly assembly,
  455. List<ResponseFileData> responseFilesData,
  456. StringBuilder builder
  457. )
  458. {
  459. var otherArguments = GetOtherArgumentsFromResponseFilesData(responseFilesData);
  460. GetProjectHeaderTemplate(
  461. builder,
  462. ProjectGuid(assembly.name),
  463. assembly.name,
  464. string.Join(";", new[] { "DEBUG", "TRACE" }.Concat(assembly.defines).Concat(responseFilesData.SelectMany(x => x.Defines)).Concat(EditorUserBuildSettings.activeScriptCompilationDefines).Distinct().ToArray()),
  465. GenerateLangVersion(otherArguments["langversion"], assembly),
  466. assembly.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe),
  467. GenerateAnalyserItemGroup(RetrieveRoslynAnalyzers(assembly, otherArguments)),
  468. GenerateRoslynAnalyzerRulesetPath(assembly, otherArguments)
  469. );
  470. }
  471. private static string GenerateLangVersion(IEnumerable<string> langVersionList, Assembly assembly)
  472. {
  473. var langVersion = langVersionList.FirstOrDefault();
  474. if (!string.IsNullOrWhiteSpace(langVersion))
  475. return langVersion;
  476. #if UNITY_2020_2_OR_NEWER
  477. return assembly.compilerOptions.LanguageVersion;
  478. #else
  479. return k_TargetLanguageVersion;
  480. #endif
  481. }
  482. private static string GenerateRoslynAnalyzerRulesetPath(Assembly assembly, ILookup<string, string> otherResponseFilesData)
  483. {
  484. #if UNITY_2020_2_OR_NEWER
  485. return GenerateAnalyserRuleSet(otherResponseFilesData["ruleset"].Append(assembly.compilerOptions.RoslynAnalyzerRulesetPath).Where(a => !string.IsNullOrEmpty(a)).Distinct().Select(x => MakeAbsolutePath(x).NormalizePath()).ToArray());
  486. #else
  487. return GenerateAnalyserRuleSet(otherResponseFilesData["ruleset"].Distinct().Select(x => MakeAbsolutePath(x).NormalizePath()).ToArray());
  488. #endif
  489. }
  490. private static string GenerateAnalyserRuleSet(string[] paths)
  491. {
  492. return paths.Length == 0
  493. ? string.Empty
  494. : $"{Environment.NewLine}{string.Join(Environment.NewLine, paths.Select(a => $" <CodeAnalysisRuleSet>{a}</CodeAnalysisRuleSet>"))}";
  495. }
  496. private static string MakeAbsolutePath(string path)
  497. {
  498. return Path.IsPathRooted(path) ? path : Path.GetFullPath(path);
  499. }
  500. private static ILookup<string, string> GetOtherArgumentsFromResponseFilesData(List<ResponseFileData> responseFilesData)
  501. {
  502. var paths = responseFilesData.SelectMany(x =>
  503. {
  504. return x.OtherArguments.Where(a => a.StartsWith("/") || a.StartsWith("-"))
  505. .Select(b =>
  506. {
  507. var index = b.IndexOf(":", StringComparison.Ordinal);
  508. if (index > 0 && b.Length > index)
  509. {
  510. var key = b.Substring(1, index - 1);
  511. return new KeyValuePair<string, string>(key, b.Substring(index + 1));
  512. }
  513. const string warnaserror = "warnaserror";
  514. return b.Substring(1).StartsWith(warnaserror)
  515. ? new KeyValuePair<string, string>(warnaserror, b.Substring(warnaserror.Length + 1))
  516. : default;
  517. });
  518. })
  519. .Distinct()
  520. .ToLookup(o => o.Key, pair => pair.Value);
  521. return paths;
  522. }
  523. string[] RetrieveRoslynAnalyzers(Assembly assembly, ILookup<string, string> otherArguments)
  524. {
  525. #if UNITY_2020_2_OR_NEWER
  526. return otherArguments["analyzer"].Concat(otherArguments["a"])
  527. .SelectMany(x=>x.Split(';'))
  528. #if !ROSLYN_ANALYZER_FIX
  529. .Concat(m_AssemblyNameProvider.GetRoslynAnalyzerPaths())
  530. #else
  531. .Concat(assembly.compilerOptions.RoslynAnalyzerDllPaths)
  532. #endif
  533. .Select(MakeAbsolutePath)
  534. .Distinct()
  535. .ToArray();
  536. #else
  537. return otherArguments["analyzer"].Concat(otherArguments["a"])
  538. .SelectMany(x=>x.Split(';'))
  539. .Distinct()
  540. .Select(MakeAbsolutePath)
  541. .ToArray();
  542. #endif
  543. }
  544. static string GenerateAnalyserItemGroup(string[] paths)
  545. {
  546. // <ItemGroup>
  547. // <Analyzer Include="..\packages\Comments_analyser.1.0.6626.21356\analyzers\dotnet\cs\Comments_analyser.dll" />
  548. // <Analyzer Include="..\packages\UnityEngineAnalyzer.1.0.0.0\analyzers\dotnet\cs\UnityEngineAnalyzer.dll" />
  549. // </ItemGroup>
  550. if (paths.Length == 0)
  551. {
  552. return string.Empty;
  553. }
  554. var analyserBuilder = new StringBuilder();
  555. analyserBuilder.Append(" <ItemGroup>").Append(k_WindowsNewline);
  556. foreach (var path in paths)
  557. {
  558. analyserBuilder.Append($" <Analyzer Include=\"{path.NormalizePath()}\" />").Append(k_WindowsNewline);
  559. }
  560. analyserBuilder.Append(" </ItemGroup>").Append(k_WindowsNewline);
  561. return analyserBuilder.ToString();
  562. }
  563. static string GetSolutionText()
  564. {
  565. return string.Join("\r\n", @"", @"Microsoft Visual Studio Solution File, Format Version {0}", @"# Visual Studio {1}", @"{2}", @"Global", @" GlobalSection(SolutionConfigurationPlatforms) = preSolution", @" Debug|Any CPU = Debug|Any CPU", @" EndGlobalSection", @" GlobalSection(ProjectConfigurationPlatforms) = postSolution", @"{3}", @" EndGlobalSection", @" GlobalSection(SolutionProperties) = preSolution", @" HideSolutionNode = FALSE", @" EndGlobalSection", @"EndGlobal", @"").Replace(" ", "\t");
  566. }
  567. static string GetProjectFooterTemplate()
  568. {
  569. return string.Join("\r\n", @" </ItemGroup>", @" <Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />", @" <!-- To modify your build process, add your task inside one of the targets below and uncomment it.", @" Other similar extension points exist, see Microsoft.Common.targets.", @" <Target Name=""BeforeBuild"">", @" </Target>", @" <Target Name=""AfterBuild"">", @" </Target>", @" -->", @"</Project>", @"");
  570. }
  571. static void GetProjectHeaderTemplate(
  572. StringBuilder builder,
  573. string assemblyGUID,
  574. string assemblyName,
  575. string defines,
  576. string langVersion,
  577. bool allowUnsafe,
  578. string analyzerBlock,
  579. string rulesetBlock
  580. )
  581. {
  582. builder.Append(@"<?xml version=""1.0"" encoding=""utf-8""?>").Append(k_WindowsNewline);
  583. builder.Append(@"<Project ToolsVersion=""").Append(k_ToolsVersion).Append(@""" DefaultTargets=""Build"" xmlns=""").Append(MSBuildNamespaceUri).Append(@""">").Append(k_WindowsNewline);
  584. builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
  585. builder.Append(@" <LangVersion>").Append(langVersion).Append("</LangVersion>").Append(k_WindowsNewline);
  586. builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
  587. builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
  588. builder.Append(@" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>").Append(k_WindowsNewline);
  589. builder.Append(@" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>").Append(k_WindowsNewline);
  590. builder.Append(@" <ProductVersion>").Append(k_ProductVersion).Append("</ProductVersion>").Append(k_WindowsNewline);
  591. builder.Append(@" <SchemaVersion>2.0</SchemaVersion>").Append(k_WindowsNewline);
  592. builder.Append(@" <RootNamespace>").Append(EditorSettings.projectGenerationRootNamespace).Append("</RootNamespace>").Append(k_WindowsNewline);
  593. builder.Append(@" <ProjectGuid>{").Append(assemblyGUID).Append("}</ProjectGuid>").Append(k_WindowsNewline);
  594. builder.Append(@" <OutputType>Library</OutputType>").Append(k_WindowsNewline);
  595. builder.Append(@" <AppDesignerFolder>Properties</AppDesignerFolder>").Append(k_WindowsNewline);
  596. builder.Append(@" <AssemblyName>").Append(assemblyName).Append("</AssemblyName>").Append(k_WindowsNewline);
  597. builder.Append(@" <TargetFrameworkVersion>").Append(k_TargetFrameworkVersion).Append("</TargetFrameworkVersion>").Append(k_WindowsNewline);
  598. builder.Append(@" <FileAlignment>512</FileAlignment>").Append(k_WindowsNewline);
  599. builder.Append(@" <BaseDirectory>").Append(k_BaseDirectory).Append("</BaseDirectory>").Append(k_WindowsNewline);
  600. builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
  601. builder.Append(@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">").Append(k_WindowsNewline);
  602. builder.Append(@" <DebugSymbols>true</DebugSymbols>").Append(k_WindowsNewline);
  603. builder.Append(@" <DebugType>full</DebugType>").Append(k_WindowsNewline);
  604. builder.Append(@" <Optimize>false</Optimize>").Append(k_WindowsNewline);
  605. builder.Append(@" <OutputPath>Temp\bin\Debug\</OutputPath>").Append(k_WindowsNewline);
  606. builder.Append(@" <DefineConstants>").Append(defines).Append("</DefineConstants>").Append(k_WindowsNewline);
  607. builder.Append(@" <ErrorReport>prompt</ErrorReport>").Append(k_WindowsNewline);
  608. builder.Append(@" <WarningLevel>4</WarningLevel>").Append(k_WindowsNewline);
  609. builder.Append(@" <NoWarn>0169</NoWarn>").Append(k_WindowsNewline);
  610. builder.Append(@" <AllowUnsafeBlocks>").Append(allowUnsafe).Append("</AllowUnsafeBlocks>").Append(k_WindowsNewline);
  611. builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
  612. builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
  613. builder.Append(@" <NoConfig>true</NoConfig>").Append(k_WindowsNewline);
  614. builder.Append(@" <NoStdLib>true</NoStdLib>").Append(k_WindowsNewline);
  615. builder.Append(@" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>").Append(k_WindowsNewline);
  616. builder.Append(@" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>").Append(k_WindowsNewline);
  617. builder.Append(@" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>").Append(k_WindowsNewline);
  618. builder.Append(rulesetBlock);
  619. builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
  620. builder.Append(analyzerBlock);
  621. builder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
  622. }
  623. void SyncSolution(IEnumerable<Assembly> assemblies)
  624. {
  625. SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(assemblies));
  626. }
  627. string SolutionText(IEnumerable<Assembly> assemblies)
  628. {
  629. var fileversion = "11.00";
  630. var vsversion = "2010";
  631. var relevantAssemblies = RelevantAssembliesForMode(assemblies);
  632. string projectEntries = GetProjectEntries(relevantAssemblies);
  633. string projectConfigurations = string.Join(k_WindowsNewline, relevantAssemblies.Select(i => GetProjectActiveConfigurations(ProjectGuid(i.name))).ToArray());
  634. return string.Format(GetSolutionText(), fileversion, vsversion, projectEntries, projectConfigurations);
  635. }
  636. static IEnumerable<Assembly> RelevantAssembliesForMode(IEnumerable<Assembly> assemblies)
  637. {
  638. return assemblies.Where(i => ScriptingLanguage.CSharp == ScriptingLanguageFor(i));
  639. }
  640. /// <summary>
  641. /// Get a Project("{guid}") = "MyProject", "MyProject.csproj", "{projectguid}"
  642. /// entry for each relevant language
  643. /// </summary>
  644. string GetProjectEntries(IEnumerable<Assembly> assemblies)
  645. {
  646. var projectEntries = assemblies.Select(i => string.Format(
  647. m_SolutionProjectEntryTemplate,
  648. SolutionGuid(i),
  649. i.name,
  650. Path.GetFileName(ProjectFile(i)),
  651. ProjectGuid(i.name)
  652. ));
  653. return string.Join(k_WindowsNewline, projectEntries.ToArray());
  654. }
  655. /// <summary>
  656. /// Generate the active configuration string for a given project guid
  657. /// </summary>
  658. string GetProjectActiveConfigurations(string projectGuid)
  659. {
  660. return string.Format(
  661. m_SolutionProjectConfigurationTemplate,
  662. projectGuid);
  663. }
  664. static string SkipPathPrefix(string path, string prefix)
  665. {
  666. if (path.StartsWith($@"{prefix}{Path.DirectorySeparatorChar}"))
  667. return path.Substring(prefix.Length + 1);
  668. return path;
  669. }
  670. string ProjectGuid(string assembly)
  671. {
  672. return m_GUIDProvider.ProjectGuid(m_ProjectName, assembly);
  673. }
  674. string SolutionGuid(Assembly assembly)
  675. {
  676. return m_GUIDProvider.SolutionGuid(m_ProjectName, GetExtensionOfSourceFiles(assembly.sourceFiles));
  677. }
  678. static string ProjectFooter()
  679. {
  680. return GetProjectFooterTemplate();
  681. }
  682. static string GetProjectExtension()
  683. {
  684. return ".csproj";
  685. }
  686. void WriteVSCodeSettingsFiles()
  687. {
  688. var vsCodeDirectory = Path.Combine(ProjectDirectory, ".vscode");
  689. if (!m_FileIOProvider.Exists(vsCodeDirectory))
  690. m_FileIOProvider.CreateDirectory(vsCodeDirectory);
  691. var vsCodeSettingsJson = Path.Combine(vsCodeDirectory, "settings.json");
  692. if (!m_FileIOProvider.Exists(vsCodeSettingsJson))
  693. m_FileIOProvider.WriteAllText(vsCodeSettingsJson, k_SettingsJson);
  694. }
  695. }
  696. public static class SolutionGuidGenerator
  697. {
  698. static MD5 mD5 = MD5CryptoServiceProvider.Create();
  699. public static string GuidForProject(string projectName)
  700. {
  701. return ComputeGuidHashFor(projectName + "salt");
  702. }
  703. public static string GuidForSolution(string projectName, string sourceFileExtension)
  704. {
  705. return "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC";
  706. }
  707. static string ComputeGuidHashFor(string input)
  708. {
  709. var hash = mD5.ComputeHash(Encoding.Default.GetBytes(input));
  710. return new Guid(hash).ToString();
  711. }
  712. }
  713. }