ProjectGeneration.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  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 System.Text;
  8. using UnityEditor;
  9. using UnityEditor.Compilation;
  10. using UnityEngine;
  11. using UnityEngine.Profiling;
  12. namespace VSCodeEditor
  13. {
  14. public interface IGenerator
  15. {
  16. bool SyncIfNeeded(List<string> affectedFiles, string[] reimportedFiles);
  17. void Sync();
  18. string SolutionFile();
  19. string ProjectDirectory { get; }
  20. IAssemblyNameProvider AssemblyNameProvider { get; }
  21. void GenerateAll(bool generateAll);
  22. bool SolutionExists();
  23. }
  24. public class ProjectGeneration : IGenerator
  25. {
  26. enum ScriptingLanguage
  27. {
  28. None,
  29. CSharp
  30. }
  31. public static readonly string MSBuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003";
  32. const string k_WindowsNewline = "\r\n";
  33. const string k_SettingsJson = @"{
  34. ""files.exclude"":
  35. {
  36. ""**/.DS_Store"":true,
  37. ""**/.git"":true,
  38. ""**/.gitignore"":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. string m_SolutionProjectEntryTemplate = string.Join("\r\n", @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""", @"EndProject").Replace(" ", "\t");
  106. 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 = new string[0];
  109. public string ProjectDirectory { get; }
  110. IAssemblyNameProvider IGenerator.AssemblyNameProvider => m_AssemblyNameProvider;
  111. public void GenerateAll(bool generateAll)
  112. {
  113. m_AssemblyNameProvider.ToggleProjectGeneration(
  114. ProjectGenerationFlag.BuiltIn
  115. | ProjectGenerationFlag.Embedded
  116. | ProjectGenerationFlag.Git
  117. | ProjectGenerationFlag.Local
  118. #if UNITY_2019_3_OR_NEWER
  119. | ProjectGenerationFlag.LocalTarBall
  120. #endif
  121. | ProjectGenerationFlag.PlayerAssemblies
  122. | ProjectGenerationFlag.Registry
  123. | ProjectGenerationFlag.Unknown);
  124. }
  125. readonly string m_ProjectName;
  126. readonly IAssemblyNameProvider m_AssemblyNameProvider;
  127. readonly IFileIO m_FileIOProvider;
  128. readonly IGUIDGenerator m_GUIDProvider;
  129. const string k_ToolsVersion = "4.0";
  130. const string k_ProductVersion = "10.0.20506";
  131. const string k_BaseDirectory = ".";
  132. const string k_TargetFrameworkVersion = "v4.7.1";
  133. const string k_TargetLanguageVersion = "latest";
  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.Replace('\\', '/');
  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. // Don't sync if we haven't synced before
  161. if (SolutionExists() && HasFilesBeenModified(affectedFiles, reimportedFiles))
  162. {
  163. var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution);
  164. var allProjectAssemblies = RelevantAssembliesForMode(assemblies).ToList();
  165. SyncSolution(allProjectAssemblies);
  166. var allAssetProjectParts = GenerateAllAssetProjectParts();
  167. var affectedNames = affectedFiles.Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset)).Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => name.Split(new [] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]);
  168. var reimportedNames = reimportedFiles.Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset)).Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => name.Split(new [] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]);
  169. var affectedAndReimported = new HashSet<string>(affectedNames.Concat(reimportedNames));
  170. var assemblyNames = new HashSet<string>(allProjectAssemblies.Select(assembly => Path.GetFileName(assembly.outputPath)));
  171. foreach (var assembly in allProjectAssemblies)
  172. {
  173. if (!affectedAndReimported.Contains(assembly.name))
  174. continue;
  175. SyncProject(assembly, allAssetProjectParts, ParseResponseFileData(assembly), assemblyNames);
  176. }
  177. Profiler.EndSample();
  178. return true;
  179. }
  180. Profiler.EndSample();
  181. return false;
  182. }
  183. bool HasFilesBeenModified(List<string> affectedFiles, string[] reimportedFiles)
  184. {
  185. return affectedFiles.Any(ShouldFileBePartOfSolution) || reimportedFiles.Any(ShouldSyncOnReimportedAsset);
  186. }
  187. static bool ShouldSyncOnReimportedAsset(string asset)
  188. {
  189. return k_ReimportSyncExtensions.Contains(new FileInfo(asset).Extension);
  190. }
  191. public void Sync()
  192. {
  193. SetupProjectSupportedExtensions();
  194. GenerateAndWriteSolutionAndProjects();
  195. }
  196. public bool SolutionExists()
  197. {
  198. return m_FileIOProvider.Exists(SolutionFile());
  199. }
  200. void SetupProjectSupportedExtensions()
  201. {
  202. m_ProjectSupportedExtensions = m_AssemblyNameProvider.ProjectSupportedExtensions;
  203. }
  204. bool ShouldFileBePartOfSolution(string file)
  205. {
  206. // Exclude files coming from packages except if they are internalized.
  207. if (m_AssemblyNameProvider.IsInternalizedPackagePath(file))
  208. {
  209. return false;
  210. }
  211. return HasValidExtension(file);
  212. }
  213. bool HasValidExtension(string file)
  214. {
  215. string extension = Path.GetExtension(file);
  216. // Dll's are not scripts but still need to be included..
  217. if (extension == ".dll")
  218. return true;
  219. if (file.ToLower().EndsWith(".asmdef"))
  220. return true;
  221. return IsSupportedExtension(extension);
  222. }
  223. bool IsSupportedExtension(string extension)
  224. {
  225. extension = extension.TrimStart('.');
  226. if (k_BuiltinSupportedExtensions.ContainsKey(extension))
  227. return true;
  228. if (m_ProjectSupportedExtensions.Contains(extension))
  229. return true;
  230. return false;
  231. }
  232. static ScriptingLanguage ScriptingLanguageFor(Assembly assembly)
  233. {
  234. return ScriptingLanguageFor(GetExtensionOfSourceFiles(assembly.sourceFiles));
  235. }
  236. static string GetExtensionOfSourceFiles(string[] files)
  237. {
  238. return files.Length > 0 ? GetExtensionOfSourceFile(files[0]) : "NA";
  239. }
  240. static string GetExtensionOfSourceFile(string file)
  241. {
  242. var ext = Path.GetExtension(file).ToLower();
  243. ext = ext.Substring(1); //strip dot
  244. return ext;
  245. }
  246. static ScriptingLanguage ScriptingLanguageFor(string extension)
  247. {
  248. return k_BuiltinSupportedExtensions.TryGetValue(extension.TrimStart('.'), out var result)
  249. ? result
  250. : ScriptingLanguage.None;
  251. }
  252. public void GenerateAndWriteSolutionAndProjects()
  253. {
  254. // Only synchronize assemblies that have associated source files and ones that we actually want in the project.
  255. // This also filters out DLLs coming from .asmdef files in packages.
  256. var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution);
  257. var allAssetProjectParts = GenerateAllAssetProjectParts();
  258. SyncSolution(assemblies);
  259. var allProjectAssemblies = RelevantAssembliesForMode(assemblies).ToList();
  260. var assemblyNames = new HashSet<string>(allProjectAssemblies.Select(assembly => Path.GetFileName(assembly.outputPath)));
  261. foreach (Assembly assembly in allProjectAssemblies)
  262. {
  263. var responseFileData = ParseResponseFileData(assembly);
  264. SyncProject(assembly, allAssetProjectParts, responseFileData, assemblyNames);
  265. }
  266. WriteVSCodeSettingsFiles();
  267. }
  268. List<ResponseFileData> ParseResponseFileData(Assembly assembly)
  269. {
  270. var systemReferenceDirectories = CompilationPipeline.GetSystemAssemblyDirectories(assembly.compilerOptions.ApiCompatibilityLevel);
  271. Dictionary<string, ResponseFileData> responseFilesData = assembly.compilerOptions.ResponseFiles.ToDictionary(x => x, x => m_AssemblyNameProvider.ParseResponseFile(
  272. x,
  273. ProjectDirectory,
  274. systemReferenceDirectories
  275. ));
  276. Dictionary<string, ResponseFileData> responseFilesWithErrors = responseFilesData.Where(x => x.Value.Errors.Any())
  277. .ToDictionary(x => x.Key, x => x.Value);
  278. if (responseFilesWithErrors.Any())
  279. {
  280. foreach (var error in responseFilesWithErrors)
  281. foreach (var valueError in error.Value.Errors)
  282. {
  283. Debug.LogError($"{error.Key} Parse Error : {valueError}");
  284. }
  285. }
  286. return responseFilesData.Select(x => x.Value).ToList();
  287. }
  288. Dictionary<string, string> GenerateAllAssetProjectParts()
  289. {
  290. Dictionary<string, StringBuilder> stringBuilders = new Dictionary<string, StringBuilder>();
  291. foreach (string asset in m_AssemblyNameProvider.GetAllAssetPaths())
  292. {
  293. // Exclude files coming from packages except if they are internalized.
  294. // TODO: We need assets from the assembly API
  295. if (m_AssemblyNameProvider.IsInternalizedPackagePath(asset))
  296. {
  297. continue;
  298. }
  299. string extension = Path.GetExtension(asset);
  300. if (IsSupportedExtension(extension) && ScriptingLanguage.None == ScriptingLanguageFor(extension))
  301. {
  302. // Find assembly the asset belongs to by adding script extension and using compilation pipeline.
  303. var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset);
  304. if (string.IsNullOrEmpty(assemblyName))
  305. {
  306. continue;
  307. }
  308. assemblyName = Path.GetFileNameWithoutExtension(assemblyName);
  309. if (!stringBuilders.TryGetValue(assemblyName, out var projectBuilder))
  310. {
  311. projectBuilder = new StringBuilder();
  312. stringBuilders[assemblyName] = projectBuilder;
  313. }
  314. projectBuilder.Append(" <None Include=\"").Append(EscapedRelativePathFor(asset)).Append("\" />").Append(k_WindowsNewline);
  315. }
  316. }
  317. var result = new Dictionary<string, string>();
  318. foreach (var entry in stringBuilders)
  319. result[entry.Key] = entry.Value.ToString();
  320. return result;
  321. }
  322. void SyncProject(
  323. Assembly assembly,
  324. Dictionary<string, string> allAssetsProjectParts,
  325. List<ResponseFileData> responseFilesData,
  326. HashSet<string> assemblyNames)
  327. {
  328. SyncProjectFileIfNotChanged(ProjectFile(assembly), ProjectText(assembly, allAssetsProjectParts, responseFilesData, assemblyNames, GetAllRoslynAnalyzerPaths().ToArray()));
  329. }
  330. private IEnumerable<string> GetAllRoslynAnalyzerPaths()
  331. {
  332. return m_AssemblyNameProvider.GetRoslynAnalyzerPaths();
  333. }
  334. void SyncProjectFileIfNotChanged(string path, string newContents)
  335. {
  336. SyncFileIfNotChanged(path, newContents);
  337. }
  338. void SyncSolutionFileIfNotChanged(string path, string newContents)
  339. {
  340. SyncFileIfNotChanged(path, newContents);
  341. }
  342. void SyncFileIfNotChanged(string filename, string newContents)
  343. {
  344. if (m_FileIOProvider.Exists(filename))
  345. {
  346. var currentContents = m_FileIOProvider.ReadAllText(filename);
  347. if (currentContents == newContents)
  348. {
  349. return;
  350. }
  351. }
  352. m_FileIOProvider.WriteAllText(filename, newContents);
  353. }
  354. string ProjectText(
  355. Assembly assembly,
  356. Dictionary<string, string> allAssetsProjectParts,
  357. List<ResponseFileData> responseFilesData,
  358. HashSet<string> assemblyNames,
  359. string[] roslynAnalyzerDllPaths)
  360. {
  361. var projectBuilder = new StringBuilder();
  362. ProjectHeader(assembly, responseFilesData, roslynAnalyzerDllPaths, projectBuilder);
  363. var references = new List<string>();
  364. foreach (string file in assembly.sourceFiles)
  365. {
  366. if (!HasValidExtension(file))
  367. continue;
  368. var extension = Path.GetExtension(file).ToLower();
  369. var fullFile = EscapedRelativePathFor(file);
  370. if (".dll" != extension)
  371. {
  372. projectBuilder.Append(" <Compile Include=\"").Append(fullFile).Append("\" />").Append(k_WindowsNewline);
  373. }
  374. else
  375. {
  376. references.Add(fullFile);
  377. }
  378. }
  379. // Append additional non-script files that should be included in project generation.
  380. if (allAssetsProjectParts.TryGetValue(assembly.name, out var additionalAssetsForProject))
  381. projectBuilder.Append(additionalAssetsForProject);
  382. var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r));
  383. var internalAssemblyReferences = assembly.assemblyReferences
  384. .Where(i => !i.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath);
  385. var allReferences =
  386. assembly.compiledAssemblyReferences
  387. .Union(responseRefs)
  388. .Union(references)
  389. .Union(internalAssemblyReferences)
  390. .Except(roslynAnalyzerDllPaths);
  391. foreach (var reference in allReferences)
  392. {
  393. string fullReference = Path.IsPathRooted(reference) ? reference : Path.Combine(ProjectDirectory, reference);
  394. AppendReference(fullReference, projectBuilder);
  395. }
  396. if (0 < assembly.assemblyReferences.Length)
  397. {
  398. projectBuilder.Append(" </ItemGroup>").Append(k_WindowsNewline);
  399. projectBuilder.Append(" <ItemGroup>").Append(k_WindowsNewline);
  400. foreach (Assembly reference in assembly.assemblyReferences.Where(i => i.sourceFiles.Any(ShouldFileBePartOfSolution)))
  401. {
  402. var referencedProject = reference.outputPath;
  403. projectBuilder.Append(" <ProjectReference Include=\"").Append(reference.name).Append(GetProjectExtension()).Append("\">").Append(k_WindowsNewline);
  404. projectBuilder.Append(" <Project>{").Append(ProjectGuid(reference.name)).Append("}</Project>").Append(k_WindowsNewline);
  405. projectBuilder.Append(" <Name>").Append(reference.name).Append("</Name>").Append(k_WindowsNewline);
  406. projectBuilder.Append(" </ProjectReference>").Append(k_WindowsNewline);
  407. }
  408. }
  409. projectBuilder.Append(ProjectFooter());
  410. return projectBuilder.ToString();
  411. }
  412. static void AppendReference(string fullReference, StringBuilder projectBuilder)
  413. {
  414. //replace \ with / and \\ with /
  415. var escapedFullPath = SecurityElement.Escape(fullReference);
  416. escapedFullPath = escapedFullPath.Replace("\\\\", "/");
  417. escapedFullPath = escapedFullPath.Replace("\\", "/");
  418. projectBuilder.Append(" <Reference Include=\"").Append(Path.GetFileNameWithoutExtension(escapedFullPath)).Append("\">").Append(k_WindowsNewline);
  419. projectBuilder.Append(" <HintPath>").Append(escapedFullPath).Append("</HintPath>").Append(k_WindowsNewline);
  420. projectBuilder.Append(" </Reference>").Append(k_WindowsNewline);
  421. }
  422. public string ProjectFile(Assembly assembly)
  423. {
  424. var fileBuilder = new StringBuilder(assembly.name);
  425. fileBuilder.Append(".csproj");
  426. return Path.Combine(ProjectDirectory, fileBuilder.ToString());
  427. }
  428. public string SolutionFile()
  429. {
  430. return Path.Combine(ProjectDirectory, $"{m_ProjectName}.sln");
  431. }
  432. void ProjectHeader(
  433. Assembly assembly,
  434. List<ResponseFileData> responseFilesData,
  435. string[] roslynAnalyzerDllPaths,
  436. StringBuilder builder
  437. )
  438. {
  439. var otherArguments = GetOtherArgumentsFromResponseFilesData(responseFilesData);
  440. GetProjectHeaderTemplate(
  441. builder,
  442. ProjectGuid(assembly.name),
  443. assembly.name,
  444. string.Join(";", new[] { "DEBUG", "TRACE" }.Concat(assembly.defines).Concat(responseFilesData.SelectMany(x => x.Defines)).Concat(EditorUserBuildSettings.activeScriptCompilationDefines).Distinct().ToArray()),
  445. assembly.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe),
  446. GenerateAnalyserItemGroup(otherArguments["analyzer"].Concat(otherArguments["a"])
  447. .SelectMany(x => x.Split(';'))
  448. .Concat(roslynAnalyzerDllPaths)
  449. .Distinct()
  450. .ToArray()));
  451. }
  452. private static ILookup<string, string> GetOtherArgumentsFromResponseFilesData(List<ResponseFileData> responseFilesData)
  453. {
  454. var paths = responseFilesData.SelectMany(x =>
  455. {
  456. return x.OtherArguments.Where(a => a.StartsWith("/") || a.StartsWith("-"))
  457. .Select(b =>
  458. {
  459. var index = b.IndexOf(":", StringComparison.Ordinal);
  460. if (index > 0 && b.Length > index)
  461. {
  462. var key = b.Substring(1, index - 1);
  463. return new KeyValuePair<string, string>(key, b.Substring(index + 1));
  464. }
  465. const string warnaserror = "warnaserror";
  466. if (b.Substring(1).StartsWith(warnaserror))
  467. {
  468. return new KeyValuePair<string, string>(warnaserror, b.Substring(warnaserror.Length + 1));
  469. }
  470. return default;
  471. });
  472. })
  473. .Distinct()
  474. .ToLookup(o => o.Key, pair => pair.Value);
  475. return paths;
  476. }
  477. private static string GenerateAnalyserItemGroup(string[] paths)
  478. {
  479. // <ItemGroup>
  480. // <Analyzer Include="..\packages\Comments_analyser.1.0.6626.21356\analyzers\dotnet\cs\Comments_analyser.dll" />
  481. // <Analyzer Include="..\packages\UnityEngineAnalyzer.1.0.0.0\analyzers\dotnet\cs\UnityEngineAnalyzer.dll" />
  482. // </ItemGroup>
  483. if (!paths.Any())
  484. return string.Empty;
  485. var analyserBuilder = new StringBuilder();
  486. analyserBuilder.Append(" <ItemGroup>").Append(k_WindowsNewline);
  487. foreach (var path in paths)
  488. {
  489. analyserBuilder.Append($" <Analyzer Include=\"{path}\" />").Append(k_WindowsNewline);
  490. }
  491. analyserBuilder.Append(" </ItemGroup>").Append(k_WindowsNewline);
  492. return analyserBuilder.ToString();
  493. }
  494. static string GetSolutionText()
  495. {
  496. 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");
  497. }
  498. static string GetProjectFooterTemplate()
  499. {
  500. 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>", @"");
  501. }
  502. static void GetProjectHeaderTemplate(
  503. StringBuilder builder,
  504. string assemblyGUID,
  505. string assemblyName,
  506. string defines,
  507. bool allowUnsafe,
  508. string analyzerBlock
  509. )
  510. {
  511. builder.Append(@"<?xml version=""1.0"" encoding=""utf-8""?>").Append(k_WindowsNewline);
  512. builder.Append(@"<Project ToolsVersion=""").Append(k_ToolsVersion).Append(@""" DefaultTargets=""Build"" xmlns=""").Append(MSBuildNamespaceUri).Append(@""">").Append(k_WindowsNewline);
  513. builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
  514. builder.Append(@" <LangVersion>").Append(k_TargetLanguageVersion).Append("</LangVersion>").Append(k_WindowsNewline);
  515. builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
  516. builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
  517. builder.Append(@" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>").Append(k_WindowsNewline);
  518. builder.Append(@" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>").Append(k_WindowsNewline);
  519. builder.Append(@" <ProductVersion>").Append(k_ProductVersion).Append("</ProductVersion>").Append(k_WindowsNewline);
  520. builder.Append(@" <SchemaVersion>2.0</SchemaVersion>").Append(k_WindowsNewline);
  521. builder.Append(@" <RootNamespace>").Append(EditorSettings.projectGenerationRootNamespace).Append("</RootNamespace>").Append(k_WindowsNewline);
  522. builder.Append(@" <ProjectGuid>{").Append(assemblyGUID).Append("}</ProjectGuid>").Append(k_WindowsNewline);
  523. builder.Append(@" <OutputType>Library</OutputType>").Append(k_WindowsNewline);
  524. builder.Append(@" <AppDesignerFolder>Properties</AppDesignerFolder>").Append(k_WindowsNewline);
  525. builder.Append(@" <AssemblyName>").Append(assemblyName).Append("</AssemblyName>").Append(k_WindowsNewline);
  526. builder.Append(@" <TargetFrameworkVersion>").Append(k_TargetFrameworkVersion).Append("</TargetFrameworkVersion>").Append(k_WindowsNewline);
  527. builder.Append(@" <FileAlignment>512</FileAlignment>").Append(k_WindowsNewline);
  528. builder.Append(@" <BaseDirectory>").Append(k_BaseDirectory).Append("</BaseDirectory>").Append(k_WindowsNewline);
  529. builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
  530. builder.Append(@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">").Append(k_WindowsNewline);
  531. builder.Append(@" <DebugSymbols>true</DebugSymbols>").Append(k_WindowsNewline);
  532. builder.Append(@" <DebugType>full</DebugType>").Append(k_WindowsNewline);
  533. builder.Append(@" <Optimize>false</Optimize>").Append(k_WindowsNewline);
  534. builder.Append(@" <OutputPath>Temp\bin\Debug\</OutputPath>").Append(k_WindowsNewline);
  535. builder.Append(@" <DefineConstants>").Append(defines).Append("</DefineConstants>").Append(k_WindowsNewline);
  536. builder.Append(@" <ErrorReport>prompt</ErrorReport>").Append(k_WindowsNewline);
  537. builder.Append(@" <WarningLevel>4</WarningLevel>").Append(k_WindowsNewline);
  538. builder.Append(@" <NoWarn>0169</NoWarn>").Append(k_WindowsNewline);
  539. builder.Append(@" <AllowUnsafeBlocks>").Append(allowUnsafe).Append("</AllowUnsafeBlocks>").Append(k_WindowsNewline);
  540. builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
  541. builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
  542. builder.Append(@" <NoConfig>true</NoConfig>").Append(k_WindowsNewline);
  543. builder.Append(@" <NoStdLib>true</NoStdLib>").Append(k_WindowsNewline);
  544. builder.Append(@" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>").Append(k_WindowsNewline);
  545. builder.Append(@" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>").Append(k_WindowsNewline);
  546. builder.Append(@" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>").Append(k_WindowsNewline);
  547. builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
  548. builder.Append(analyzerBlock);
  549. builder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
  550. }
  551. void SyncSolution(IEnumerable<Assembly> assemblies)
  552. {
  553. SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(assemblies));
  554. }
  555. string SolutionText(IEnumerable<Assembly> assemblies)
  556. {
  557. var fileversion = "11.00";
  558. var vsversion = "2010";
  559. var relevantAssemblies = RelevantAssembliesForMode(assemblies);
  560. string projectEntries = GetProjectEntries(relevantAssemblies);
  561. string projectConfigurations = string.Join(k_WindowsNewline, relevantAssemblies.Select(i => GetProjectActiveConfigurations(ProjectGuid(i.name))).ToArray());
  562. return string.Format(GetSolutionText(), fileversion, vsversion, projectEntries, projectConfigurations);
  563. }
  564. static IEnumerable<Assembly> RelevantAssembliesForMode(IEnumerable<Assembly> assemblies)
  565. {
  566. return assemblies.Where(i => ScriptingLanguage.CSharp == ScriptingLanguageFor(i));
  567. }
  568. /// <summary>
  569. /// Get a Project("{guid}") = "MyProject", "MyProject.csproj", "{projectguid}"
  570. /// entry for each relevant language
  571. /// </summary>
  572. string GetProjectEntries(IEnumerable<Assembly> assemblies)
  573. {
  574. var projectEntries = assemblies.Select(i => string.Format(
  575. m_SolutionProjectEntryTemplate,
  576. SolutionGuid(i),
  577. i.name,
  578. Path.GetFileName(ProjectFile(i)),
  579. ProjectGuid(i.name)
  580. ));
  581. return string.Join(k_WindowsNewline, projectEntries.ToArray());
  582. }
  583. /// <summary>
  584. /// Generate the active configuration string for a given project guid
  585. /// </summary>
  586. string GetProjectActiveConfigurations(string projectGuid)
  587. {
  588. return string.Format(
  589. m_SolutionProjectConfigurationTemplate,
  590. projectGuid);
  591. }
  592. string EscapedRelativePathFor(string file)
  593. {
  594. var projectDir = ProjectDirectory.Replace('/', '\\');
  595. file = file.Replace('/', '\\');
  596. var path = SkipPathPrefix(file, projectDir);
  597. var packageInfo = m_AssemblyNameProvider.FindForAssetPath(path.Replace('\\', '/'));
  598. if (packageInfo != null)
  599. {
  600. // We have to normalize the path, because the PackageManagerRemapper assumes
  601. // dir seperators will be os specific.
  602. var absolutePath = Path.GetFullPath(NormalizePath(path)).Replace('/', '\\');
  603. path = SkipPathPrefix(absolutePath, projectDir);
  604. }
  605. return SecurityElement.Escape(path);
  606. }
  607. static string SkipPathPrefix(string path, string prefix)
  608. {
  609. if (path.StartsWith($@"{prefix}\"))
  610. return path.Substring(prefix.Length + 1);
  611. return path;
  612. }
  613. static string NormalizePath(string path)
  614. {
  615. if (Path.DirectorySeparatorChar == '\\')
  616. return path.Replace('/', Path.DirectorySeparatorChar);
  617. return path.Replace('\\', Path.DirectorySeparatorChar);
  618. }
  619. string ProjectGuid(string assembly)
  620. {
  621. return m_GUIDProvider.ProjectGuid(m_ProjectName, assembly);
  622. }
  623. string SolutionGuid(Assembly assembly)
  624. {
  625. return m_GUIDProvider.SolutionGuid(m_ProjectName, GetExtensionOfSourceFiles(assembly.sourceFiles));
  626. }
  627. static string ProjectFooter()
  628. {
  629. return GetProjectFooterTemplate();
  630. }
  631. static string GetProjectExtension()
  632. {
  633. return ".csproj";
  634. }
  635. void WriteVSCodeSettingsFiles()
  636. {
  637. var vsCodeDirectory = Path.Combine(ProjectDirectory, ".vscode");
  638. if (!m_FileIOProvider.Exists(vsCodeDirectory))
  639. m_FileIOProvider.CreateDirectory(vsCodeDirectory);
  640. var vsCodeSettingsJson = Path.Combine(vsCodeDirectory, "settings.json");
  641. if (!m_FileIOProvider.Exists(vsCodeSettingsJson))
  642. m_FileIOProvider.WriteAllText(vsCodeSettingsJson, k_SettingsJson);
  643. }
  644. }
  645. public static class SolutionGuidGenerator
  646. {
  647. static MD5 mD5 = MD5CryptoServiceProvider.Create();
  648. public static string GuidForProject(string projectName)
  649. {
  650. return ComputeGuidHashFor(projectName + "salt");
  651. }
  652. public static string GuidForSolution(string projectName, string sourceFileExtension)
  653. {
  654. if (sourceFileExtension.ToLower() == "cs")
  655. // GUID for a C# class library: http://www.codeproject.com/Reference/720512/List-of-Visual-Studio-Project-Type-GUIDs
  656. return "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC";
  657. return ComputeGuidHashFor(projectName);
  658. }
  659. static string ComputeGuidHashFor(string input)
  660. {
  661. var hash = mD5.ComputeHash(Encoding.Default.GetBytes(input));
  662. return new Guid(hash).ToString();
  663. }
  664. }
  665. }