zoukankan      html  css  js  c++  java
  • 从"runtime类型模块是否可以依赖runtime类型模块?"看UnrealBuildTool的基本概念及流程

    一、问题

    在配置一个模块的时候,我们通常会指定一个模块的依赖。那么一个runtime类型的module能在依赖中包含Editor类型的module吗?

    二、哪个Target.cs文件被使用

    当在编译环境中选择一个构建目标时,当选择不同的“解决方案配置”时,构建时传递给UnrealBuildTool的“-Target”选项也各不相同,而这个-Target选项直接决定了构建时使用项目文件夹下使用哪个.Target.cs文件。
    该文件最关键的是定义了该目标使用的构建类型,这些构建类型的枚举在EngineSourceProgramsUnrealBuildToolConfigurationTargetRules.cs文件中定义,从文件中还可以看到,可以在Target.cs文件中定义链接类型。而且还可以看到,在默认情况下,如果构建目标是Editor类型,则各个模块默认是以模块形式链接,如果是生成game则是以单一二进制形式使用(参看LinkType属性get属性中的 ((Type == global::UnrealBuildTool.TargetType.Editor) ? TargetLinkType.Modular : TargetLinkType.Monolithic)语句)。
    namespace UnrealBuildTool
    {
    /// <summary>
    /// The type of target
    /// </summary>
    [Serializable]
    public enum TargetType
    {
    /// <summary>
    /// Cooked monolithic game executable (GameName.exe). Also used for a game-agnostic engine executable (UE4Game.exe or RocketGame.exe)
    /// </summary>
    Game,

    /// <summary>
    /// Uncooked modular editor executable and DLLs (UE4Editor.exe, UE4Editor*.dll, GameName*.dll)
    /// </summary>
    Editor,

    /// <summary>
    /// Cooked monolithic game client executable (GameNameClient.exe, but no server code)
    /// </summary>
    Client,

    /// <summary>
    /// Cooked monolithic game server executable (GameNameServer.exe, but no client code)
    /// </summary>
    Server,

    /// <summary>
    /// Program (standalone program, e.g. ShaderCompileWorker.exe, can be modular or monolithic depending on the program)
    /// </summary>
    Program,
    }

    /// <summary>
    /// Specifies how to link all the modules in this target
    /// </summary>
    [Serializable]
    public enum TargetLinkType
    {
    /// <summary>
    /// Use the default link type based on the current target type
    /// </summary>
    Default,

    /// <summary>
    /// Link all modules into a single binary
    /// </summary>
    Monolithic,

    /// <summary>
    /// Link modules into individual dynamic libraries
    /// </summary>
    Modular,
    }
    ……
    /// <summary>
    /// The type of target.
    /// </summary>
    public global::UnrealBuildTool.TargetType Type = global::UnrealBuildTool.TargetType.Game;
    ……
    /// <summary>
    /// Specifies how to link modules in this target (monolithic or modular). This is currently protected for backwards compatibility. Call the GetLinkType() accessor
    /// until support for the deprecated ShouldCompileMonolithic() override has been removed.
    /// </summary>
    public TargetLinkType LinkType
    {
    get
    {
    return (LinkTypePrivate != TargetLinkType.Default) ? LinkTypePrivate : ((Type == global::UnrealBuildTool.TargetType.Editor) ? TargetLinkType.Modular : TargetLinkType.Monolithic);
    }
    set
    {
    LinkTypePrivate = value;
    }
    }

    /// <summary>
    /// Backing storage for the LinkType property.
    /// </summary>
    [RequiresUniqueBuildEnvironment]
    [CommandLine("-Monolithic", Value ="Monolithic")]
    [CommandLine("-Modular", Value ="Modular")]
    TargetLinkType LinkTypePrivate = TargetLinkType.Default;

    三、哪些模块会被构建

    在模块的声明中会声明一个模块属于哪种类型,在构建某种类型target的时候,会判断该模块类型和target模块是否兼容,这个兼容性判断在EngineSourceProgramsUnrealBuildToolSystemModuleDescriptor.cs文件中的ModuleDescriptor类的IsCompiledInConfiguration函数完成。可以看到:runtime模块可以被Editor/Game/Server等所有非Program类型Target使用,Editor模块只能在Editor类型Target使用,Developer模式只能被Editor和Program使用(任何游戏逻辑无法使用)等规则。
    /// <summary>
    /// Class containing information about a code module
    /// </summary>
    [DebuggerDisplay("Name={Name}")]
    public class ModuleDescriptor
    {
    /// <summary>
    /// Name of this module
    /// </summary>
    public readonly string Name;

    /// <summary>
    /// Usage type of module
    /// </summary>
    public ModuleHostType Type;
    ……
    public bool IsCompiledInConfiguration(UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, string TargetName, TargetType TargetType, bool bBuildDeveloperTools, bool bBuildRequiresCookedData)
    {
    ……
    // Check the module is compatible with this target.
    switch (Type)
    {
    case ModuleHostType.Runtime:
    case ModuleHostType.RuntimeNoCommandlet:
    return TargetType != TargetType.Program;
    case ModuleHostType.RuntimeAndProgram:
    return true;
    case ModuleHostType.CookedOnly:
    return bBuildRequiresCookedData;
    case ModuleHostType.UncookedOnly:
    return !bBuildRequiresCookedData;
    case ModuleHostType.Developer:
    return TargetType == TargetType.Editor || TargetType == TargetType.Program;
    case ModuleHostType.DeveloperTool:
    return bBuildDeveloperTools;
    case ModuleHostType.Editor:
    case ModuleHostType.EditorNoCommandlet:
    return TargetType == TargetType.Editor;
    case ModuleHostType.EditorAndProgram:
    return TargetType == TargetType.Editor || TargetType == TargetType.Program;
    case ModuleHostType.Program:
    return TargetType == TargetType.Program;
    case ModuleHostType.ServerOnly:
    return TargetType != TargetType.Program && TargetType != TargetType.Client;
    case ModuleHostType.ClientOnly:
    case ModuleHostType.ClientOnlyNoCommandlet:
    return TargetType != TargetType.Program && TargetType != TargetType.Server;
    }
    return false;
    }

    四、项目定义的cs文件如何运行时执行

    可以看到,UnrealBuildTool会在执行的时候动态加载项目定义的Target.cs和Build.cs文件,这些文件都有框架层的基类(Target、Build分别对应TargetRules和ModuleRules基类)。这就涉及到这些csharp源文件是如何被UnrealBuildTool运行时加载的(类比C++语言,就是如何在运行时动态执行一个cpp文件的内容)。通过分析整个流程来看,整个应该是使用了csharp内置的运行时编译和构建功能。
    在EngineSourceProgramsUnrealBuildToolSystemRulesAssembly.cs文件中RulesAssembly类的构造函数可以看到,在构造函数中通过DynamicCompilation.CompileAndLoadAssembly==>>DynamicCompilation.CompileAssembly完成csharp源文件的编译。
    private static Assembly CompileAssembly(FileReference OutputAssemblyPath, HashSet<FileReference> SourceFileNames, List<string> ReferencedAssembies, List<string> PreprocessorDefines = null, bool TreatWarningsAsErrors = false)
    {
    ……
    // Compile the code
    CompilerResults CompileResults;
    try
    {
    Dictionary<string, string> ProviderOptions = new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } };
    CSharpCodeProvider Compiler = new CSharpCodeProvider(ProviderOptions);
    CompileResults = Compiler.CompileAssemblyFromFile(CompileParams, SourceFileNames.Select(x => x.FullName).ToArray());
    }
    catch (Exception Ex)
    {
    throw new BuildException(Ex, "Failed to launch compiler to compile assembly from source files: {0} (Exception: {1})", String.Join(" ", SourceFileNames), Ex.ToString());
    }
    ……
    }
    编译完成之后使用反射找到constructor函数并调用EngineSourceProgramsUnrealBuildToolSystemRulesAssembly.cs文件中的CreateModuleRules函数完成运行时构造函数的查找和执行:
    public ModuleRules CreateModuleRules(string ModuleName, ReadOnlyTargetRules Target, string ReferenceChain)
    {
    ……
    // Call the constructor
    ConstructorInfo Constructor = RulesObjectType.GetConstructor(new Type[] { typeof(ReadOnlyTargetRules) });
    if(Constructor == null)
    {
    throw new BuildException("No valid constructor found for {0}.", ModuleName);
    }
    Constructor.Invoke(RulesObject, new object[] { Target });
    ……
    }

    五、模块定义中的Private、Public文件的作用和意义

    在一个模块的Build.cs文件中,通常会声明一些模块特有的属性,主要包括PublicIncludePaths、PrivateIncludePaths、PublicDependencyModuleNames、PrivateDependencyModuleNames。这里的两个IncludePaths主要是构建本模块时需要包含的头文件路径,Public和Private的区别在于,如果当前模块A被另一个B依赖的时候,编译B的时候需要把A的Public包含目录添加到自己的包含路径中(主要是Class和Public文件),Module的意义相同,不过它们是在连接时使用。
    在EngineSourceProgramsUnrealBuildToolConfigurationUEBuildModule.cs文件中

    /// <summary>
    /// Sets up the environment for compiling this module.
    /// </summary>
    protected virtual void SetupPrivateCompileEnvironment(
    HashSet<DirectoryReference> IncludePaths,
    HashSet<DirectoryReference> SystemIncludePaths,
    List<string> Definitions,
    List<UEBuildFramework> AdditionalFrameworks,
    List<FileItem> AdditionalPrerequisites,
    bool bWithLegacyPublicIncludePaths
    )
    {
    if (!Rules.bTreatAsEngineModule)
    {
    Definitions.Add("DEPRECATED_FORGAME=DEPRECATED");
    Definitions.Add("UE_DEPRECATED_FORGAME=UE_DEPRECATED");
    }

    // Add this module's private include paths and definitions.
    IncludePaths.UnionWith(PrivateIncludePaths);

    // Find all the modules that are part of the public compile environment for this module.
    Dictionary<UEBuildModule, bool> ModuleToIncludePathsOnlyFlag = new Dictionary<UEBuildModule, bool>();
    FindModulesInPrivateCompileEnvironment(ModuleToIncludePathsOnlyFlag);

    // Now set up the compile environment for the modules in the original order that we encountered them
    foreach (UEBuildModule Module in ModuleToIncludePathsOnlyFlag.Keys)
    {
    Module.AddModuleToCompileEnvironment(Binary, IncludePaths, SystemIncludePaths, Definitions, AdditionalFrameworks, AdditionalPrerequisites, bWithLegacyPublicIncludePaths);
    }
    }
    ……
    /// <summary>
    /// Sets up the environment for linking this module.
    /// </summary>
    public virtual void SetupPrivateLinkEnvironment(
    UEBuildBinary SourceBinary,
    LinkEnvironment LinkEnvironment,
    List<UEBuildBinary> BinaryDependencies,
    HashSet<UEBuildModule> VisitedModules,
    DirectoryReference ExeDir
    )
    {
    // Add the private rpaths
    LinkEnvironment.RuntimeLibraryPaths.AddRange(ExpandPathVariables(Rules.PrivateRuntimeLibraryPaths, SourceBinary.OutputDir, ExeDir));

    // Allow the module's public dependencies to add library paths and additional libraries to the link environment.
    SetupPublicLinkEnvironment(SourceBinary, LinkEnvironment.Libraries, LinkEnvironment.SystemLibraryPaths, LinkEnvironment.SystemLibraries, LinkEnvironment.RuntimeLibraryPaths, LinkEnvironment.Frameworks, LinkEnvironment.WeakFrameworks,
    LinkEnvironment.AdditionalFrameworks, LinkEnvironment.AdditionalBundleResources, LinkEnvironment.DelayLoadDLLs, BinaryDependencies, VisitedModules, ExeDir);

    // Also allow the module's public and private dependencies to modify the link environment.
    List<UEBuildModule> AllDependencyModules = new List<UEBuildModule>();
    AllDependencyModules.AddRange(PrivateDependencyModules);
    AllDependencyModules.AddRange(PublicDependencyModules);

    foreach (UEBuildModule DependencyModule in AllDependencyModules)
    {
    DependencyModule.SetupPublicLinkEnvironment(SourceBinary, LinkEnvironment.Libraries, LinkEnvironment.SystemLibraryPaths, LinkEnvironment.SystemLibraries, LinkEnvironment.RuntimeLibraryPaths, LinkEnvironment.Frameworks, LinkEnvironment.WeakFrameworks,
    LinkEnvironment.AdditionalFrameworks, LinkEnvironment.AdditionalBundleResources, LinkEnvironment.DelayLoadDLLs, BinaryDependencies, VisitedModules, ExeDir);
    }

    // Add all the additional properties
    LinkEnvironment.AdditionalProperties.AddRange(Rules.AdditionalPropertiesForReceipt.Inner);

    // this is a link-time property that needs to be accumulated (if any modules contributing to this module is ignoring, all are ignoring)
    LinkEnvironment.bIgnoreUnresolvedSymbols |= Rules.bIgnoreUnresolvedSymbols;
    }

    六、回到最初的问题

    一个runtime的module是否可以依赖editor类型的模块?这个依赖于target类型:如果是构建target类型是Editor就可以,如果是game/server/client则不行。(该结论未测试)

  • 相关阅读:
    为上次写的框架加了一个辅助功能
    复制文件夹下所有文件
    进制之间的相互转换
    c# 修改appConfig文件节点
    GUID
    太悲哀了
    poj2411 Mondriaan's Dream
    poj3311 Hie with the Pie
    HDU3001 Travelling
    luogu p2622关灯问题II
  • 原文地址:https://www.cnblogs.com/tsecer/p/15120553.html
Copyright © 2011-2022 走看看