/// <summary> /// Read all the plugins available to a given project /// </summary> /// <param name="EngineDir">Path to the engine directory</param> /// <param name="ProjectFileName">Path to the project file (or null)</param> /// <returns>Sequence of PluginInfo objects, one for each discovered plugin</returns> public static List<PluginInfo> ReadAvailablePlugins(string EngineDir, string ProjectFileName) { List<PluginInfo> Plugins = new List<PluginInfo>(); // Read all the engine plugins string EnginePluginsDir = Path.Combine(EngineDir, "Plugins"); foreach(string PluginFileName in EnumeratePlugins(EnginePluginsDir)) { PluginInfo Plugin = new PluginInfo(PluginFileName, PluginLoadedFrom.Engine); Plugins.Add(Plugin); } // Read all the project plugins if(!String.IsNullOrEmpty(ProjectFileName)) { string ProjectPluginsDir = Path.Combine(Path.GetDirectoryName(ProjectFileName), "Plugins"); foreach(string PluginFileName in EnumeratePlugins(ProjectPluginsDir)) { PluginInfo Plugin = new PluginInfo(PluginFileName, PluginLoadedFrom.GameProject); Plugins.Add(Plugin); } } return Plugins; }
/** Updates the intermediate include directory timestamps of all the passed in UObject modules */ private static void UpdateDirectoryTimestamps(List <UHTModuleInfo> UObjectModules) { foreach (var Module in UObjectModules) { string GeneratedCodeDirectory = Path.GetDirectoryName(Module.GeneratedCPPFilenameBase); var GeneratedCodeDirectoryInfo = new DirectoryInfo(GeneratedCodeDirectory); try { if (GeneratedCodeDirectoryInfo.Exists) { if (UnrealBuildTool.RunningRocket()) { // If it is an Engine folder and we are building a rocket project do NOT update the timestamp! // @todo Rocket: This contains check is hacky/fragile string FullGeneratedCodeDirectory = GeneratedCodeDirectoryInfo.FullName; FullGeneratedCodeDirectory = FullGeneratedCodeDirectory.Replace("\\", "/"); if (FullGeneratedCodeDirectory.Contains("Engine/Intermediate/Build")) { continue; } // Skip checking timestamps for engine plugin intermediate headers in Rocket PluginInfo Info = Plugins.GetPluginInfoForModule(Module.ModuleName); if (Info != null) { if (Info.LoadedFrom == PluginInfo.LoadedFromType.Engine) { continue; } } } // Touch the include directory since we have technically 'generated' the headers // However, the headers might not be touched at all since that would cause the compiler to recompile everything // We can't alter the directory timestamp directly, because this may throw exceptions when the directory is // open in visual studio or windows explorer, so instead we create a blank file that will change the timestamp for us string TimestampFile = GeneratedCodeDirectoryInfo.FullName + Path.DirectorySeparatorChar + @"Timestamp"; if (!GeneratedCodeDirectoryInfo.Exists) { GeneratedCodeDirectoryInfo.Create(); } // Save all of the UObject files to a timestamp file. We'll load these on the next run to see if any new // files with UObject classes were deleted, so that we'll know to run UHT even if the timestamps of all // of the other source files were unchanged { var AllUObjectFiles = new List <string>(); AllUObjectFiles.AddRange(Module.PublicUObjectClassesHeaders.ConvertAll(Item => Item.AbsolutePath)); AllUObjectFiles.AddRange(Module.PublicUObjectHeaders.ConvertAll(Item => Item.AbsolutePath)); AllUObjectFiles.AddRange(Module.PrivateUObjectHeaders.ConvertAll(Item => Item.AbsolutePath)); ResponseFile.Create(TimestampFile, AllUObjectFiles); } } } catch (Exception Exception) { throw new BuildException(Exception, "Couldn't touch header directories: " + Exception.Message); } } }
/// <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<UnrealTargetPlatform>(); // 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 (UnrealTargetPlatform Platform in Enum.GetValues( typeof( UnrealTargetPlatform ) ) ) { 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<UnrealTargetPlatform> Blacklist = new List<UnrealTargetPlatform>(); ConvertPlatformArrayToList((Array)BlacklistObject, ref Blacklist, PluginFileInfo.FullName); // now remove them from the module platform list foreach (UnrealTargetPlatform 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> /// 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 (!UnrealBuildTool.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); } } } }
/// <summary> /// Read all the plugin descriptors under the given directory /// </summary> /// <param name="ParentDirectory">The parent directory to look in.</param> /// <param name="LoadedFrom">The directory type</param> /// <returns>Sequence of the found PluginInfo object.</returns> public static IReadOnlyList<PluginInfo> ReadPluginsFromDirectory(DirectoryReference ParentDirectory, PluginLoadedFrom LoadedFrom) { List<PluginInfo> Plugins; if (!PluginInfoCache.TryGetValue(ParentDirectory, out Plugins)) { Plugins = new List<PluginInfo>(); foreach (FileReference PluginFileName in EnumeratePlugins(ParentDirectory)) { PluginInfo Plugin = new PluginInfo(PluginFileName, LoadedFrom); Plugins.Add(Plugin); } PluginInfoCache.Add(ParentDirectory, Plugins); } return Plugins; }
/// <summary> /// Determine if a plugin is enabled for a given project /// </summary> /// <param name="Project">The project to check</param> /// <param name="Plugin">Information about the plugin</param> /// <param name="Platform">The target platform</param> /// <returns>True if the plugin should be enabled for this project</returns> public static bool IsPluginDescriptorRequiredForProject(PluginInfo Plugin, ProjectDescriptor Project, UnrealTargetPlatform Platform, TargetRules.TargetType TargetType, bool bBuildDeveloperTools, bool bBuildEditor) { // Check if it's referenced by name from the project descriptor. If it is, we'll need the plugin to be included with the project regardless of whether it has // any platform-specific modules or content, just so the runtime can make the call. if (Project != null && Project.Plugins != null) { foreach (PluginReferenceDescriptor PluginReference in Project.Plugins) { if (String.Compare(PluginReference.Name, Plugin.Name, true) == 0) { return PluginReference.IsEnabledForPlatform(Platform) && PluginReference.IsEnabledForTarget(TargetType); } } } // If the plugin contains content, it should be included for all platforms if(Plugin.Descriptor.bCanContainContent) { return true; } // Check if the plugin has any modules for the given target foreach (ModuleDescriptor Module in Plugin.Descriptor.Modules) { if(Module.IsCompiledInConfiguration(Platform, TargetType, bBuildDeveloperTools, bBuildEditor)) { return true; } } return false; }
/// <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 <UnrealTargetPlatform>(); // 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 (UnrealTargetPlatform Platform in Enum.GetValues(typeof(UnrealTargetPlatform))) { 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 <UnrealTargetPlatform> Blacklist = new List <UnrealTargetPlatform>(); ConvertPlatformArrayToList((Array)BlacklistObject, ref Blacklist, PluginFileInfo.FullName); // now remove them from the module platform list foreach (UnrealTargetPlatform 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> /// Read all the plugin descriptors under the given directory /// </summary> /// <param name="RootDirectory">The directory to look in.</param> /// <param name="Subdirectory">A subdirectory to look in in RootDirectory and any other Platform directories under Root</param> /// <param name="Type">The plugin type</param> /// <returns>Sequence of the found PluginInfo object.</returns> public static IReadOnlyList <PluginInfo> ReadPluginsFromDirectory(DirectoryReference RootDirectory, string Subdirectory, PluginType Type) { // look for directories in RootDirectory and and Platform directories under RootDirectory List <DirectoryReference> RootDirectories = new List <DirectoryReference>() { DirectoryReference.Combine(RootDirectory, Subdirectory) }; // now look for platform subdirectories with the Subdirectory DirectoryReference PlatformDirectory = DirectoryReference.Combine(RootDirectory, "Platforms"); if (DirectoryReference.Exists(PlatformDirectory)) { foreach (DirectoryReference Dir in DirectoryReference.EnumerateDirectories(PlatformDirectory)) { RootDirectories.Add(DirectoryReference.Combine(Dir, Subdirectory)); } } Dictionary <PluginInfo, FileReference> ChildPlugins = new Dictionary <PluginInfo, FileReference>(); List <PluginInfo> AllParentPlugins = new List <PluginInfo>(); foreach (DirectoryReference Dir in RootDirectories) { if (!DirectoryReference.Exists(Dir)) { continue; } List <PluginInfo> Plugins; if (!PluginInfoCache.TryGetValue(Dir, out Plugins)) { Plugins = new List <PluginInfo>(); foreach (FileReference PluginFileName in EnumeratePlugins(Dir)) { PluginInfo Plugin = new PluginInfo(PluginFileName, Type); // is there a parent to merge up into? if (Plugin.Descriptor.bIsPluginExtension) { ChildPlugins.Add(Plugin, PluginFileName); } else { Plugins.Add(Plugin); } } PluginInfoCache.Add(Dir, Plugins); } // gather all of the plugins into one list AllParentPlugins.AddRange(Plugins); } // now that all parent plugins are read in, we can let the children look up the parents foreach (KeyValuePair <PluginInfo, FileReference> Pair in ChildPlugins) { TryMergeWithParent(Pair.Key, Pair.Value); } return(AllParentPlugins); }
/** Checks whether a plugin path contains a platform directory fragment */ private static bool ShouldExcludePlugin(PluginInfo Plugin, List<string> ExcludeFragments) { string RelativePathFromRoot; if(Plugin.LoadedFrom == PluginLoadedFrom.Engine) { RelativePathFromRoot = Utils.CleanDirectorySeparators(Utils.MakePathRelativeTo(Plugin.FileName, BuildConfiguration.RelativeEnginePath), '/'); } else { RelativePathFromRoot = Utils.CleanDirectorySeparators(Utils.MakePathRelativeTo(Plugin.FileName, UnrealBuildTool.GetUProjectPath()), '/'); } return ExcludeFragments.Any(x => RelativePathFromRoot.Contains(x)); }
/// <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 UEBuildBinaryType BinaryType = bCompileMonolithic ? UEBuildBinaryType.StaticLibrary : UEBuildBinaryType.DynamicLinkLibrary; // Get the output path. Don't prefix the app name for Rocket string[] OutputFilePaths; if ((UnrealBuildTool.BuildingRocket() || UnrealBuildTool.RunningRocket()) && bCompileMonolithic) { OutputFilePaths = MakeBinaryPaths(Module.Name, Module.Name, BinaryType, TargetType, Plugin, AppName); if (UnrealBuildTool.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 == UnrealTargetConfiguration.DebugGame ? UnrealTargetConfiguration.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 UEBuildBinaryConfiguration Config = new UEBuildBinaryConfiguration( InType: BinaryType, InOutputFilePaths: OutputFilePaths, InIntermediateDirectory: PluginIntermediateBuildPath, bInAllowExports: true, bInAllowCompilation: bShouldBeBuiltFromSource, bInHasModuleRules: bHasModuleRules, InModuleNames: new List<string> { Module.Name } ); AppBinaries.Add(new UEBuildBinaryCPP(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; }
/** 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, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, UEBuildBinaryType 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 = UEBuildPlatform.GetBuildPlatform(Platform); string BinaryExtension = BuildPlatform.GetBinaryExtension(BinaryType); UnrealTargetConfiguration LocalConfig = Configuration; if(Configuration == UnrealTargetConfiguration.DebugGame && !String.IsNullOrEmpty(ModuleName) && !RulesCompiler.IsGameModule(ModuleName)) { LocalConfig = UnrealTargetConfiguration.Development; } string ModuleBinariesSubDir = ""; if (BinaryType == UEBuildBinaryType.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 == UEBuildBinaryType.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 && UnrealBuildTool.HasUProjectFile() && !ProjectFileGenerator.bGenerateProjectFiles) { BinariesDirName = Path.Combine( UnrealBuildTool.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 != UEBuildBinaryType.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 == UnrealTargetPlatform.Linux && (BinaryType == UEBuildBinaryType.DynamicLinkLibrary || BinaryType == UEBuildBinaryType.StaticLibrary)) { Prefix = "lib"; } if (LocalConfig == UnrealTargetConfiguration.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 UEBuildConfiguration.bBuildDeveloperTools; case PluginInfo.PluginModuleType.Editor: case PluginInfo.PluginModuleType.EditorNoCommandlet: return UEBuildConfiguration.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, UEBuildBinaryType BinaryType, TargetRules.TargetType? TargetType, PluginInfo PluginInfo, string AppName, bool bForceNameAsForDevelopment = false, string ExeBinariesSubFolder = null) { if (String.IsNullOrEmpty(ModuleName) && Configuration == UnrealTargetConfiguration.DebugGame && !bCompileMonolithic) { return MakeBinaryPaths(ModuleName, BinaryName, Platform, UnrealTargetConfiguration.Development, BinaryType, TargetType, PluginInfo, AppName, bForceNameAsForDevelopment); } else { return MakeBinaryPaths(ModuleName, BinaryName, Platform, Configuration, BinaryType, TargetType, PluginInfo, AppName, bForceNameAsForDevelopment, ExeBinariesSubFolder); } }
/// <summary> /// Tries to find the PluginInfo associated with a given module file /// </summary> /// <param name="ModuleFile">The module to search for</param> /// <param name="Plugin">The matching plugin info, or null.</param> /// <returns>True if the module belongs to a plugin</returns> public bool TryGetPluginForModule(FileReference ModuleFile, out PluginInfo Plugin) { if (ModuleFileToPluginInfo.TryGetValue(ModuleFile, out Plugin)) { return true; } else { return (Parent == null) ? false : Parent.TryGetPluginForModule(ModuleFile, out Plugin); } }
/// <summary> /// Attempt to merge a child plugin up into a parent plugin (via file naming scheme). Very little merging happens /// but it does allow for platform extensions to extend a plugin with module files /// </summary> /// <param name="Child">Child plugin that needs to merge to a main, parent plugin</param> /// <param name="Filename">Child plugin's filename, used to determine the parent's name</param> private static void TryMergeWithParent(PluginInfo Child, FileReference Filename) { // find the parent PluginInfo Parent = null; string[] Tokens = Filename.GetFileNameWithoutAnyExtensions().Split("_".ToCharArray()); if (Tokens.Length == 2) { string ParentPluginName = Tokens[0]; foreach (KeyValuePair <DirectoryReference, List <PluginInfo> > Pair in PluginInfoCache) { Parent = Pair.Value.FirstOrDefault(x => x.Name.Equals(ParentPluginName, StringComparison.InvariantCultureIgnoreCase)); if (Parent != null) { break; } } } // did we find a parent plugin? if (Parent == null) { throw new BuildException("Child plugin {0} was not named properly. It should be in the form <ParentPlugin>_<Platform>.uplugin", Filename); } // validate child plugin file name string PlatformName = Tokens[1]; if (!IsValidChildPluginSuffix(PlatformName)) { Log.TraceWarning("Ignoring child plugin: {0} - Unknown suffix \"{1}\". Expected valid platform or group", Child.File.GetFileName(), PlatformName); return; } // add our uplugin file to the existing plugin to be used to search for modules later Parent.ChildFiles.Add(Child.File); // merge the supported platforms Parent.Descriptor.MergeSupportedTargetPlatforms(Child.Descriptor.SupportedTargetPlatforms); // make sure we are whitelisted for any modules we list, if the parent had a whitelist if (Child.Descriptor.Modules != null) { // this should cause an error if it's invalid platform name UnrealTargetPlatform Platform = UnrealTargetPlatform.Parse(PlatformName); foreach (ModuleDescriptor ChildModule in Child.Descriptor.Modules) { ModuleDescriptor ParentModule = Parent.Descriptor.Modules.FirstOrDefault(x => x.Name.Equals(ChildModule.Name) && x.Type == ChildModule.Type); if (ParentModule != null) { // merge white/blacklists (if the parent had a list, and child didn't specify a list, just add the child platform to the parent list - for white and black!) if (ParentModule.WhitelistPlatforms != null && ParentModule.WhitelistPlatforms.Length > 0) { List <UnrealTargetPlatform> Whitelist = ParentModule.WhitelistPlatforms.ToList(); if (ChildModule.WhitelistPlatforms != null && ChildModule.WhitelistPlatforms.Length > 0) { Whitelist.AddRange(ChildModule.WhitelistPlatforms); } else { Whitelist.Add(Platform); } ParentModule.WhitelistPlatforms = Whitelist.ToArray(); } if (ParentModule.BlacklistPlatforms != null && ParentModule.BlacklistPlatforms.Length > 0) { if (ChildModule.BlacklistPlatforms != null && ChildModule.BlacklistPlatforms.Length > 0) { List <UnrealTargetPlatform> Blacklist = ParentModule.BlacklistPlatforms.ToList(); Blacklist.AddRange(ChildModule.BlacklistPlatforms); ParentModule.BlacklistPlatforms = Blacklist.ToArray(); } } } } } // @todo platplug: what else do we want to support merging?!? }
/// <summary> /// Include the given plugin in the target. It may be included as a separate binary, or compiled into a monolithic executable. /// </summary> public void AddPlugin(PluginInfo Plugin) { UEBuildBinaryType BinaryType = ShouldCompileMonolithic() ? UEBuildBinaryType.StaticLibrary : UEBuildBinaryType.DynamicLinkLibrary; if(Plugin.Descriptor.Modules != null) { foreach(ModuleDescriptor Module in Plugin.Descriptor.Modules) { if (Module.IsCompiledInConfiguration(Platform, TargetType, UEBuildConfiguration.bBuildDeveloperTools, UEBuildConfiguration.bBuildEditor)) { // Add the corresponding binary for it string ModuleFileName = RulesCompiler.GetModuleFilename(Module.Name); bool bHasSource = (!String.IsNullOrEmpty(ModuleFileName) && Directory.EnumerateFiles(Path.GetDirectoryName(ModuleFileName), "*.cpp", SearchOption.AllDirectories).Any()); AddBinaryForModule(Module.Name, BinaryType, bAllowCompilation: bHasSource, bIsCrossTarget: false); // Add it to the binary if we're compiling monolithic (and it's enabled) if(ShouldCompileMonolithic() && EnabledPlugins.Contains(Plugin)) { AppBinaries[0].AddModule(Module.Name); } } } } }
/// <summary> /// Determine if a plugin is enabled for a given project /// </summary> /// <param name="Project">The project to check</param> /// <param name="Plugin">Information about the plugin</param> /// <param name="Platform">The target platform</param> /// <returns>True if the plugin should be enabled for this project</returns> public static bool IsPluginEnabledForProject(PluginInfo Plugin, ProjectDescriptor Project, UnrealTargetPlatform Platform) { bool bEnabled = Plugin.Descriptor.bEnabledByDefault || Plugin.LoadedFrom == PluginLoadedFrom.GameProject; if(Project != null && Project.Plugins != null) { foreach(PluginReferenceDescriptor PluginReference in Project.Plugins) { if(String.Compare(PluginReference.Name, Plugin.Name, true) == 0) { bEnabled = PluginReference.IsEnabledForPlatform(Platform); } } } return bEnabled; }
/** Sets up the plugins for this target */ protected virtual void SetupPlugins() { // Filter the plugins list by the current project ValidPlugins = Plugins.ReadAvailablePlugins(UnrealBuildTool.GetUProjectFile()); // Remove any plugins for platforms we don't have foreach (UnrealTargetPlatform TargetPlatform in Enum.GetValues(typeof(UnrealTargetPlatform))) { if (TargetPlatform != UnrealTargetPlatform.Desktop && UEBuildPlatform.GetBuildPlatform(TargetPlatform, true) == null) { string DirectoryFragment = String.Format("/{0}/", TargetPlatform.ToString()); ValidPlugins.RemoveAll(x => x.Directory.Replace('\\', '/').Contains(DirectoryFragment)); } } // Build a list of enabled plugins EnabledPlugins = new List<PluginInfo>(); // If we're compiling against the engine, add the plugins enabled for this target if(UEBuildConfiguration.bCompileAgainstEngine) { ProjectDescriptor Project = UnrealBuildTool.HasUProjectFile()? ProjectDescriptor.FromFile(UnrealBuildTool.GetUProjectFile()) : null; foreach(PluginInfo ValidPlugin in ValidPlugins) { if(UProjectInfo.IsPluginEnabledForProject(ValidPlugin, Project, Platform)) { EnabledPlugins.Add(ValidPlugin); } } } // Add the plugins explicitly required by the target rules foreach(string AdditionalPlugin in Rules.AdditionalPlugins) { PluginInfo Plugin = ValidPlugins.FirstOrDefault(ValidPlugin => ValidPlugin.Name == AdditionalPlugin); if(Plugin == null) { throw new BuildException("Plugin '{0}' is in the list of additional plugins for {1}, but was not found.", AdditionalPlugin, TargetName); } if(!EnabledPlugins.Contains(Plugin)) { EnabledPlugins.Add(Plugin); } } // Set the list of plugins that should be built if (bPrecompile && TargetType != TargetRules.TargetType.Program) { BuildPlugins = new List<PluginInfo>(ValidPlugins); } else { BuildPlugins = new List<PluginInfo>(EnabledPlugins); } // Add any foreign plugins to the list if(ForeignPlugins != null) { foreach(string ForeignPlugin in ForeignPlugins) { PluginInfo ForeignPluginInfo = new PluginInfo(ForeignPlugin, PluginLoadedFrom.GameProject); ValidPlugins.Add(ForeignPluginInfo); BuildPlugins.Add(ForeignPluginInfo); } } }
/// <summary> /// Determine if a plugin is enabled for a given project /// </summary> /// <param name="Project">The project to check</param> /// <param name="Plugin">Information about the plugin</param> /// <param name="Platform">The target platform</param> /// <returns>True if the plugin should be enabled for this project</returns> public static bool IsPluginEnabledForProject(PluginInfo Plugin, ProjectDescriptor Project, UnrealTargetPlatform Platform, TargetRules.TargetType Target) { bool bEnabled = Plugin.Descriptor.bEnabledByDefault; if (Project != null && Project.Plugins != null) { foreach (PluginReferenceDescriptor PluginReference in Project.Plugins) { if (String.Compare(PluginReference.Name, Plugin.Name, true) == 0) { bEnabled = PluginReference.IsEnabledForPlatform(Platform) && PluginReference.IsEnabledForTarget(Target); } } } return bEnabled; }
/// <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> /// Constructor /// </summary> /// <param name="Info">The static plugin information</param> public UEBuildPlugin(PluginInfo Info) { this.Info = Info; }
/// <summary> /// Attempt to merge a child plugin up into a parent plugin (via file naming scheme). Very little merging happens /// but it does allow for platform extensions to extend a plugin with module files /// </summary> /// <param name="Child">Child plugin that needs to merge to a main, parent plugin</param> /// <param name="Filename">Child plugin's filename, used to determine the parent's name</param> private static void TryMergeWithParent(PluginInfo Child, FileReference Filename) { // find the parent PluginInfo Parent = null; string[] Tokens = Filename.GetFileNameWithoutAnyExtensions().Split("_".ToCharArray()); if (Tokens.Length == 2) { string ParentPluginName = Tokens[0]; foreach (KeyValuePair <DirectoryReference, List <PluginInfo> > Pair in PluginInfoCache) { Parent = Pair.Value.FirstOrDefault(x => x.Name.Equals(ParentPluginName, StringComparison.InvariantCultureIgnoreCase)); if (Parent != null) { break; } } } else { throw new BuildException("Platform extension plugin {0} was named improperly. It must be in the form <ParentPlugin>_<Platform>.uplugin", Filename); } // did we find a parent plugin? if (Parent == null) { throw new BuildException("Unable to find parent plugin {0} for platform extension plugin {1}. Make sure {0}.uplugin exists.", Tokens[0], Filename); } // validate child plugin file name string PlatformName = Tokens[1]; if (!IsValidChildPluginSuffix(PlatformName)) { Log.TraceWarning("Ignoring child plugin: {0} - Unknown suffix \"{1}\". Expected valid platform or group", Child.File.GetFileName(), PlatformName); return; } // add our uplugin file to the existing plugin to be used to search for modules later Parent.ChildFiles.Add(Child.File); // this should cause an error if it's invalid platform name //UnrealTargetPlatform Platform = UnrealTargetPlatform.Parse(PlatformName); // merge the supported platforms if (Child.Descriptor.SupportedTargetPlatforms != null) { if (Parent.Descriptor.SupportedTargetPlatforms == null) { Parent.Descriptor.SupportedTargetPlatforms = Child.Descriptor.SupportedTargetPlatforms; } else { Parent.Descriptor.SupportedTargetPlatforms = Parent.Descriptor.SupportedTargetPlatforms.Union(Child.Descriptor.SupportedTargetPlatforms).ToList(); } } // make sure we are whitelisted for any modules we list if (Child.Descriptor.Modules != null) { if (Parent.Descriptor.Modules == null) { Parent.Descriptor.Modules = Child.Descriptor.Modules; } else { foreach (ModuleDescriptor ChildModule in Child.Descriptor.Modules) { ModuleDescriptor ParentModule = Parent.Descriptor.Modules.FirstOrDefault(x => x.Name.Equals(ChildModule.Name) && x.Type == ChildModule.Type); if (ParentModule != null) { // merge white/blacklists (if the parent had a list, and child didn't specify a list, just add the child platform to the parent list - for white and black!) if (ChildModule.WhitelistPlatforms != null) { if (ParentModule.WhitelistPlatforms == null) { ParentModule.WhitelistPlatforms = ChildModule.WhitelistPlatforms; } else { ParentModule.WhitelistPlatforms = ParentModule.WhitelistPlatforms.Union(ChildModule.WhitelistPlatforms).ToList(); } } if (ChildModule.BlacklistPlatforms != null) { if (ParentModule.BlacklistPlatforms == null) { ParentModule.BlacklistPlatforms = ChildModule.BlacklistPlatforms; } else { ParentModule.BlacklistPlatforms = ParentModule.BlacklistPlatforms.Union(ChildModule.BlacklistPlatforms).ToList(); } } } else { Parent.Descriptor.Modules.Add(ChildModule); } } } } // make sure we are whitelisted for any plugins we list if (Child.Descriptor.Plugins != null) { if (Parent.Descriptor.Plugins == null) { Parent.Descriptor.Plugins = Child.Descriptor.Plugins; } else { foreach (PluginReferenceDescriptor ChildPluginReference in Child.Descriptor.Plugins) { PluginReferenceDescriptor ParentPluginReference = Parent.Descriptor.Plugins.FirstOrDefault(x => x.Name.Equals(ChildPluginReference.Name)); if (ParentPluginReference != null) { // we only need to whitelist the platform if the parent had a whitelist (otherwise, we could mistakenly remove all other platforms) if (ParentPluginReference.WhitelistPlatforms != null) { if (ChildPluginReference.WhitelistPlatforms != null) { ParentPluginReference.WhitelistPlatforms = ParentPluginReference.WhitelistPlatforms.Union(ChildPluginReference.WhitelistPlatforms).ToList(); } } // if we want to blacklist a platform, add it even if the parent didn't have a blacklist. this won't cause problems with other platforms if (ChildPluginReference.BlacklistPlatforms != null) { if (ParentPluginReference.BlacklistPlatforms == null) { ParentPluginReference.BlacklistPlatforms = ChildPluginReference.BlacklistPlatforms; } else { ParentPluginReference.BlacklistPlatforms = ParentPluginReference.BlacklistPlatforms.Union(ChildPluginReference.BlacklistPlatforms).ToList(); } } } else { Parent.Descriptor.Plugins.Add(ChildPluginReference); } } } } // @todo platplug: what else do we want to support merging?!? }