// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml; using Flax.Build.Platforms; namespace Flax.Build.Projects.VisualStudio { /// /// Project generator for Visual Studio IDE. /// public abstract class VisualStudioProjectGenerator : ProjectGenerator { private sealed class AndroidProject : VisualStudioProject { /// public override Guid ProjectTypeGuid => ProjectTypeGuids.Android; /// public override void Generate(string solutionPath) { // Try to reuse the existing project guid from existing files ProjectGuid = GetProjectGuid(Path, Name); if (ProjectGuid == Guid.Empty) ProjectGuid = GetProjectGuid(solutionPath, Name); if (ProjectGuid == Guid.Empty) ProjectGuid = Guid.NewGuid(); var gen = (VisualStudioProjectGenerator)Generator; var projectFileToolVersion = gen.ProjectFileToolVersion; var vcProjectFileContent = new StringBuilder(); vcProjectFileContent.AppendLine(""); vcProjectFileContent.AppendLine(string.Format("", projectFileToolVersion)); vcProjectFileContent.AppendLine(" "); foreach (var configuration in Configurations) { vcProjectFileContent.AppendLine(string.Format(" ", configuration.Name)); vcProjectFileContent.AppendLine(string.Format(" {0}", configuration.Text)); vcProjectFileContent.AppendLine(string.Format(" {0}", configuration.ArchitectureName)); vcProjectFileContent.AppendLine(" "); } vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(string.Format(" {0}", ProjectGuid.ToString("B").ToUpperInvariant())); vcProjectFileContent.AppendLine(string.Format(" {0}", BaseName)); vcProjectFileContent.AppendLine(string.Format(" {0}", projectFileToolVersion)); vcProjectFileContent.AppendLine(string.Format(" {0}", Configuration.AndroidPlatformApi)); vcProjectFileContent.AppendLine(string.Format(" {0}", "arm64-v8a")); vcProjectFileContent.AppendLine(" Application"); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" $(RootNamespace)"); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(""); Utilities.WriteFileIfChanged(Path, vcProjectFileContent.ToString()); } } /// /// The Visual Studio solution project file types GUIDs. Based on http://www.codeproject.com/Reference/720512/List-of-Visual-Studio-Project-Type-GUIDs. /// public static class ProjectTypeGuids { /// /// The solution folder. /// public static Guid SolutionFolder = new Guid("{2150E333-8FDC-42A3-9474-1A3956D46DE8}"); /// /// The Windows C# /// public static Guid WindowsCSharp = new Guid("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"); /// /// The Windows Visual C++. /// public static Guid WindowsVisualCpp = new Guid("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"); /// /// The Android. /// public static Guid Android = new Guid("{39E2626F-3545-4960-A6E8-258AD8476CE5}"); /// /// The Flax.VS. /// public static Guid FlaxVS = new Guid("{BE633490-FBA4-41EB-80D4-EFA312592B8E}"); /// /// Converts the GUI to To the option. /// /// Type of the project. /// The option string. public static string ToOption(Guid projectType) { return projectType.ToString("B").ToUpperInvariant(); } } /// /// The target IDE version. /// public readonly VisualStudioVersion Version; /// /// Gets the project file tool version. /// public string ProjectFileToolVersion { get { switch (Version) { case VisualStudioVersion.VisualStudio2015: return "14.0"; case VisualStudioVersion.VisualStudio2017: return "15.0"; case VisualStudioVersion.VisualStudio2019: return "16.0"; case VisualStudioVersion.VisualStudio2022: return "17.0"; } return string.Empty; } } /// /// Initializes a new instance of the class. /// /// The version. protected VisualStudioProjectGenerator(VisualStudioVersion version) { Version = version; } /// /// Gets the project unique identifier from the existing project or generates a new one. /// /// The path. /// The project ID. public static Guid GetProjectGuid(string path, string projectName) { // Look up for the guid in VC++-project file if (File.Exists(path) && Path.GetExtension(path).Equals(".vcxproj", StringComparison.OrdinalIgnoreCase)) { try { XmlDocument doc = new XmlDocument(); doc.Load(path); XmlNodeList elements = doc.GetElementsByTagName("ProjectGuid"); foreach (XmlElement element in elements) { return Guid.ParseExact(element.InnerText.Trim("{}".ToCharArray()), "D"); } } catch { // Hide errors } } if (File.Exists(path) && Path.GetExtension(path).Equals(".sln", StringComparison.OrdinalIgnoreCase)) { try { Regex projectRegex = new Regex(@"Project\(""{(\S+)}""\) = \""(\S+)\"", \""(\S+)\"", \""{(\S+)}\"""); MatchCollection matches = projectRegex.Matches(File.ReadAllText(path)); for (int i = 0; i < matches.Count; i++) { if (matches[i].Groups[1].Value.Equals("2150E333-8FDC-42A3-9474-1A3956D46DE8", StringComparison.OrdinalIgnoreCase)) continue; if (matches[i].Groups[2].Value == projectName) return Guid.ParseExact(matches[i].Groups[4].Value, "D"); } } catch { // Hide errors } } return Guid.Empty; } /// public override string SolutionFileExtension => "sln"; /// public override Project CreateProject() { return new VisualStudioProject { Generator = this, }; } private struct SolutionConfiguration : IEquatable, IComparable { public string Name; public string OriginalName; public string Configuration; public string Platform; public SolutionConfiguration(Project.ConfigurationData configuration) { OriginalName = configuration.Name.Replace("AnyCPU", "Any CPU"); var name = OriginalName.Remove(OriginalName.IndexOf('|')); var parts = name.Split('.'); if (parts.Length == 3) Configuration = parts[0] + '.' + parts[2]; else if (parts.Length == 1) Configuration = parts[0]; else throw new Exception($"Unknown project configuration {configuration.Name}."); Platform = configuration.PlatformName + "_" + configuration.ArchitectureName; if (configuration.Architecture == TargetArchitecture.AnyCPU) Platform = "Any CPU"; var platform = Build.Platform.GetPlatform(configuration.Platform); if (platform is IProjectCustomizer customizer) customizer.GetSolutionArchitectureName(configuration.Architecture, ref Platform); Name = Configuration + '|' + Platform; } public SolutionConfiguration(string configuration, string platform) { OriginalName = string.Empty; Name = configuration + '|' + platform; Configuration = configuration; Platform = platform; } /// public int CompareTo(object obj) { if (obj is SolutionConfiguration other) return Name.CompareTo(other.Name); return 1; } /// public bool Equals(SolutionConfiguration other) { return Name == other.Name; } /// public override string ToString() { return Name; } } /// public override void GenerateSolution(Solution solution) { // Ensure that the main project is the first one (initially selected by Visual Studio) if (solution.MainProject != null && solution.Projects.Length != 0 && solution.Projects[0] != solution.MainProject) { for (int i = 1; i < solution.Projects.Length; i++) { if (solution.Projects[i] == solution.MainProject) { solution.Projects[i] = solution.Projects[0]; solution.Projects[0] = solution.MainProject; break; } } } // Try to extract solution folder info from the existing solution file to make random IDs stable var solutionId = Guid.NewGuid(); var folderIds = new Dictionary(); if (File.Exists(solution.Path)) { try { var contents = File.ReadAllText(solution.Path); var solutionIdMatch = Regex.Match(contents, "SolutionGuid = \\{(.*?)\\}"); if (solutionIdMatch.Success) { var value = solutionIdMatch.Value; solutionId = Guid.ParseExact(value.Substring(15), "B"); } var folderIdMatches = new Regex("Project\\(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\"\\) = \"(.*?)\", \"(.*?)\", \"{(.*?)}\"").Matches(contents); foreach (Match match in folderIdMatches) { var folder = match.Groups[2].Value; var folderId = Guid.ParseExact(match.Groups[3].Value, "D"); folderIds[folder] = folderId; } } catch (Exception ex) { Log.Warning("Failed to restore solution and solution folders identifiers from existing file."); Log.Exception(ex); } } StringBuilder vcSolutionFileContent = new StringBuilder(); var solutionDirectory = Path.GetDirectoryName(solution.Path); var projects = solution.Projects.Cast().ToArray(); // Header if (Version == VisualStudioVersion.VisualStudio2022) { vcSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00"); vcSolutionFileContent.AppendLine("# Visual Studio Version 17"); vcSolutionFileContent.AppendLine("VisualStudioVersion = 17.0.31314.256"); vcSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1"); } else if (Version == VisualStudioVersion.VisualStudio2019) { vcSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00"); vcSolutionFileContent.AppendLine("# Visual Studio Version 16"); vcSolutionFileContent.AppendLine("VisualStudioVersion = 16.0.28315.86"); vcSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1"); } else if (Version == VisualStudioVersion.VisualStudio2017) { vcSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00"); vcSolutionFileContent.AppendLine("# Visual Studio 15"); vcSolutionFileContent.AppendLine("VisualStudioVersion = 15.0.25807.0"); vcSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1"); } else if (Version == VisualStudioVersion.VisualStudio2015) { vcSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00"); vcSolutionFileContent.AppendLine("# Visual Studio 14"); vcSolutionFileContent.AppendLine("VisualStudioVersion = 14.0.22310.1"); vcSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1"); } else { throw new Exception("Unsupported solution file format."); } // Solution folders var folderNames = new HashSet(); { // Move projects to subfolders based on group names including subfolders to match the location of the source in the workspace foreach (var project in projects) { var folder = project.GroupName; if (project.SourceDirectories != null && project.SourceDirectories.Count == 1) { var subFolder = Utilities.MakePathRelativeTo(Path.GetDirectoryName(project.SourceDirectories[0]), project.WorkspaceRootPath); if (subFolder.StartsWith("Source\\")) subFolder = subFolder.Substring(7); if (subFolder.Length != 0) { if (folder.Length != 0) folder += '\\'; folder += subFolder; } } if (string.IsNullOrEmpty(folder)) continue; var folderParents = folder.Split('\\'); for (int i = 0; i < folderParents.Length; i++) { var folderPath = folderParents[0]; for (int j = 1; j <= i; j++) folderPath += '\\' + folderParents[j]; if (folderNames.Contains(folderPath)) { project.FolderGuid = folderIds[folderPath]; } else { if (!folderIds.TryGetValue(folderPath, out project.FolderGuid)) { project.FolderGuid = Guid.NewGuid(); folderIds.Add(folderPath, project.FolderGuid); } folderNames.Add(folderPath); } } } foreach (var folder in folderNames) { var folderGuid = folderIds[folder].ToString("B").ToUpperInvariant(); var typeGuid = ProjectTypeGuids.ToOption(ProjectTypeGuids.SolutionFolder); var lastSplit = folder.LastIndexOf('\\'); var name = lastSplit != -1 ? folder.Substring(lastSplit + 1) : folder; vcSolutionFileContent.AppendLine(string.Format("Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\"", typeGuid, name, folder, folderGuid)); vcSolutionFileContent.AppendLine("EndProject"); } } // Solution projects foreach (var project in projects) { var projectId = project.ProjectGuid.ToString("B").ToUpperInvariant(); var typeGuid = ProjectTypeGuids.ToOption(project.ProjectTypeGuid); vcSolutionFileContent.AppendLine(string.Format("Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\"", typeGuid, project.Name, Utilities.MakePathRelativeTo(project.Path, solutionDirectory), projectId)); if (project.Dependencies.Count > 0) { vcSolutionFileContent.AppendLine("\tProjectSection(ProjectDependencies) = postProject"); foreach (var dependency in project.Dependencies.Cast()) { string dependencyId = dependency.ProjectGuid.ToString("B").ToUpperInvariant(); vcSolutionFileContent.AppendLine("\t\t" + dependencyId + " = " + dependencyId); } vcSolutionFileContent.AppendLine("\tEndProjectSection"); } vcSolutionFileContent.AppendLine("EndProject"); } // Global configuration { vcSolutionFileContent.AppendLine("Global"); // Collect all unique configurations var configurations = new HashSet(); var mainArchitectures = solution.MainProject.Targets.SelectMany(x => x.Architectures).Distinct().ToArray(); foreach (var project in projects) { if (project.Configurations == null || project.Configurations.Count == 0) throw new Exception("Missing configurations for project " + project.Name); // Prevent generating default Debug|AnyCPU and Release|AnyCPU configurations from Flax projects if (project.Name == "BuildScripts" || project.Name == "Flax.Build" || project.Name == "Flax.Build.Tests") continue; foreach (var configuration in project.Configurations) { // Skip architectures which are not included in the game project if (!mainArchitectures.Contains(configuration.Architecture)) continue; configurations.Add(new SolutionConfiguration(configuration)); } } // Add missing configurations (Visual Studio needs all permutations of configuration/platform pair) var configurationNames = configurations.Select(x => x.Configuration).Distinct().ToArray(); var platformNames = configurations.Select(x => x.Platform).Distinct().ToArray(); foreach (var configurationName in configurationNames) { foreach (var platformName in platformNames) { configurations.Add(new SolutionConfiguration(configurationName, platformName)); } } // Sort configurations var configurationsSorted = new List(configurations); configurationsSorted.Sort(); // Global configurations { vcSolutionFileContent.AppendLine(" GlobalSection(SolutionConfigurationPlatforms) = preSolution"); foreach (var configuration in configurationsSorted) { vcSolutionFileContent.AppendLine(" " + configuration.Name + " = " + configuration.Name); } vcSolutionFileContent.AppendLine(" EndGlobalSection"); } // Per-project configurations mapping { vcSolutionFileContent.AppendLine(" GlobalSection(ProjectConfigurationPlatforms) = postSolution"); foreach (var project in projects) { string projectId = project.ProjectGuid.ToString("B").ToUpperInvariant(); foreach (var configuration in configurationsSorted) { SolutionConfiguration projectConfiguration; bool build = false; int firstFullMatch = -1, firstPlatformMatch = -1, firstEditorMatch = -1; for (int i = 0; i < project.Configurations.Count; i++) { var e = new SolutionConfiguration(project.Configurations[i]); if (e.Name == configuration.Name) { firstFullMatch = i; break; } if (firstPlatformMatch == -1 && e.Platform == configuration.Platform) { firstPlatformMatch = i; } if (firstEditorMatch == -1 && e.Configuration == configuration.Configuration) { firstEditorMatch = i; } } if (project is AndroidProject) { // Utility Android deploy project only for exact match if (firstFullMatch != -1) projectConfiguration = configuration; else projectConfiguration = new SolutionConfiguration(project.Configurations[0]); } else if (firstFullMatch != -1) { projectConfiguration = configuration; // Always build the main project build = solution.MainProject == project; // Build C# projects (needed for Rider solution wide analysis) build |= project.Type == TargetType.DotNetCore; // Always build the project named after solution if main project was not set build |= solution.MainProject == null && project.Name == solution.Name; } else if (firstPlatformMatch != -1 && !configuration.Name.StartsWith("Editor.")) { // No exact match, pick the first configuration for matching platform projectConfiguration = new SolutionConfiguration(project.Configurations[firstPlatformMatch]); } else if (firstEditorMatch != -1 && configuration.Name.StartsWith("Editor.")) { // No exact match, pick the matching editor configuration for different platform. // As an example, Editor configuration for Android projects should be remapped // to desktop platform in order to provide working Intellisense information. projectConfiguration = new SolutionConfiguration(project.Configurations[firstEditorMatch]); } else { // No match projectConfiguration = new SolutionConfiguration(project.Configurations[0]); } vcSolutionFileContent.AppendLine(string.Format(" {0}.{1}.ActiveCfg = {2}", projectId, configuration.Name, projectConfiguration.OriginalName)); if (build) vcSolutionFileContent.AppendLine(string.Format(" {0}.{1}.Build.0 = {2}", projectId, configuration.Name, projectConfiguration.OriginalName)); } } vcSolutionFileContent.AppendLine(" EndGlobalSection"); } // Always show solution root node { vcSolutionFileContent.AppendLine(" GlobalSection(SolutionProperties) = preSolution"); vcSolutionFileContent.AppendLine(" HideSolutionNode = FALSE"); vcSolutionFileContent.AppendLine(" EndGlobalSection"); } // Solution directory hierarchy { vcSolutionFileContent.AppendLine(" GlobalSection(NestedProjects) = preSolution"); // Write nested folders hierarchy foreach (var folder in folderNames) { var lastSplit = folder.LastIndexOf('\\'); if (lastSplit != -1) { var folderGuid = folderIds[folder].ToString("B").ToUpperInvariant(); var parentFolder = folder.Substring(0, lastSplit); var parentFolderGuid = folderIds[parentFolder].ToString("B").ToUpperInvariant(); vcSolutionFileContent.AppendLine(string.Format(" {0} = {1}", folderGuid, parentFolderGuid)); } } // Write mapping for projectId - folderId foreach (var project in projects) { if (project.FolderGuid != Guid.Empty) { var projectGuidString = project.ProjectGuid.ToString("B").ToUpperInvariant(); var folderGuidString = project.FolderGuid.ToString("B").ToUpperInvariant(); vcSolutionFileContent.AppendLine(string.Format(" {0} = {1}", projectGuidString, folderGuidString)); } } vcSolutionFileContent.AppendLine(" EndGlobalSection"); } // Solution identifier { vcSolutionFileContent.AppendLine(" GlobalSection(ExtensibilityGlobals) = postSolution"); vcSolutionFileContent.AppendLine(string.Format(" SolutionGuid = {0}", solutionId.ToString("B").ToUpperInvariant())); vcSolutionFileContent.AppendLine(" EndGlobalSection"); } vcSolutionFileContent.AppendLine("EndGlobal"); } // Save the file Utilities.WriteFileIfChanged(solution.Path, vcSolutionFileContent.ToString()); // Generate launch profiles for C# projects if (Version >= VisualStudioVersion.VisualStudio2022) { var profiles = new Dictionary(); var profile = new StringBuilder(); var editorPath = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), $"Development/FlaxEditor{Utilities.GetPlatformExecutableExt()}")).Replace('\\', '/'); var workspacePath = Utilities.NormalizePath(solutionDirectory).Replace('\\', '/'); foreach (var project in projects) { if (project.Type == TargetType.DotNetCore) { var path = Path.Combine(Path.GetDirectoryName(project.Path), "Properties/launchSettings.json"); path = Utilities.NormalizePath(path); if (profiles.ContainsKey(path)) profile.AppendLine(","); profile.AppendLine($" \"{project.BaseName}\": {{"); profile.AppendLine(" \"commandName\": \"Executable\","); profile.AppendLine($" \"workingDirectory\": \"{workspacePath}\","); profile.AppendLine($" \"executablePath\": \"{editorPath}\","); profile.AppendLine($" \"commandLineArgs\": \"-project \\\"{workspacePath}\\\"\","); profile.AppendLine(" \"nativeDebugging\": false"); profile.Append(" }"); if (profiles.ContainsKey(path)) profiles[path] += profile.ToString(); else profiles.Add(path, profile.ToString()); profile.Clear(); } } foreach (var e in profiles) { var folder = Path.GetDirectoryName(e.Key); if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); profile.Clear(); profile.AppendLine("{"); profile.AppendLine(" \"profiles\": {"); profile.AppendLine(e.Value); profile.AppendLine(" }"); profile.Append("}"); File.WriteAllText(e.Key, profile.ToString(), Encoding.UTF8); } } // Generate Rider-specific configuration files { StringBuilder dotSettingsFileContent = new StringBuilder(); string dotSettingsUserFilePath = solution.Path + ".DotSettings.user"; // Solution settings (user layer) bool useResharperBuild = false; // This needs to be disabled for custom build steps to run properly if (File.Exists(dotSettingsUserFilePath)) { foreach (var line in File.ReadAllLines(dotSettingsUserFilePath)) { if (line.Contains(@"/UseMsbuildSolutionBuilder/@EntryValue")) { if (!useResharperBuild) { dotSettingsFileContent.Append("\t").Append(@"No"); if (line.Contains("")) dotSettingsFileContent.Append("\n"); else dotSettingsFileContent.Append("\n"); } continue; } dotSettingsFileContent.Append(line).Append("\n"); } } else { dotSettingsFileContent.Append(@"").Append("\n"); if (!useResharperBuild) dotSettingsFileContent.Append("\t").Append(@"No"); dotSettingsFileContent.Append("\n"); } Utilities.WriteFileIfChanged(dotSettingsUserFilePath, dotSettingsFileContent.ToString()); } // Custom MSBuild .targets file to prevent building Flax C#-projects directly with MSBuild { var targetsFileContent = new StringBuilder(); targetsFileContent.AppendLine(""); targetsFileContent.AppendLine(" "); targetsFileContent.AppendLine(" "); targetsFileContent.AppendLine(""); Utilities.WriteFileIfChanged(Path.Combine(Globals.Root, "Cache", "Projects", "Flax.Build.CSharp.SkipBuild.targets"), targetsFileContent.ToString()); } // Override MSBuild build tasks to run Flax.Build in C#-only projects { // Build command for the build tool var buildToolPath = Path.ChangeExtension(typeof(Builder).Assembly.Location, null); var targetsFileContent = new StringBuilder(); targetsFileContent.AppendLine(""); targetsFileContent.AppendLine(" "); targetsFileContent.AppendLine(" "); AppendBuildToolCommands(targetsFileContent, "-build"); targetsFileContent.AppendLine(" "); targetsFileContent.AppendLine(" "); AppendBuildToolCommands(targetsFileContent, "-rebuild"); targetsFileContent.AppendLine(" "); targetsFileContent.AppendLine(" "); AppendBuildToolCommands(targetsFileContent, "-clean"); targetsFileContent.AppendLine(" "); targetsFileContent.AppendLine(""); Utilities.WriteFileIfChanged(Path.Combine(Globals.Root, "Cache", "Projects", "Flax.Build.CSharp.targets"), targetsFileContent.ToString()); void AppendBuildToolCommands(StringBuilder str, string extraArgs) { foreach (var configuration in solution.MainProject.Configurations) { var cmdLine = string.Format("\"{0}\" -log -mutex -workspace=\"{1}\" -arch={2} -configuration={3} -platform={4} -buildTargets={5}", buildToolPath, solution.MainProject.WorkspaceRootPath, configuration.Architecture, configuration.Configuration, configuration.Platform, configuration.Target); Configuration.PassArgs(ref cmdLine); str.AppendLine(string.Format(" ", cmdLine, extraArgs, configuration.Name)); } } } } /// public override void GenerateCustomProjects(List projects) { base.GenerateCustomProjects(projects); // Generate custom project for Visual Studio project to debug native code on Android var androidProject = projects.FirstOrDefault(p => p.Configurations.Any(c => c.Platform == TargetPlatform.Android)); if (androidProject != null && AndroidSdk.Instance.IsValid && AndroidNdk.Instance.IsValid) { var platform = Platform.GetPlatform(TargetPlatform.Android); var rootProject = Globals.Project; var workspaceRoot = rootProject.ProjectFolderPath; var project = new AndroidProject { Generator = this, }; var targetsSorted = new List(androidProject.Targets); targetsSorted.Sort((a, b) => b.Platforms.Length.CompareTo(a.Platforms.Length)); var target = targetsSorted.First(x => x.Platforms.Contains(TargetPlatform.Android)); project.Type = TargetType.NativeCpp; project.Name = project.BaseName = "Android"; project.Targets = new Target[0]; project.SearchPaths = new string[0]; project.WorkspaceRootPath = rootProject.ProjectFolderPath; project.Path = Path.Combine(workspaceRoot, "Cache", "Projects", project.Name + ".androidproj"); project.SourceDirectories = new List(); project.Configurations = new List { new Project.ConfigurationData(target, androidProject, platform, TargetArchitecture.ARM64, TargetConfiguration.Debug), new Project.ConfigurationData(target, androidProject, platform, TargetArchitecture.ARM64, TargetConfiguration.Development), new Project.ConfigurationData(target, androidProject, platform, TargetArchitecture.ARM64, TargetConfiguration.Release), }; projects.Add(project); } } } }