// Copyright (c) Ubisoft. All Rights Reserved. // Licensed under the Apache 2.0 License. See LICENSE.md in the project root for license information. using System.Collections.Generic; using System.IO; using System.Linq; namespace Sharpmake.Generators.Apple { public partial class XCWorkspace : ISolutionGenerator { // Solution _Solution; private Builder _builder; private const string SolutionExtension = ".xcworkspace"; private const string SolutionContentsFileName = "contents.xcworkspacedata"; private Dictionary _solutionFolderCache = new Dictionary(); public void Generate(Builder builder, Solution solution, List configurations, string solutionFile, List generatedFiles, List skipFiles) { _builder = builder; FileInfo fileInfo = new FileInfo(solutionFile); string solutionPath = fileInfo.Directory.FullName; string solutionFileName = fileInfo.Name; bool updated; string solutionFileResult = Generate(solution, configurations, solutionPath, solutionFileName, out updated); if (updated) generatedFiles.Add(solutionFileResult); else skipFiles.Add(solutionFileResult); _builder = null; } private string Generate(Solution solution, List configurations, string solutionPath, string solutionFile, out bool updated) { // Create the target folder (solutions and projects are folders in XCode). string solutionFolder = Util.GetCapitalizedPath(solutionPath + Path.DirectorySeparatorChar + solutionFile + SolutionExtension); Directory.CreateDirectory(solutionFolder); // Main solution file. string solutionFileContentsPath = solutionFolder + Path.DirectorySeparatorChar + SolutionContentsFileName; FileInfo solutionFileContentsInfo = new FileInfo(solutionFileContentsPath); bool projectsWereFiltered; List solutionProjects = solution.GetResolvedProjects(configurations, out projectsWereFiltered).ToList(); solutionProjects.Sort((a, b) => string.Compare(a.ProjectName, b.ProjectName)); // Ensure all projects are always in the same order to avoid random shuffles // Move the first executable project on top. foreach (Solution.ResolvedProject resolvedProject in solutionProjects) { if (resolvedProject.Configurations[0].Output == Project.Configuration.OutputType.Exe) { solutionProjects.Remove(resolvedProject); solutionProjects.Insert(0, resolvedProject); break; } } if (solutionProjects.Count == 0) { updated = solutionFileContentsInfo.Exists; if (updated) File.Delete(solutionFileContentsPath); return solutionFolder; } var fileGenerator = new FileGenerator(); fileGenerator.Write(Template.Header); _solutionFolderCache.Clear(); List solutionsFiles = solutionProjects.Select(project => { var solutionFileItem = new SolutionFile() { Name = project.ProjectFile, Parent = ParseSolutionFolder(project.SolutionFolder) }; solutionFileItem.RegisterToParent(); return solutionFileItem; }).ToList(); List solutionsItems = solutionsFiles.GroupBy(solutionsItem => solutionsItem.GetRoot()).Select(group => group.Key).ToList(); solutionsItems.Sort((a, b) => string.Compare(a.Name, b.Name)); foreach (var solutionItem in solutionsItems) { //Sort of folders content (solutionItem as SolutionFolder)?.Sort(); WriteSolutionItem(fileGenerator, solutionItem); } fileGenerator.Write(Template.Footer); // Write the solution file updated = _builder.Context.WriteGeneratedFile(solution.GetType(), solutionFileContentsInfo, fileGenerator); return solutionFileContentsInfo.FullName; } private void Write(string value, TextWriter writer, Resolver resolver) { string resolvedValue = resolver.Resolve(value); StringReader reader = new StringReader(resolvedValue); string str = reader.ReadToEnd(); writer.Write(str); writer.Flush(); } private static void WriteSolutionItem(FileGenerator fileGenerator, SolutionItem solutionItem, int nbIndent = 0) { bool isFolder = !solutionItem.IsFile; string indent = new string('\t', nbIndent); if (isFolder) { SolutionFolder solutionFolder = solutionItem as SolutionFolder; using (fileGenerator.Declare("folderName", solutionFolder.Name)) using (fileGenerator.Declare("indent", indent)) { fileGenerator.Write(Template.GroupBegin); } foreach (var child in solutionFolder.Childs) { WriteSolutionItem(fileGenerator, child, nbIndent + 1); } using (fileGenerator.Declare("indent", indent)) { fileGenerator.Write(Template.GroupEnd); } } else { using (fileGenerator.Declare("projectPath", solutionItem.Name)) using (fileGenerator.Declare("indent", indent)) { fileGenerator.Write(Template.ProjectReferenceAbsolute); } } } [System.Diagnostics.DebuggerDisplay("{Path}")] private class SolutionItem { public string Name; public string Path { get { if (Parent == null) return Name; return System.IO.Path.Combine(Parent.Path, Name); } } public SolutionItem Parent; public virtual bool IsFile => true; public SolutionItem GetRoot() { return Parent == null ? this : Parent.GetRoot(); } } private class SolutionFile : SolutionItem { public override bool IsFile => true; public void RegisterToParent() { var parent = Parent as SolutionFolder; if (parent != null) parent.Childs.Add(this); } } private class SolutionFolder : SolutionItem { public List Childs = new List(); public void Sort() { Childs.Sort((a, b) => string.Compare(a.Name, b.Name)); foreach (var child in Childs) { (child as SolutionFolder)?.Sort(); } } public override bool IsFile => false; } private SolutionFolder ParseSolutionFolder(string solutionFolders) { if (string.IsNullOrEmpty(solutionFolders)) return null; SolutionFolder result = null; string path = MakeStandartPath(solutionFolders); if (!_solutionFolderCache.TryGetValue(path, out result)) { List solutionFolderStack = path.Split(Util._pathSeparators, System.StringSplitOptions.RemoveEmptyEntries).ToList(); string folderName = solutionFolderStack.Last(); solutionFolderStack.RemoveAt(solutionFolderStack.Count - 1); SolutionFolder parent = ParseSolutionFolder(string.Join(Path.DirectorySeparatorChar.ToString(), solutionFolderStack)); result = new SolutionFolder() { Name = folderName, Parent = parent }; if (parent != null) { parent.Childs.Add(result); } _solutionFolderCache.Add(path, result); } return result; } private string MakeStandartPath(string path) { return string.Join(Path.DirectorySeparatorChar.ToString(), path.Split(Util._pathSeparators, System.StringSplitOptions.RemoveEmptyEntries)); } } }