ProjectGeneration.cs 31 KB

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