Support for multiple IDE (Question to Epic devs)

Hi Epic dev team

Not sure where to talk about this so I thought to make a new thread here. I am working atm on a patch that supports multiple IDE on a platform. Actually it works already and I tested on GNU/Linux and Mac but should work on Windows too. I can select either XCode or CodeLite on Mac and on Linux and on Windows you may select VisualStudio and CodeLite or later whatever. I was thinking to make later a PR. Maybe you would be interested in because a lot of people asking for support of other IDE’s too. If not that is ok too :D.

What I mainly did is to put everything related to source code and IDE into the ISourceCodeAccessor interface and remove all hardcoded #if PLATFORM… stuff. So there are no

#if PLATFORM_WINDOWS
// Do stuff for VisualStudio
#elif PLATFORM_MAC
// Do stuff for XCode
#elif PLATFORM_LINUX
// Do stuff for …
#endif

The SourceCodeAccessor knows which IDE to use and which flags are needed for UBT to create the project files. The IDE in UE4 is opened in: (modified version of mine using CanAccessSolution with extended ISourceCodeAccessor)


bool GameProjectUtils::OpenCodeIDE(const FString& ProjectFile, FText& OutFailReason)
{
	if ( ProjectFile.IsEmpty() )
	{
		OutFailReason = LOCTEXT( "NoProjectFileSpecified", "You must specify a project file." );
		return false;
	}

	// Check whether this project is a foreign project. Don't use the cached project dictionary; we may have just created a new project.
	FString SolutionFolder;
	FString SolutionFilenameWithoutExtension;
	if( FUProjectDictionary(FPaths::RootDir()).IsForeignProject(ProjectFile) )
	{
		SolutionFolder = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FPaths::GetPath(ProjectFile));
		SolutionFilenameWithoutExtension = FPaths::GetBaseFilename(ProjectFile);
	}
	else
	{
		SolutionFolder = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FPaths::RootDir());
		SolutionFilenameWithoutExtension = TEXT("UE4");
	}

	ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked<ISourceCodeAccessModule>("SourceCodeAccess");

	// Open the solution with the default application
	FString FullPath;
	if ( SourceCodeAccessModule.GetAccessor().CanAccessSolution(SolutionFolder, SolutionFilenameWithoutExtension, FullPath) )
	{
		FPlatformProcess::LaunchFileInDefaultExternalApplication( *FullPath );
		return true;
	}

	FFormatNamedArguments Args;
	Args.Add( TEXT("Path"), FText::FromString( FullPath ) );
	OutFailReason = FText::Format( LOCTEXT( "OpenCodeIDE_MissingFile", "Could not edit the code editing IDE. {Path} could not be found." ), Args );
	return false;

}


I am just wondering if the FPlatformProcess::LaunchFileInDefaultExternalApplication( *FullPath ); has to be in the GameProjectUtils class. In short, is there any reason not to start the IDE within the accessor module? I think it would be better to put it into the Accessor.
I could then replace CanAccessSolution with something similar to GameProjectUtils::OpenCodeIDE, like ISourceCodeAccessor::OpenCodeIDE.
Hope you have some suggestions.

That all sounds reasonable, I don’t see a reason to make it any more complicated than that. e.g. Making it plugin based would be useless without mods to UBT anyway.

So you mean this: ISourceCodeAccessor::OpenCodeIDE souldn’t be used? So the code above code is enough?

Furthermore, I mentioned already that generating project and solutions files are decided using the accessor module too. Here an example (see the bold part):


bool FDesktopPlatformBase::GenerateProjectFiles(const FString& RootDir, const FString& ProjectFileName, FFeedbackContext* Warn)
{
	ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked<ISourceCodeAccessModule>("SourceCodeAccess");
	**FString Arguments = SourceCodeAccessModule.GetAccessor().GetProjectGeneratorFlag();**

	// Build the arguments to pass to UBT. If it's a non-foreign project, just build full project files.
	if ( !ProjectFileName.IsEmpty() && GetCachedProjectDictionary(RootDir).IsForeignProject(ProjectFileName) )
	{
		// Figure out whether it's a foreign project
		const FUProjectDictionary &ProjectDictionary = GetCachedProjectDictionary(RootDir);
		if(ProjectDictionary.IsForeignProject(ProjectFileName))
		{
			Arguments += FString::Printf(TEXT(" -project=\"%s\""), *IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ProjectFileName));

			// Always include game source
			Arguments += TEXT(" -game");

			// Determine whether or not to include engine source
			if(IsSourceDistribution(RootDir) && !FRocketSupport::IsRocket())
			{
				Arguments += TEXT(" -engine");
			}
			else
			{
				Arguments += TEXT(" -rocket");
			}
		}
	}
	Arguments += TEXT(" -progress");
...
...


So I made a second extension to ISourceCodeAcess::GetProjectGeneratorFlag that handles how UBT is going to create the project files. So if someone selects the XCodeSourceCodeAccessor it makes sense to use -xcodeprojectfile and in the case of CodeLiteSourceCodeAccessor -codelitefile etc. etc. Here an an example for XCode and CodeLite



// This is in XCode accesor
FString FXCodeSourceCodeAccessor::GetProjectGeneratorFlag() const
{
	return FString("-xcodeprojectfile");
}

// This is in CodeLite accessor.
FString FCodeLiteSourceCodeAccessor::GetProjectGeneratorFlag() const
{
	return FString("-codelitefile");
}



And those methods are used in the FDesktopPlatformBase::GenerateProjectFiles see above… How about that? That works actually quite well.

OP: Its an oversight for sure, but we do have ISourceCodeAccessor::OpenSolution, so this should probably be reworked to use that instead.

As for the project generation flags, it sounds like a reasonable idea to me. My separation-of-concerns spidey sense is tingling a little (source code access is supposed to do* just that *right now), but it should be OK.

Ideally we would probably want some plugin-based game project generation module as Nick D mentioned, but that would almost certainly require UBT changes too.

Hi Epic team

Can you have a look at https://github.com/EpicGames/UnrealEngine/pull/1528