Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs
henrik karlsson d50cfaab05 [UnrealBuildTool]
* Fixed so .uproject added on commandline is fixed up with correct casing to make sure it generate the same solution as when discovering uproject file when not provided

Fixes so UGS, UnrealVs.RefreshProjects and GenerateProjectFiles.bat all create the same thing

#preflight 62f6b700f3107c023c182236
#rb Joe.Kirchoff

#ROBOMERGE-AUTHOR: henrik.karlsson
#ROBOMERGE-SOURCE: CL 21365731 via CL 21387079 via CL 21389956 via CL 21390645
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v975-21357124)

[CL 21392587 by henrik karlsson in ue5-main branch]
2022-08-15 15:51:18 -04:00

317 lines
13 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using EpicGames.Core;
using System.Diagnostics;
using UnrealBuildBase;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
namespace UnrealBuildTool
{
/// <summary>
/// Generates project files for one or more projects
/// </summary>
[ToolMode("GenerateProjectFiles", ToolModeOptions.XmlConfig | ToolModeOptions.BuildPlatforms | ToolModeOptions.SingleInstance | ToolModeOptions.UseStartupTraceListener)]
class GenerateProjectFilesMode : ToolMode
{
/// <summary>
/// Types of project files to generate
/// </summary>
[CommandLine("-ProjectFileFormat")]
[CommandLine("-2019", Value = nameof(ProjectFileFormat.VisualStudio2019))] // + override compiler
[CommandLine("-2022", Value = nameof(ProjectFileFormat.VisualStudio2022))] // + override compiler
[CommandLine("-Makefile", Value = nameof(ProjectFileFormat.Make))]
[CommandLine("-CMakefile", Value = nameof(ProjectFileFormat.CMake))]
[CommandLine("-QMakefile", Value = nameof(ProjectFileFormat.QMake))]
[CommandLine("-KDevelopfile", Value = nameof(ProjectFileFormat.KDevelop))]
[CommandLine("-CodeLiteFiles", Value = nameof(ProjectFileFormat.CodeLite))]
[CommandLine("-XCodeProjectFiles", Value = nameof(ProjectFileFormat.XCode))]
[CommandLine("-EddieProjectFiles", Value = nameof(ProjectFileFormat.Eddie))]
[CommandLine("-VSCode", Value = nameof(ProjectFileFormat.VisualStudioCode))]
[CommandLine("-VSMac", Value = nameof(ProjectFileFormat.VisualStudioMac))]
[CommandLine("-CLion", Value = nameof(ProjectFileFormat.CLion))]
[CommandLine("-Rider", Value = nameof(ProjectFileFormat.Rider))]
#if __VPROJECT_AVAILABLE__
[CommandLine("-VProject", Value = nameof(ProjectFileFormat.VProject))]
#endif
HashSet<ProjectFileFormat> ProjectFileFormats = new HashSet<ProjectFileFormat>();
/// <summary>
/// Disable native project file generators for platforms. Platforms with native project file generators typically require IDE extensions to be installed.
/// </summary>
[XmlConfigFile(Category = "ProjectFileGenerator")]
string[]? DisablePlatformProjectGenerators = null;
/// <summary>
/// Whether this command is being run in an automated mode
/// </summary>
[CommandLine("-Automated")]
bool bAutomated = false;
/// <summary>
/// Execute the tool mode
/// </summary>
/// <param name="Arguments">Command line arguments</param>
/// <returns>Exit code</returns>
/// <param name="Logger"></param>
public override int Execute(CommandLineArguments Arguments, ILogger Logger)
{
// Apply any command line arguments to this class
Arguments.ApplyTo(this);
// Apply the XML config to this class
XmlConfig.ApplyTo(this);
// set up logging (taken from BuildMode)
FileReference LogFile = FileReference.Combine(UnrealBuildTool.EngineProgramSavedDirectory, "UnrealBuildTool", "Log_GPF.txt");
Log.AddFileWriter("DefaultLogTraceListener", LogFile);
// Parse rocket-specific arguments.
FileReference? ProjectFile;
TryParseProjectFileArgument(Arguments, Logger, out ProjectFile);
// Warn if there are explicit project file formats specified
if (ProjectFileFormats.Count > 0 && !bAutomated)
{
StringBuilder Configuration = new StringBuilder();
Configuration.Append("Project file formats specified via the command line will be ignored when generating\n");
Configuration.Append("project files from the editor and other engine tools.\n");
Configuration.Append("\n");
Configuration.Append("Consider setting your desired IDE from the editor preferences window, or modify your\n");
Configuration.Append("BuildConfiguration.xml file with:\n");
Configuration.Append("\n");
Configuration.Append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
Configuration.Append("<Configuration xmlns=\"https://www.unrealengine.com/BuildConfiguration\">\n");
Configuration.Append(" <ProjectFileGenerator>\n");
foreach(ProjectFileFormat ProjectFileFormat in ProjectFileFormats)
{
Configuration.AppendFormat(" <Format>{0}</Format>\n", ProjectFileFormat);
}
Configuration.Append(" </ProjectFileGenerator>\n");
Configuration.Append("</Configuration>\n");
Logger.LogWarning("{Configuration}", Configuration.ToString());
}
// If there aren't any formats set, read the default project file format from the config file
if (ProjectFileFormats.Count == 0)
{
// Read from the XML config
if (!String.IsNullOrEmpty(ProjectFileGeneratorSettings.Format))
{
ProjectFileFormats.UnionWith(ProjectFileGeneratorSettings.ParseFormatList(ProjectFileGeneratorSettings.Format, Logger));
}
// Read from the editor config
ProjectFileFormat PreferredSourceCodeAccessor;
if (ProjectFileGenerator.GetPreferredSourceCodeAccessor(ProjectFile, out PreferredSourceCodeAccessor))
{
ProjectFileFormats.Add(PreferredSourceCodeAccessor);
}
// If there's still nothing set, get the default project file format for this platform
if (ProjectFileFormats.Count == 0)
{
ProjectFileFormats.UnionWith(BuildHostPlatform.Current.GetDefaultProjectFileFormats());
}
}
// Register all the platform project generators
PlatformProjectGeneratorCollection PlatformProjectGenerators = new PlatformProjectGeneratorCollection();
foreach (Type CheckType in Assembly.GetExecutingAssembly().GetTypes())
{
if (CheckType.IsClass && !CheckType.IsAbstract && CheckType.IsSubclassOf(typeof(PlatformProjectGenerator)))
{
PlatformProjectGenerator Generator = (PlatformProjectGenerator)Activator.CreateInstance(CheckType, Arguments, Logger)!;
foreach(UnrealTargetPlatform Platform in Generator.GetPlatforms())
{
if(DisablePlatformProjectGenerators == null || !DisablePlatformProjectGenerators.Any(x => x.Equals(Platform.ToString(), StringComparison.OrdinalIgnoreCase)))
{
Logger.LogDebug("Registering project generator {CheckType} for {Platform}", CheckType, Platform);
PlatformProjectGenerators.RegisterPlatformProjectGenerator(Platform, Generator, Logger);
}
}
}
}
// print out any errors to the log
List<string> BadPlatformNames = new List<string>();
Logger.LogDebug("\n--- SDK INFO START ---");
foreach (string PlatformName in UnrealTargetPlatform.GetValidPlatformNames())
{
UEBuildPlatformSDK? SDK = UEBuildPlatformSDK.GetSDKForPlatform(PlatformName);
if (SDK != null && SDK.bIsSdkAllowedOnHost)
{
// print out the info to the log, and if it's invalid, remember it
SDKStatus Validity = SDK.PrintSDKInfoAndReturnValidity(LogEventType.Verbose, LogFormatOptions.NoConsoleOutput, LogEventType.Warning, LogFormatOptions.NoConsoleOutput);
if (Validity == SDKStatus.Invalid)
{
BadPlatformNames.Add(PlatformName);
}
}
}
if (BadPlatformNames.Count > 0)
{
Log.TraceInformationOnce("\nSome Platforms were skipped due to invalid SDK setup: {0}.\nSee the log file for detailed information\n\n", string.Join(", ", BadPlatformNames));
Logger.LogInformation("");
}
Logger.LogDebug("--- SDK INFO END ---");
Logger.LogDebug("");
// Create each project generator and run it
List<ProjectFileGenerator> Generators = new List<ProjectFileGenerator>();
foreach (ProjectFileFormat ProjectFileFormat in ProjectFileFormats.Distinct())
{
ProjectFileGenerator Generator;
switch (ProjectFileFormat)
{
case ProjectFileFormat.Make:
Generator = new MakefileGenerator(ProjectFile);
break;
case ProjectFileFormat.CMake:
Generator = new CMakefileGenerator(ProjectFile);
break;
case ProjectFileFormat.QMake:
Generator = new QMakefileGenerator(ProjectFile);
break;
case ProjectFileFormat.KDevelop:
Generator = new KDevelopGenerator(ProjectFile);
break;
case ProjectFileFormat.CodeLite:
Generator = new CodeLiteGenerator(ProjectFile, Arguments);
break;
case ProjectFileFormat.VisualStudio:
Generator = new VCProjectFileGenerator(ProjectFile, VCProjectFileFormat.Default, Arguments);
break;
case ProjectFileFormat.VisualStudio2019:
Generator = new VCProjectFileGenerator(ProjectFile, VCProjectFileFormat.VisualStudio2019, Arguments);
break;
case ProjectFileFormat.VisualStudio2022:
Generator = new VCProjectFileGenerator(ProjectFile, VCProjectFileFormat.VisualStudio2022, Arguments);
break;
case ProjectFileFormat.XCode:
Generator = new XcodeProjectFileGenerator(ProjectFile, Arguments);
break;
case ProjectFileFormat.Eddie:
Generator = new EddieProjectFileGenerator(ProjectFile);
break;
case ProjectFileFormat.VisualStudioCode:
Generator = new VSCodeProjectFileGenerator(ProjectFile);
break;
case ProjectFileFormat.CLion:
Generator = new CLionGenerator(ProjectFile);
break;
case ProjectFileFormat.VisualStudioMac:
Generator = new VCMacProjectFileGenerator(ProjectFile, Arguments);
break;
case ProjectFileFormat.Rider:
Generator = new RiderProjectFileGenerator(ProjectFile, Arguments);
break;
#if __VPROJECT_AVAILABLE__
case ProjectFileFormat.VProject:
Generator = new VProjectFileGenerator(ProjectFile);
break;
#endif
default:
throw new BuildException("Unhandled project file type '{0}", ProjectFileFormat);
}
Generators.Add(Generator);
}
// Check there are no superfluous command line arguments
// TODO (still pass raw arguments below)
// Arguments.CheckAllArgumentsUsed();
// Now generate project files
ProjectFileGenerator.bGenerateProjectFiles = true;
foreach(ProjectFileGenerator Generator in Generators)
{
ProjectFileGenerator.Current = Generator;
bool bGenerateSuccess = Generator.GenerateProjectFiles(PlatformProjectGenerators, Arguments.GetRawArray(), Logger);
ProjectFileGenerator.Current = null;
if (!bGenerateSuccess)
{
return (int)CompilationResult.OtherCompilationError;
}
}
return (int)CompilationResult.Succeeded;
}
/// <summary>
/// Try to parse the project file from the command line
/// </summary>
/// <param name="Arguments">The command line arguments</param>
/// <param name="Logger">Logger for output</param>
/// <param name="ProjectFile">The project file that was parsed</param>
/// <returns>True if the project file was parsed, false otherwise</returns>
private static bool TryParseProjectFileArgument(CommandLineArguments Arguments, ILogger Logger, [NotNullWhen(true)] out FileReference? ProjectFile)
{
string? CandidateProjectPath = null;
// look for -project=<path>, if it does not exist check arguments for anything that has .uproject in it
if (!Arguments.TryGetValue("-Project=", out CandidateProjectPath))
{
// Go through the argument list and try to match poorly (or well..) formed arguments like
// EngineTest, EngineTest.uproject
// Collaboration/FooProject
// by checking for those in the native project list
for (int Idx = 0; Idx < Arguments.Count; Idx++)
{
if (Arguments[Idx][0] != '-' && Arguments[Idx].EndsWith(".uproject", StringComparison.OrdinalIgnoreCase))
{
CandidateProjectPath = Arguments[Idx];
Arguments.MarkAsUsed(Idx);
break;
}
}
}
// We have a project file either via -project= or because there was something called .uproject in the arg list
// so now validate it
if (!string.IsNullOrEmpty(CandidateProjectPath))
{
FileReference? CandidateProjectFile = FileReference.FindCorrectCase(new FileReference(CandidateProjectPath));
// if the path doesn't exist then check native paths (ueprojectdirs)
if (!FileReference.Exists(CandidateProjectFile))
{
// clean everything the user provided to just the name and make sure it has the expected extension
string ProjectName = CandidateProjectFile.ChangeExtension("uproject").GetFileName();
// check native project paths (uprojectdirs)
IEnumerable<FileReference> NativeProjectFiles = NativeProjects.EnumerateProjectFiles(Logger);
CandidateProjectFile = NativeProjectFiles.Where(F => F.GetFileName().Equals(ProjectName, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
}
if (CandidateProjectFile == null || !FileReference.Exists(CandidateProjectFile))
{
// if we didn't find anything then throw an error as the user explicitly provided a uproject
throw new Exception(string.Format("Unable to find project file based on argument {0}", CandidateProjectPath));
}
Logger.LogDebug("Resolved project argument {CandidateProjectPath} to {CandidateProjectFile}", CandidateProjectPath, CandidateProjectFile);
ProjectFile = CandidateProjectFile;
return true;
}
FileReference? InstalledProjectFile = UnrealBuildTool.GetInstalledProjectFile();
if(InstalledProjectFile != null)
{
ProjectFile = InstalledProjectFile;
return true;
}
ProjectFile = null;
return false;
}
}
}