Setting up Unreal Engine 4 CMake environment for CLion

This time we’re going to set up our system for later use. I must say that hardware requirements are a bit higher than stated on Unreal Engine website. From my personal point of view I’d recommend having Mac with 16Gb RAM, Core i7 CPU, discrete GPU and SSD drive.

First thing you should know is that you have to build Unreal Engine from source if you want to use any IDE other than Xcode on your Mac, in my case CLion, because CMake file generator is Linux-only and is yet not perfect. Epic team says they have CMake support in development. After registering on UE website you have to follow tutorial and retrieve sources for UE4.

See tutorial for building UE on Mac here.

Let’s try to create simple empty C++ project with name ‘Game’. Wait for project files to be generated. The next step is to review project folder. In project root you will see two project files: `Game.uproject` and `Game.xcodeproject`. But in order to open your game in CLion you need `CMakeLists.txt` file.

You can rebuild project files manually using this command:

cd /path/to/UE4/Engine/Build/BatchFiles/Mac
./GenerateProjectFiles.sh -cmakefile -game -project “/path/to/Game.uproject"

If you look into generated `CMakeLists.txt` you’ll notice that source folders refer to Linux code and build targets also have Linux architecture.

Now, let’s change UnrealBuildTool a bit. Open project at `${UE4_ROOT}/Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool_Mono.csproj` and find `System/CMakefileGenerator.cs`. After exploring module source we see the root of the problem.

We’ll have to determine host architecture and generate `CMakeLists.txt` file according to it.

Add

bool isMac = BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac;
bool isLinux = BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux;
String HostArchitecture = null;
if (isLinux) HostArchitecture = "Linux";
if (isMac) HostArchitecture = "Mac";

before `if (!String.IsNullOrEmpty (GameProjectName))` in `WriteCMakeLists()` method.

Then replace all “Linux” string entries with “{}” and add HostArchitecture as argument to `String.Format` calls.

Next, move directory filter to separate functions:

private bool IsLinuxFiltered( String SourceFileRelativeToRoot ) {
  return
    !SourceFileRelativeToRoot.Contains ("Source/ThirdParty/") &&
    !SourceFileRelativeToRoot.Contains ("/Windows/") &&
    !SourceFileRelativeToRoot.Contains ("/Mac/") &&
    !SourceFileRelativeToRoot.Contains ("/IOS/") &&

    !SourceFileRelativeToRoot.Contains ("/iOS/") &&

    !SourceFileRelativeToRoot.Contains ("/VisualStudioSourceCodeAccess/") &&

    !SourceFileRelativeToRoot.Contains ("/XCodeSourceCodeAccess/") &&

    !SourceFileRelativeToRoot.Contains ("/WmfMedia/") &&

    !SourceFileRelativeToRoot.Contains ("/IOSDeviceProfileSelector/") &&

    !SourceFileRelativeToRoot.Contains ("/WindowsDeviceProfileSelector/") &&

    !SourceFileRelativeToRoot.Contains ("/WindowsMoviePlayer/") &&

    !SourceFileRelativeToRoot.Contains ("/AppleMoviePlayer/") &&

    !SourceFileRelativeToRoot.Contains ("/MacGraphicsSwitching/") &&

    !SourceFileRelativeToRoot.Contains ("/Apple/") &&

    !SourceFileRelativeToRoot.Contains ("/WinRT/");

}


private bool IsMacFiltered( String SourceFileRelativeToRoot ) {

  return

    !SourceFileRelativeToRoot.Contains ("Source/ThirdParty/") &&

    !SourceFileRelativeToRoot.Contains ("/Windows/") &&

    !SourceFileRelativeToRoot.Contains ("/Linux/") &&

    !SourceFileRelativeToRoot.Contains ("/VisualStudioSourceCodeAccess/") &&

    !SourceFileRelativeToRoot.Contains ("/WmfMedia/") &&

    !SourceFileRelativeToRoot.Contains ("/WindowsDeviceProfileSelector/") &&

    !SourceFileRelativeToRoot.Contains ("/WindowsMoviePlayer/") &&

    !SourceFileRelativeToRoot.Contains ("/WinRT/");
}



And then replace condition on line 109 with

if ((isLinux && IsLinuxFiltered(SourceFileRelativeToRoot)) || (isMac && IsMacFiltered(SourceFileRelativeToRoot)))

Second problem is that there are no include directories, Let’s fix it. Add

private string GetIncludeDirectory(string IncludeDir, string ProjectDir)
{
  string FullProjectPath = Path.GetFullPath(ProjectFileGenerator.MasterProjectRelativePath);
  string FullPath = "";
  if (IncludeDir.StartsWith("/") && !IncludeDir.StartsWith(FullProjectPath))
  {
    // Full path to a folder outside of project
    FullPath = IncludeDir;
  }
  else
  {
    FullPath = Path.GetFullPath(Path.Combine(ProjectDir, IncludeDir));
    FullPath = Utils.MakePathRelativeTo(FullPath, FullProjectPath);
    FullPath = FullPath.TrimEnd('/');
  }
  return FullPath;
}

Finally, print include directories to CMake file.

var IncludeDirectoriesList = "include_directories( \n";
List<String> IncludeDirectories = new List<String>();
foreach (var CurProject in GeneratedProjectFiles) {
  foreach (var CurPath in CurProject.IntelliSenseIncludeSearchPaths) {
    string IncludeDirectory = GetIncludeDirectory(CurPath, Path.GetDirectoryName(CurProject.ProjectFilePath));
    if (IncludeDirectory != null && !IncludeDirectories.Contains (IncludeDirectory)) 
    {
      IncludeDirectories.Add(IncludeDirectory);
    }
  }
}
foreach (string IncludeDirectory in IncludeDirectories) 
{
  IncludeDirectoriesList += ("\t\"" + IncludeDirectory + "\"\n");
}
IncludeDirectoriesList += CMakeSectionEnd;
CMakefileContent.Append (IncludeDirectoriesList);

After all code changes rebuild UnrealBuildProject and run `GenerageProjectFiles.sh` again. You should now have valid CMake file for your Mac, but there if you open the project in CLion and wait for project to parse, you’ll see that most macros are red. Is’s necessary to add preprocessor definitions to CMakeLists.txt file. This is done in the same way as include directories are added.

List<String> PreprocessorDefinitions = new List<String>();
foreach (var CurProject in GeneratedProjectFiles) {
  foreach (var CurPath in CurProject.IntelliSenseIncludeSearchPaths) {
    ... // already added code
  }
  foreach (var CurDefinition in CurProject.IntelliSensePreprocessorDefinitions)
  {
    string Definition = CurDefinition;
    string AlternateDefinition = Definition.Contains("=0") ? Definition.Replace("=0", "=1") : Definition.Replace("=1", "=0");
    if (Definition.Equals("WITH_EDITORONLY_DATA=0") || Definition.Equals("WITH_DATABASE_SUPPORT=1"))
    {
      Definition = AlternateDefinition;
    }
    if (!PreprocessorDefinitions.Contains(Definition) && !PreprocessorDefinitions.Contains(AlternateDefinition) && !Definition.StartsWith("UE_ENGINE_DIRECTORY") && !Definition.StartsWith("ORIGINAL_FILE_NAME"))
    {
      PreprocessorDefinitions.Add(Definition);
    }
  }
}
foreach (string PreprocessorDefinition in PreprocessorDefinitions) 
{
  PreprocessorDefinitionsList += ("\t-D" + PreprocessorDefinition + "\n");
}
PreprocessorDefinitionsList += ("\t-DMONOLITHIC_BUILD=1\n");
PreprocessorDefinitionsList += CMakeSectionEnd;
CMakefileContent.Append (PreprocessorDefinitionsList);

That’s it. Rebuild UnrealBuildTool and generate CMakeLists.txt file again. Now you are ready to start developing your game in CLion.

Please note, that it may happen that includes are not recognised and remain red. This behaviour is known and there is an issue on JetBrains bug tracker. Temporary workaround for this is to create fake build target in CMakeLists.txt

add_executable(FakeTarget $(SOURCE_FILES))