// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { class QMakefileProjectFile : ProjectFile { public QMakefileProjectFile(FileReference InitFilePath, DirectoryReference BaseDir) : base(InitFilePath, BaseDir) { } } /// /// QMakefile project file generator implementation /// class QMakefileGenerator : ProjectFileGenerator { /// Default constructor public QMakefileGenerator(FileReference? InOnlyGameProject) : base(InOnlyGameProject) { } /// File extension for project files we'll be generating (e.g. ".vcxproj") public override string ProjectFileExtension => ".pro"; protected override bool WritePrimaryProjectFile(ProjectFile? UBTProject, PlatformProjectGeneratorCollection PlatformProjectGenerators, ILogger Logger) { bool bSuccess = true; return bSuccess; } /// /// Splits the definition text into macro name and value (if any). /// /// Definition text /// Out: The definition name /// Out: The definition value or null if it has none /// Pair representing macro name and value. private void SplitDefinitionAndValue(string Definition, out string Key, out string Value) { int EqualsIndex = Definition.IndexOf('='); if (EqualsIndex >= 0) { Key = Definition.Substring(0, EqualsIndex); Value = Definition.Substring(EqualsIndex + 1); } else { Key = Definition; Value = ""; } } /// Adds the include directory to the list, after converting it to relative to Unreal root private void AddIncludeDirectory(ref List IncludeDirectories, string IncludeDir, string ProjectDir) { string FullProjectPath = ProjectFileGenerator.PrimaryProjectPath.FullName; string FullPath = ""; if (IncludeDir.StartsWith("/") && !IncludeDir.StartsWith(FullProjectPath)) { // Full path to a fulder outside of project FullPath = IncludeDir; } else { FullPath = Path.GetFullPath(Path.Combine(ProjectDir, IncludeDir)); FullPath = Utils.MakePathRelativeTo(FullPath, FullProjectPath); FullPath = FullPath.TrimEnd('/'); } IncludeDirectories.Add(FullPath); } // For later when moving all this back to MakefileGenerator.cs private void AddDefines(ref List DefinesContent, string define, string value) { } private bool WriteQMakePro(ILogger Logger) { // Some more stuff borrowed from Mac side of things. List IncludeDirectories = new List(); List SystemIncludeDirectories = new List(); List DefinesAndValues = new List(); HashSet DefinesOnly = new HashSet(); // DefineList.Add (""); string QMakeIncludesFileName = PrimaryProjectName + "Includes.pri"; StringBuilder QMakeIncludesPriFileContent = new StringBuilder(); string QMakeDefinesFileName = PrimaryProjectName + "Defines.pri"; StringBuilder QMakeDefinesPriFileContent = new StringBuilder(); string GameProjectPath = ""; string GameProjectFile = ""; string GameProjectRootPath = ""; string BuildCommand = ""; string QMakeGameProjectFile = ""; string QMakeFilesDirectory = ".qmake"; char Seperator = Path.DirectorySeparatorChar; string CurrentPlatform = ""; string Console = ""; if (OperatingSystem.IsWindows()) { CurrentPlatform = "Win64"; } else if (OperatingSystem.IsLinux()) { CurrentPlatform = "Linux"; Console = "bash"; } else if (OperatingSystem.IsMacOS()) { CurrentPlatform = "Mac"; Console = "bash"; } foreach (ProjectFile CurProject in GeneratedProjectFiles) { QMakefileProjectFile? QMakeProject = CurProject as QMakefileProjectFile; if (QMakeProject == null) { Logger.LogInformation("QMakeProject == null"); continue; } foreach (string CurPath in QMakeProject.IntelliSenseIncludeSearchPaths) { AddIncludeDirectory(ref IncludeDirectories, CurPath, Path.GetDirectoryName(QMakeProject.ProjectFilePath.FullName)!); // System.Console.WriteLine ("Not empty now? CurPath == ", CurPath); } foreach (string CurPath in QMakeProject.IntelliSenseSystemIncludeSearchPaths) { AddIncludeDirectory(ref SystemIncludeDirectories, CurPath, Path.GetDirectoryName(QMakeProject.ProjectFilePath.FullName)!); } } // Remove duplicate paths from include dir and system include dir list IncludeDirectories = IncludeDirectories.Distinct().ToList(); SystemIncludeDirectories = SystemIncludeDirectories.Distinct().ToList(); // Iterate through all the defines for the projects that are generated by // UnrealBuildTool // !RAKE: move to seperate function QMakeDefinesPriFileContent.Append("DEFINES += \\\n"); foreach (ProjectFile CurProject in GeneratedProjectFiles) { QMakefileProjectFile? QMakeProject = CurProject as QMakefileProjectFile; if (QMakeProject == null) { Logger.LogInformation("QMakeProject == null"); continue; } foreach (string CurDefine in QMakeProject.IntelliSensePreprocessorDefinitions) { string define = ""; string value = ""; SplitDefinitionAndValue(CurDefine, out define, out value); if (!DefinesOnly.Contains(define)) { // Logger.LogInformation (CurDefine); if (String.IsNullOrEmpty(value)) { DefinesAndValues.Add("\t"); DefinesAndValues.Add(String.Format("{0}=", define)); DefinesAndValues.Add(" \\\n"); } else { // Escape special characters // If the value contains spaces, we must put the whole value inside quotes value = value.Replace("\"", "\\\\\\\""); bool hasSpaces = false; if (value.Contains(' ')) { value = value.Replace(" ", "\\ "); hasSpaces = true; } value = value.Replace("{", "\\{"); value = value.Replace("}", "\\}"); DefinesAndValues.Add("\t"); DefinesAndValues.Add(define); DefinesAndValues.Add("="); if (hasSpaces) { DefinesAndValues.Add("\""); } DefinesAndValues.Add(value); if (hasSpaces) { DefinesAndValues.Add("\""); } DefinesAndValues.Add(" \\\n"); } DefinesOnly.Add(define); } } } foreach (string Def in DefinesAndValues) { QMakeDefinesPriFileContent.Append(Def); } // Iterate through all the include paths that // UnrealBuildTool generates // !RAKE: Move to seperate function QMakeIncludesPriFileContent.Append("INCLUDEPATH += \\\n"); foreach (string CurPath in IncludeDirectories) { QMakeIncludesPriFileContent.Append('\t'); QMakeIncludesPriFileContent.Append(CurPath); QMakeIncludesPriFileContent.Append(" \\\n"); } foreach (string CurPath in SystemIncludeDirectories) { QMakeIncludesPriFileContent.Append('\t'); QMakeIncludesPriFileContent.Append(CurPath); QMakeIncludesPriFileContent.Append(" \\\n"); } QMakeIncludesPriFileContent.Append('\n'); if (!String.IsNullOrEmpty(GameProjectName)) { GameProjectPath = OnlyGameProject!.Directory.FullName; GameProjectFile = OnlyGameProject.FullName; QMakeGameProjectFile = "gameProjectFile=" + GameProjectFile + "\n"; BuildCommand = $"build={Console} $$unrealRootPath{Seperator}Engine{Seperator}{Unreal.RelativeDotnetDirectory}{Seperator}dotnet $$unrealRootPath{Seperator}Engine{Seperator}Binaries{Seperator}DotNET{Seperator}UnrealBuildTool{Seperator}UnrealBuildTool.dll\n\n"; } else { BuildCommand = String.Format("build=bash $$unrealRootPath{0}Engine{0}Build{0}BatchFiles{0}{1}{0}Build.sh\n", Seperator, CurrentPlatform); } string UnrealRootPath = Unreal.RootDirectory.FullName; string FileName = PrimaryProjectName + ".pro"; string QMakeSourcePriFileName = PrimaryProjectName + "Source.pri"; string QMakeHeaderPriFileName = PrimaryProjectName + "Header.pri"; string QMakeConfigPriFileName = PrimaryProjectName + "Config.pri"; StringBuilder QMakeFileContent = new StringBuilder(); StringBuilder QMakeSourcePriFileContent = new StringBuilder(); StringBuilder QMakeHeaderPriFileContent = new StringBuilder(); StringBuilder QMakeConfigPriFileContent = new StringBuilder(); string QMakeSectionEnd = " \n\n"; StringBuilder QMakeSourceFilesListBuilder = new StringBuilder("SOURCES += \\ \n"); StringBuilder QMakeHeaderFilesListBuilder = new StringBuilder("HEADERS += \\ \n"); StringBuilder QMakeConfigFilesListBuilder = new StringBuilder("OTHER_FILES += \\ \n"); string QMakeTargetList = "QMAKE_EXTRA_TARGETS += \\ \n"; if (!String.IsNullOrEmpty(GameProjectName)) { GameProjectRootPath = GameProjectName + "RootPath=" + GameProjectPath + "\n\n"; } QMakeFileContent.Append( "# UnrealEngine.pro generated by QMakefileGenerator.cs\n" + "# *DO NOT EDIT*\n\n" + "TEMPLATE = aux\n" + "CONFIG += c++14\n" + "CONFIG -= console\n" + "CONFIG -= app_bundle\n" + "CONFIG -= qt\n\n" + "TARGET = UE5 \n\n" + "unrealRootPath=" + UnrealRootPath + "\n" + GameProjectRootPath + QMakeGameProjectFile + BuildCommand + "args=$(ARGS)\n\n" + "include(" + QMakeFilesDirectory + Seperator + QMakeSourcePriFileName + ")\n" + "include(" + QMakeFilesDirectory + Seperator + QMakeHeaderPriFileName + ")\n" + "include(" + QMakeFilesDirectory + Seperator + QMakeConfigPriFileName + ")\n" + "include(" + QMakeFilesDirectory + Seperator + QMakeIncludesFileName + ")\n" + "include(" + QMakeFilesDirectory + Seperator + QMakeDefinesFileName + ")\n\n" ); // Create SourceFiles, HeaderFiles, and ConfigFiles sections. List AllModuleFiles = DiscoverModules(FindGameProjects(Logger), null); foreach (FileReference CurModuleFile in AllModuleFiles) { List FoundFiles = SourceFileSearch.FindModuleSourceFiles(CurModuleFile); foreach (FileReference CurSourceFile in FoundFiles) { string SourceFileRelativeToRoot = CurSourceFile.MakeRelativeTo(Unreal.EngineDirectory); // Exclude some directories that we don't compile (note that we still want Windows/Mac etc for code navigation) if (!SourceFileRelativeToRoot.Contains($"Source{Seperator}ThirdParty{Seperator}")) { if (SourceFileRelativeToRoot.EndsWith(".cpp")) { if (!SourceFileRelativeToRoot.StartsWith("..") && !Path.IsPathRooted(SourceFileRelativeToRoot)) { QMakeSourceFilesListBuilder.Append("\t\"" + $"$$unrealRootPath{Seperator}Engine{Seperator}" + SourceFileRelativeToRoot + "\" \\\n"); } else { if (String.IsNullOrEmpty(GameProjectName)) { QMakeSourceFilesListBuilder.Append("\t\"" + SourceFileRelativeToRoot.Substring(3) + "\" \\\n"); } else { QMakeSourceFilesListBuilder.Append("\t\"$$" + GameProjectName + $"RootPath{Seperator}" + Utils.MakePathRelativeTo(CurSourceFile.FullName, GameProjectPath) + "\" \\\n"); } } } if (SourceFileRelativeToRoot.EndsWith(".h")) { if (!SourceFileRelativeToRoot.StartsWith("..") && !Path.IsPathRooted(SourceFileRelativeToRoot)) { // SourceFileRelativeToRoot = "Engine/" + SourceFileRelativeToRoot; QMakeHeaderFilesListBuilder.Append("\t\"" + $"$$unrealRootPath{Seperator}Engine{Seperator}" + SourceFileRelativeToRoot + "\" \\\n"); } else { if (String.IsNullOrEmpty(GameProjectName)) { // SourceFileRelativeToRoot = SourceFileRelativeToRoot.Substring (3); QMakeHeaderFilesListBuilder.Append("\t\"" + SourceFileRelativeToRoot.Substring(3) + "\" \\\n"); } else { QMakeHeaderFilesListBuilder.Append("\t\"$$" + GameProjectName + $"RootPath{Seperator}" + Utils.MakePathRelativeTo(CurSourceFile.FullName, GameProjectPath) + "\" \\\n"); } } } if (SourceFileRelativeToRoot.EndsWith(".cs")) { if (!SourceFileRelativeToRoot.StartsWith("..") && !Path.IsPathRooted(SourceFileRelativeToRoot)) { // SourceFileRelativeToRoot = "Engine/" + SourceFileRelativeToRoot; QMakeConfigFilesListBuilder.Append("\t\"" + $"$$unrealRootPath{Seperator}Engine{Seperator}" + SourceFileRelativeToRoot + "\" \\\n"); } else { if (String.IsNullOrEmpty(GameProjectName)) { // SourceFileRelativeToRoot = SourceFileRelativeToRoot.Substring (3); QMakeConfigFilesListBuilder.Append("\t\"" + SourceFileRelativeToRoot.Substring(3) + "\" \\\n"); } else { QMakeConfigFilesListBuilder.Append("\t\"$$" + GameProjectName + $"RootPath{Seperator}" + Utils.MakePathRelativeTo(CurSourceFile.FullName, GameProjectPath) + "\" \\\n"); } } } } } } // Add section end to section strings; QMakeSourceFilesListBuilder.Append(QMakeSectionEnd); QMakeHeaderFilesListBuilder.Append(QMakeSectionEnd); QMakeConfigFilesListBuilder.Append(QMakeSectionEnd); // Append sections to the QMakeLists.txt file QMakeSourcePriFileContent.Append(QMakeSourceFilesListBuilder); QMakeHeaderPriFileContent.Append(QMakeHeaderFilesListBuilder); QMakeConfigPriFileContent.Append(QMakeConfigFilesListBuilder); string QMakeProjectCmdArg = ""; foreach (ProjectFile Project in GeneratedProjectFiles) { foreach (ProjectTarget TargetFile in Project.ProjectTargets.OfType()) { if (TargetFile.TargetFilePath == null) { continue; } string TargetName = TargetFile.TargetFilePath.GetFileNameWithoutAnyExtensions(); // Remove both ".cs" and ". foreach (UnrealTargetConfiguration CurConfiguration in (UnrealTargetConfiguration[])Enum.GetValues(typeof(UnrealTargetConfiguration))) { if (CurConfiguration != UnrealTargetConfiguration.Unknown && CurConfiguration != UnrealTargetConfiguration.Development) { if (InstalledPlatformInfo.IsValidConfiguration(CurConfiguration, EProjectType.Code)) { if (TargetName == GameProjectName || TargetName == (GameProjectName + "Editor")) { QMakeProjectCmdArg = " -project=\"\\\"$$gameProjectFile\\\"\""; } string ConfName = Enum.GetName(typeof(UnrealTargetConfiguration), CurConfiguration)!; QMakeFileContent.Append(String.Format("{0}-{3}-{1}.commands = $$build {0} {3} {1} {2} $$args\n", TargetName, ConfName, QMakeProjectCmdArg, CurrentPlatform)); QMakeTargetList += "\t" + TargetName + "-" + CurrentPlatform + "-" + ConfName + " \\\n"; // , TargetName, ConfName); } } } if (TargetName == GameProjectName || TargetName == (GameProjectName + "Editor")) { QMakeProjectCmdArg = " -project=\"\\\"$$gameProjectFile\\\"\""; } QMakeFileContent.Append(String.Format("{0}.commands = $$build {0} {2} Development {1} $$args\n\n", TargetName, QMakeProjectCmdArg, CurrentPlatform)); QMakeTargetList += "\t" + TargetName + " \\\n"; } } QMakeFileContent.Append(QMakeTargetList.TrimEnd('\\')); string FullFileName = Path.Combine(PrimaryProjectPath.FullName, FileName); string QMakeFilePath = Path.Combine(ProjectFileGenerator.PrimaryProjectPath.FullName, QMakeFilesDirectory); string FullQMakeDefinesFileName = Path.Combine(QMakeFilePath, QMakeDefinesFileName); string FullQMakeIncludesFileName = Path.Combine(QMakeFilePath, QMakeIncludesFileName); string FullQMakeSourcePriFileName = Path.Combine(QMakeFilePath, QMakeSourcePriFileName); string FullQMakeHeaderPriFileName = Path.Combine(QMakeFilePath, QMakeHeaderPriFileName); string FullQMakeConfigPriFileName = Path.Combine(QMakeFilePath, QMakeConfigPriFileName); WriteFileIfChanged(FullQMakeDefinesFileName, QMakeDefinesPriFileContent.ToString(), Logger); WriteFileIfChanged(FullQMakeIncludesFileName, QMakeIncludesPriFileContent.ToString(), Logger); WriteFileIfChanged(FullQMakeSourcePriFileName, QMakeSourcePriFileContent.ToString(), Logger); WriteFileIfChanged(FullQMakeHeaderPriFileName, QMakeHeaderPriFileContent.ToString(), Logger); WriteFileIfChanged(FullQMakeConfigPriFileName, QMakeConfigPriFileContent.ToString(), Logger); return WriteFileIfChanged(FullFileName, QMakeFileContent.ToString(), Logger); } /// ProjectFileGenerator interface //protected override bool WritePrimaryProjectFile( ProjectFile UBTProject ) protected override bool WriteProjectFiles(PlatformProjectGeneratorCollection PlatformProjectGenerators, ILogger Logger) { return WriteQMakePro(Logger); } /// ProjectFileGenerator interface /// /// Allocates a generator-specific project file object /// /// Path to the project file /// The base directory for files within this project /// The newly allocated project file object protected override ProjectFile AllocateProjectFile(FileReference InitFilePath, DirectoryReference BaseDir) { return new QMakefileProjectFile(InitFilePath, BaseDir); } /// ProjectFileGenerator interface public override void CleanProjectFiles(DirectoryReference InPrimaryProjectDirectory, string InPrimaryProjectName, DirectoryReference InIntermediateProjectFilesDirectory, ILogger Logger) { } } }