/// <summary> /// Loads a plugin descriptor file and fills out a new PluginInfo structure. Throws an exception on failure. /// </summary> /// <param name="PluginFile">The path to the plugin file to load</param> /// <param name="LoadedFrom">Where the plugin was loaded from</param> /// <returns>New PluginInfo for the loaded descriptor.</returns> private static PluginInfo LoadPluginDescriptor(FileInfo PluginFileInfo, PluginInfo.LoadedFromType LoadedFrom) { // Load the file up (JSon format) Dictionary<string, object> PluginDescriptorDict; { string FileContent; using (var StreamReader = new StreamReader(PluginFileInfo.FullName)) { FileContent = StreamReader.ReadToEnd(); } // Parse the Json into a dictionary var CaseSensitiveJSonDict = fastJSON.JSON.Instance.ToObject<Dictionary<string, object>>(FileContent); // Convert to a case-insensitive dictionary, so that we can be more tolerant of hand-typed files PluginDescriptorDict = new Dictionary<string, object>(CaseSensitiveJSonDict, StringComparer.InvariantCultureIgnoreCase); } // File version check long PluginVersionNumber; { // Try to get the version of the plugin object PluginVersionObject; if (!PluginDescriptorDict.TryGetValue("FileVersion", out PluginVersionObject)) { if (!PluginDescriptorDict.TryGetValue("PluginFileVersion", out PluginVersionObject)) { throw new BuildException("Plugin descriptor file '{0}' does not contain a valid FileVersion entry", PluginFileInfo.FullName); } } if (!(PluginVersionObject is long)) { throw new BuildException("Unable to parse the version number of the plugin descriptor file '{0}'", PluginFileInfo.FullName); } PluginVersionNumber = (long)PluginVersionObject; if (PluginVersionNumber > LatestPluginDescriptorFileVersion) { throw new BuildException("Plugin descriptor file '{0}' appears to be in a newer version ({1}) of the file format that we can load (max version: {2}).", PluginFileInfo.FullName, PluginVersionNumber, LatestPluginDescriptorFileVersion); } // @todo plugin: Should we also test the engine version here? (we would need to load it from build.properties) } // NOTE: At this point, we can use PluginVersionNumber to handle backwards compatibility when loading the rest of the file! var PluginInfo = new PluginInfo(); PluginInfo.LoadedFrom = LoadedFrom; PluginInfo.Directory = PluginFileInfo.Directory.FullName; PluginInfo.Name = Path.GetFileName(PluginInfo.Directory); // Determine whether the plugin should be enabled by default object EnabledByDefaultObject; if (PluginDescriptorDict.TryGetValue("EnabledByDefault", out EnabledByDefaultObject) && (EnabledByDefaultObject is bool)) { PluginInfo.bEnabledByDefault = (bool)EnabledByDefaultObject; } // This plugin might have some modules that we need to know about. Let's take a look. { object ModulesObject; if (PluginDescriptorDict.TryGetValue("Modules", out ModulesObject)) { if (!(ModulesObject is Array)) { throw new BuildException("Found a 'Modules' entry in plugin descriptor file '{0}', but it doesn't appear to be in the array format that we were expecting.", PluginFileInfo.FullName); } var ModulesArray = (Array)ModulesObject; foreach (var ModuleObject in ModulesArray) { var ModuleDict = new Dictionary<string, object>((Dictionary<string, object>)ModuleObject, StringComparer.InvariantCultureIgnoreCase); var PluginModuleInfo = new PluginInfo.PluginModuleInfo(); // Module name { // All modules require a name to be set object ModuleNameObject; if (!ModuleDict.TryGetValue("Name", out ModuleNameObject)) { throw new BuildException("Found a 'Module' entry with a missing 'Name' field in plugin descriptor file '{0}'", PluginFileInfo.FullName); } string ModuleName = (string)ModuleNameObject; // @todo plugin: Locate this module right now and validate it? Repair case? PluginModuleInfo.Name = ModuleName; } // Module type { // Check to see if the user specified the module's type object ModuleTypeObject; if (!ModuleDict.TryGetValue("Type", out ModuleTypeObject)) { throw new BuildException("Found a Module entry '{0}' with a missing 'Type' field in plugin descriptor file '{1}'", PluginModuleInfo.Name, PluginFileInfo.FullName); } string ModuleTypeString = (string)ModuleTypeObject; // Check to see if this is a valid type bool FoundValidType = false; foreach (PluginInfo.PluginModuleType PossibleType in Enum.GetValues(typeof(PluginInfo.PluginModuleType))) { if (ModuleTypeString.Equals(PossibleType.ToString(), StringComparison.InvariantCultureIgnoreCase)) { FoundValidType = true; PluginModuleInfo.Type = PossibleType; break; } } if (!FoundValidType) { throw new BuildException("Module entry '{0}' specified an unrecognized module Type '{1}' in plugin descriptor file '{0}'", PluginModuleInfo.Name, ModuleTypeString, PluginFileInfo.FullName); } } // Supported platforms PluginModuleInfo.Platforms = new List<STTargetPlatform>(); // look for white and blacklists object WhitelistObject, BlacklistObject; ModuleDict.TryGetValue("WhitelistPlatforms", out WhitelistObject); ModuleDict.TryGetValue("BlacklistPlatforms", out BlacklistObject); if (WhitelistObject != null && BlacklistObject != null) { throw new BuildException("Found a module '{0}' with both blacklist and whitelist platform lists in plugin file '{1}'", PluginModuleInfo.Name, PluginFileInfo.FullName); } // now process the whitelist if (WhitelistObject != null) { if (!(WhitelistObject is Array)) { throw new BuildException("Found a 'WhitelistPlatforms' entry in plugin descriptor file '{0}', but it doesn't appear to be in the array format that we were expecting.", PluginFileInfo.FullName); } // put the whitelist array directly into the plugin's modulelist ConvertPlatformArrayToList((Array)WhitelistObject, ref PluginModuleInfo.Platforms, PluginFileInfo.FullName); } // handle the blacklist (or lack of blacklist and whitelist which means all platforms) else { // start with all platforms supported foreach (STTargetPlatform Platform in Enum.GetValues(typeof(STTargetPlatform))) { PluginModuleInfo.Platforms.Add(Platform); } // if we want to disallow some platforms, then pull them out now if (BlacklistObject != null) { if (!(BlacklistObject is Array)) { throw new BuildException("Found a 'BlacklistPlatforms' entry in plugin descriptor file '{0}', but it doesn't appear to be in the array format that we were expecting.", PluginFileInfo.FullName); } // put the whitelist array directly into the plugin's modulelist List<STTargetPlatform> Blacklist = new List<STTargetPlatform>(); ConvertPlatformArrayToList((Array)BlacklistObject, ref Blacklist, PluginFileInfo.FullName); // now remove them from the module platform list foreach (STTargetPlatform Platform in Blacklist) { PluginModuleInfo.Platforms.Remove(Platform); } } } object ModuleShouldBuild; if (ModuleDict.TryGetValue("bShouldBuild", out ModuleShouldBuild)) { PluginInfo.bShouldBuild = (Int64)ModuleShouldBuild == 1 ? true : false; } else { PluginInfo.bShouldBuild = true; } if (PluginInfo.bShouldBuild) { // add to list of modules PluginInfo.Modules.Add(PluginModuleInfo); } } } else { // Plugin contains no modules array. That's fine. } } return PluginInfo; }
/// <summary> /// Finds all plugins in the specified base directory /// </summary> /// <param name="PluginsDirectory">Base directory to search. All subdirectories will be searched, except directories within other plugins.</param> /// <param name="LoadedFrom">Where we're loading these plugins from</param> /// <param name="Plugins">List of all of the plugins we found</param> public static void FindPluginsIn(string PluginsDirectory, PluginInfo.LoadedFromType LoadedFrom, ref List<PluginInfo> Plugins) { if (Directory.Exists(PluginsDirectory)) { FindPluginsRecursively(PluginsDirectory, LoadedFrom, ref Plugins); } }
/// <summary> /// Recursively locates all plugins in the specified folder, appending to the incoming list /// </summary> /// <param name="PluginsDirectory">Directory to search</param> /// <param name="LoadedFrom">Where we're loading these plugins from</param> /// <param name="Plugins">List of plugins found so far</param> private static void FindPluginsRecursively(string PluginsDirectory, PluginInfo.LoadedFromType LoadedFrom, ref List<PluginInfo> Plugins) { // NOTE: The logic in this function generally matches that of the C++ code for FindPluginsRecursively // in the core engine code. These routines should be kept in sync. // Each sub-directory is possibly a plugin. If we find that it contains a plugin, we won't recurse any // further -- you can't have plugins within plugins. If we didn't find a plugin, we'll keep recursing. var PluginsDirectoryInfo = new DirectoryInfo(PluginsDirectory); foreach (var PossiblePluginDirectory in PluginsDirectoryInfo.EnumerateDirectories()) { if (!STBuildTool.IsPathIgnoredForBuild(PossiblePluginDirectory.FullName)) { // Do we have a plugin descriptor in this directory? bool bFoundPlugin = false; foreach (var PluginDescriptorFileName in Directory.GetFiles(PossiblePluginDirectory.FullName, "*" + PluginDescriptorFileExtension)) { // Found a plugin directory! No need to recurse any further, but make sure it's unique. if (!Plugins.Any(x => x.Directory == PossiblePluginDirectory.FullName)) { // Load the plugin info and keep track of it var PluginDescriptorFile = new FileInfo(PluginDescriptorFileName); var PluginInfo = LoadPluginDescriptor(PluginDescriptorFile, LoadedFrom); Plugins.Add(PluginInfo); bFoundPlugin = true; Log.TraceVerbose("Found plugin in: " + PluginInfo.Directory); } // No need to search for more plugins break; } if (!bFoundPlugin) { // Didn't find a plugin in this directory. Continue to look in subfolders. FindPluginsRecursively(PossiblePluginDirectory.FullName, LoadedFrom, ref Plugins); } } } }
/** Given a UBT-built binary name (e.g. "Core"), returns a relative path to the binary for the current build configuration (e.g. "../Binaries/Win64/Core-Win64-Debug.lib") */ public static string[] MakeBinaryPaths(string ModuleName, string BinaryName, STTargetPlatform Platform, STTargetConfiguration Configuration, STBuildBinaryType BinaryType, TargetRules.TargetType? TargetType, PluginInfo PluginInfo, string AppName, bool bForceNameAsForDevelopment = false, string ExeBinariesSubFolder = null) { // Determine the binary extension for the platform and binary type. var BuildPlatform = STBuildPlatform.GetBuildPlatform(Platform); string BinaryExtension; if (!BuildConfiguration.bRunUnrealCodeAnalyzer) { BinaryExtension = BuildPlatform.GetBinaryExtension(BinaryType); } else { BinaryExtension = @"-" + BuildConfiguration.UCAModuleToAnalyze + @".analysis"; } STTargetConfiguration LocalConfig = Configuration; if (Configuration == STTargetConfiguration.DebugGame && !String.IsNullOrEmpty(ModuleName) && !RulesCompiler.IsGameModule(ModuleName)) { LocalConfig = STTargetConfiguration.Development; } string ModuleBinariesSubDir = ""; if (BinaryType == STBuildBinaryType.DynamicLinkLibrary && (string.IsNullOrEmpty(ModuleName) == false)) { // Allow for modules to specify sub-folders in the Binaries folder var RulesFilename = RulesCompiler.GetModuleFilename(ModuleName); // Plugins can be binary-only and can have no rules object if (PluginInfo == null || !String.IsNullOrEmpty(RulesFilename)) { ModuleRules ModuleRulesObj = RulesCompiler.CreateModuleRules(ModuleName, new TargetInfo(Platform, Configuration, TargetType), out RulesFilename); if (ModuleRulesObj != null) { ModuleBinariesSubDir = ModuleRulesObj.BinariesSubFolder; } } } else if (BinaryType == STBuildBinaryType.Executable && string.IsNullOrEmpty(ExeBinariesSubFolder) == false) { ModuleBinariesSubDir = ExeBinariesSubFolder; } //@todo.Rocket: This just happens to work since exp and lib files go into intermediate... // Build base directory string ("../Binaries/<Platform>/") string BinariesDirName; if (TargetType.HasValue && TargetType.Value == TargetRules.TargetType.Program && STBuildTool.HasUProjectFile() && !ProjectFileGenerator.bGenerateProjectFiles) { BinariesDirName = Path.Combine(STBuildTool.GetUProjectPath(), "Binaries"); } else if (PluginInfo != null) { BinariesDirName = Path.Combine(PluginInfo.Directory, "Binaries"); } else { BinariesDirName = Path.Combine("..", "Binaries"); } var BaseDirectory = Path.Combine(BinariesDirName, Platform.ToString()); if (ModuleBinariesSubDir.Length > 0) { BaseDirectory = Path.Combine(BaseDirectory, ModuleBinariesSubDir); } string BinarySuffix = ""; if ((PluginInfo != null) && (BinaryType != STBuildBinaryType.DynamicLinkLibrary)) { BinarySuffix = "-Static"; } // append the architecture to the end of the binary name BinarySuffix = BuildPlatform.ApplyArchitectureName(BinarySuffix); string OutBinaryPath = ""; // Append binary file name string Prefix = ""; if (Platform == STTargetPlatform.Linux && (BinaryType == STBuildBinaryType.DynamicLinkLibrary || BinaryType == STBuildBinaryType.StaticLibrary)) { Prefix = "lib"; } if (LocalConfig == STTargetConfiguration.Development || bForceNameAsForDevelopment) { OutBinaryPath = Path.Combine(BaseDirectory, String.Format("{3}{0}{1}{2}", BinaryName, BinarySuffix, BinaryExtension, Prefix)); } else { OutBinaryPath = Path.Combine(BaseDirectory, String.Format("{5}{0}-{1}-{2}{3}{4}", BinaryName, Platform.ToString(), LocalConfig.ToString(), BinarySuffix, BinaryExtension, Prefix)); } return BuildPlatform.FinalizeBinaryPaths(OutBinaryPath); }
/// <summary> /// Determines whether the given plugin module is part of the current build. /// </summary> private bool ShouldIncludePluginModule(PluginInfo Plugin, PluginInfo.PluginModuleInfo Module) { // Check it can be built for this platform... if (Module.Platforms.Contains(Platform)) { // ...and that it's appropriate for the current build environment. switch (Module.Type) { case PluginInfo.PluginModuleType.Runtime: case PluginInfo.PluginModuleType.RuntimeNoCommandlet: return true; case PluginInfo.PluginModuleType.Developer: return STBuildConfiguration.bBuildDeveloperTools; case PluginInfo.PluginModuleType.Editor: case PluginInfo.PluginModuleType.EditorNoCommandlet: return STBuildConfiguration.bBuildEditor; case PluginInfo.PluginModuleType.Program: return TargetType == TargetRules.TargetType.Program; } } return false; }
/** Given a UBT-built binary name (e.g. "Core"), returns a relative path to the binary for the current build configuration (e.g. "../../Binaries/Win64/Core-Win64-Debug.lib") */ public string[] MakeBinaryPaths(string ModuleName, string BinaryName, STBuildBinaryType BinaryType, TargetRules.TargetType? TargetType, PluginInfo PluginInfo, string AppName, bool bForceNameAsForDevelopment = false, string ExeBinariesSubFolder = null) { if (String.IsNullOrEmpty(ModuleName) && Configuration == STTargetConfiguration.DebugGame && !bCompileMonolithic) { return MakeBinaryPaths(ModuleName, BinaryName, Platform, STTargetConfiguration.Development, BinaryType, TargetType, PluginInfo, AppName, bForceNameAsForDevelopment); } else { return MakeBinaryPaths(ModuleName, BinaryName, Platform, Configuration, BinaryType, TargetType, PluginInfo, AppName, bForceNameAsForDevelopment, ExeBinariesSubFolder); } }
/// <summary> /// Include the given plugin module in the target. Will be built in the appropriate subfolder under the plugin directory. /// </summary> public List<string> AddPluginModule(PluginInfo Plugin, PluginInfo.PluginModuleInfo Module) { var SpecialRocketLibFilesThatAreBuildProducts = new List<string>(); bool bCompileMonolithic = ShouldCompileMonolithic(); // Get the binary type to build STBuildBinaryType BinaryType = bCompileMonolithic ? STBuildBinaryType.StaticLibrary : STBuildBinaryType.DynamicLinkLibrary; // Get the output path. Don't prefix the app name for Rocket string[] OutputFilePaths; if ((STBuildTool.BuildingRocket() || STBuildTool.RunningRocket()) && bCompileMonolithic) { OutputFilePaths = MakeBinaryPaths(Module.Name, Module.Name, BinaryType, TargetType, Plugin, AppName); if (STBuildTool.BuildingRocket()) { SpecialRocketLibFilesThatAreBuildProducts.AddRange(OutputFilePaths); } } else { OutputFilePaths = MakeBinaryPaths(Module.Name, GetAppName() + "-" + Module.Name, BinaryType, TargetType, Plugin, AppName); } // Try to determine if we have the rules file var ModuleFilename = RulesCompiler.GetModuleFilename(Module.Name); var bHasModuleRules = String.IsNullOrEmpty(ModuleFilename) == false; // Figure out whether we should build it from source var ModuleSourceFolder = bHasModuleRules ? Path.GetDirectoryName(RulesCompiler.GetModuleFilename(Module.Name)) : ModuleFilename; bool bShouldBeBuiltFromSource = bHasModuleRules && Directory.GetFiles(ModuleSourceFolder, "*.cpp", SearchOption.AllDirectories).Length > 0; string PluginIntermediateBuildPath; { if (Plugin.LoadedFrom == PluginInfo.LoadedFromType.Engine) { // Plugin folder is in the engine directory var PluginConfiguration = Configuration == STTargetConfiguration.DebugGame ? STTargetConfiguration.Development : Configuration; PluginIntermediateBuildPath = Path.GetFullPath(Path.Combine(BuildConfiguration.RelativeEnginePath, BuildConfiguration.PlatformIntermediateFolder, AppName, PluginConfiguration.ToString())); } else { // Plugin folder is in the project directory PluginIntermediateBuildPath = Path.GetFullPath(Path.Combine(ProjectDirectory, BuildConfiguration.PlatformIntermediateFolder, GetTargetName(), Configuration.ToString())); } PluginIntermediateBuildPath = Path.Combine(PluginIntermediateBuildPath, "Plugins", ShouldCompileMonolithic() ? "Static" : "Dynamic"); } // Create the binary STBuildBinaryConfiguration Config = new STBuildBinaryConfiguration(InType: BinaryType, InOutputFilePaths: OutputFilePaths, InIntermediateDirectory: PluginIntermediateBuildPath, bInAllowExports: true, bInAllowCompilation: bShouldBeBuiltFromSource, bInHasModuleRules: bHasModuleRules, InModuleNames: new List<string> { Module.Name }); AppBinaries.Add(new STBuildBinaryCPP(this, Config)); return SpecialRocketLibFilesThatAreBuildProducts; }
/// <summary> /// Include the given plugin in the target. It may be included as a separate binary, or compiled into a monolithic executable. /// </summary> public List<string> AddPlugin(PluginInfo Plugin) { var SpecialRocketLibFilesThatAreBuildProducts = new List<string>(); foreach (PluginInfo.PluginModuleInfo Module in Plugin.Modules) { if (ShouldIncludePluginModule(Plugin, Module)) { SpecialRocketLibFilesThatAreBuildProducts.AddRange(AddPluginModule(Plugin, Module)); } } return SpecialRocketLibFilesThatAreBuildProducts; }