MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
RuntimeDependencies.Build.cs
Go to the documentation of this file.
1/** @noop License Comment
2 * @file
3 * @copyright
4 * This Source Code is subject to the terms of the Mozilla Public License, v2.0.
5 * If a copy of the MPL was not distributed with this file You can obtain one at
6 * https://mozilla.org/MPL/2.0/
7 *
8 * @author David Mórász
9 * @date 2025
10 */
11
12using System;
13using System.Collections.Generic;
14using System.IO;
15using System.Linq;
16using System.Reflection;
17using System.Runtime.CompilerServices;
18using System.Xml.Serialization;
19using EpicGames.Core;
20using UnrealBuildTool;
21
22namespace McroBuild;
23
24/// <summary>
25/// Model for one runtime dependency
26/// </summary>
28{
29 [XmlText]
30 public string Value = "";
31
32 [XmlAttribute]
33 public string Platform;
34
35 [XmlAttribute]
36 public string Config;
37};
38
39/// <summary>
40/// Model for reading a collection of runtime dependencies gathered by an external tool into an XML document
41/// </summary>
43{
44 [XmlElement]
46
47 [XmlElement]
48 public RuntimeDependency[] Files = Array.Empty<RuntimeDependency>();
49
50 [XmlElement]
51 public RuntimeDependency[] Dlls = Array.Empty<RuntimeDependency>();
52
53 public void Serialize(TextWriter writer)
54 {
55 var serializer = new XmlSerializer(GetType());
56 serializer.Serialize(writer, this);
57 }
58
59 public void Serialize(string file)
60 {
61 using TextWriter writer = new StreamWriter(file);
62 Serialize(writer);
63 }
64
65 public static RuntimeDependencies Deserialize(FileStream stream)
66 {
67 var serializer = new XmlSerializer(typeof(RuntimeDependencies));
68 var result = serializer.Deserialize(stream);
69 return result as RuntimeDependencies;
70 }
71
72 public static RuntimeDependencies Deserialize(string file)
73 {
74 if (file == null || !File.Exists(file)) return null;
75 using var stream = new FileStream(file, FileMode.Open);
76 return Deserialize(stream);
77 }
78}
79
80/// <summary>
81/// Utilities for managing runtime file dependencies (usually DLL's) of third-party libraries in a more consistent and
82/// automatic way than it is expected by vanilla templates.
83/// </summary>
84public static partial class ModuleRuleExtensions
85{
86 /// <summary>
87 /// <para>
88 /// Provide a folder from which runtime dependencies (by default dynamic libraries) are collected, copied to
89 /// plugin-module binaries folder while keeping their relative folder structure and add them of course as
90 /// `RuntimeDependencies` or if they're really dynamic libraries then add them to `PublicDelayLoadDLLs`.
91 /// </para>
92 /// <para>
93 /// The list of DLL's and their base path is propagated to source code via preprocessor definitions
94 /// `*_DLL_PATH` and `*_DLL_FILES`, where `*`˛is the capitalized module name. This goes in tandem with utilities
95 /// inside `Mcro/Dll.h` especially `TModuleBoundDlls` which can sort out all your dynamic library loading chores
96 /// on module startup and shutdown with a one-liner.
97 /// </para>
98 /// </summary>
99 /// <example>
100 /// TModuleBoundDlls&lt;FMyAwesomeModule&gt; GMyLibraryDlls {MYLIBRARY_DLL_PATH, MYLIBRARY_DLL_FILES};
101 /// </example>
102 /// <param name="self"></param>
103 /// <param name="libraryFolder">Path containing the runtime dependencies</param>
104 /// <param name="filePattern">Consider files with given glob pattern. Platform specific DLL files by default</param>
105 /// <param name="thirdParty">Is this module importing a third party library. True by default</param>
106 /// <param name="destinationPostfix">Extra levels in the plugin-module binaries folder. Empty by default</param>
107 public static void PrepareRuntimeDependencies(
108 this ModuleRules self,
109 AbsolutePath libraryFolder,
110 string filePattern = null,
111 bool thirdParty = true,
112 string destinationPostfix = ""
113 ) {
114 var dllExtension = self.Target.Platform == UnrealTargetPlatform.Win64 ? "dll" : "so";
115 filePattern ??= "*." + dllExtension;
116 var binaries = thirdParty ? self.PluginBinaries() / "ThirdParty" : self.PluginBinaries();
117 var dstDir = self.PluginModuleBinariesPlatform(thirdParty ? "ThirdParty" : "") / destinationPostfix;
118 var files = libraryFolder.Copy(dstDir, filePattern);
119
120 foreach (var dep in files) self.RuntimeDependencies.Add(dep);
121 self.DefineDllPath(dstDir.RelativeToBase(self.PluginPath()));
122 self.DefineDllList(
123 files
124 .Where(f => f.HasExtension("." + dllExtension))
125 .Select(f => f.Name)
126 );
127 }
128
129 /// <summary>
130 /// Propagate a search path for module-specific delay loaded DLL files to C++ source via preprocessor.
131 /// </summary>
132 /// <param name="self"></param>
133 /// <param name="pluginRelativePath">A relative path with the plugin as its base, which will be determined in runtime.</param>
134 public static void DefineDllPath(this ModuleRules self, string pluginRelativePath)
135 {
136 self.PublicRuntimeLibraryPaths.Add(self.PluginPath() / pluginRelativePath);
137 self.PublicDefinitions.Add($"{self.GetBaseModuleName().ToUpper()}_DLL_PATH=TEXT(\"{pluginRelativePath}\")");
138 }
139
140 /// <summary>
141 /// Propagate delay loaded module-specific dynamic library files to C++ source via preprocessor.
142 /// </summary>
143 /// <param name="self"></param>
144 /// <param name="dlls">List of DLL file names to load at some point by C++</param>
145 public static void DefineDllList(this ModuleRules self, IEnumerable<string> dlls)
146 {
147 var dllsCache = dlls.ToArray();
148 var dllList = string.Join(',', dllsCache.Select(d => $"TEXT(\"{d}\")"));
149 self.PublicDefinitions.Add($"{self.GetBaseModuleName().ToUpper()}_DLL_FILES={dllList}");
150 self.PublicDelayLoadDLLs.AddRange(dllsCache);
151 }
152
153 /// <summary>
154 /// <para>
155 /// Read a list of runtime dependencies from `RuntimeDeps.xml` located directly in the module folder. An external
156 /// tool can generate this manifest for elaborate libraries and manage their runtime files themselves. The files
157 /// listed in the manifest should be already copied to the place where they will be shipped for runtime as well
158 /// (usually that's the plugin-module binaries folder)
159 /// </para>
160 /// <para>
161 /// The list of DLL's and their base path is propagated to source code via preprocessor definitions
162 /// `*_DLL_PATH` and `*_DLL_FILES`, where `*`˛is the capitalized module name. This goes in tandem with utilities
163 /// inside `Mcro/Dll.h` especially `TModuleBoundDlls` which can sort out all your dynamic library loading chores
164 /// on module startup and shutdown with a one-liner.
165 /// </para>
166 /// </summary>
167 /// <example>
168 /// TModuleBoundDlls&lt;FMyAwesomeModule&gt; GMyLibraryDlls {MYLIBRARY_DLL_PATH, MYLIBRARY_DLL_FILES};
169 /// </example>
170 /// <param name="self"></param>
171 /// <param name="allowDebugLibraries"></param>
172 public static void UseRuntimeDependencies(this ModuleRules self, bool allowDebugLibraries = true)
173 {
174 var manifestFile = self.ModulePath() / "RuntimeDeps.xml";
175 var deps = RuntimeDependencies.Deserialize(manifestFile);
176 if (deps == null)
177 {
178 Log.TraceInformationOnce(
179 "{0}: Ignoring RuntimeDeps.xml because {1} doesn't exist.",
180 self.GetType().Name,
181 manifestFile
182 );
183 return;
184 }
185
186 var runtimeDeps = deps.Files
187 .Where(i => i.Platform?.Contains(self.Target.Platform.ToString()) ?? true)
188 .Where(i => i.Config?.Contains(self.GetLibraryConfig(allowDebugLibraries)) ?? true)
189 .Select(i => self.PluginPath() / i.Value);
190
191 foreach (var dep in runtimeDeps) self.RuntimeDependencies.Add(dep);
192
193 var runtimeLibPath = deps.RuntimeLibraryPath
194 .Where(i => i.Platform?.Contains(self.Target.Platform.ToString()) ?? true)
195 .Where(i => i.Config?.Contains(self.GetLibraryConfig(allowDebugLibraries)) ?? true)
196 .Select(i => i.Value)
197 .FirstOrDefault()
198 ??
199 $"Binaries/ThirdParty/{self.GetBaseModuleName()}/{self.Target.Platform}/{self.GetLibraryConfig(allowDebugLibraries)}";
200
201 self.DefineDllPath(runtimeLibPath);
202
203 var dllDeps = deps.Dlls
204 .Where(i => i.Platform?.Contains(self.Target.Platform.ToString()) ?? true)
205 .Where(i => i.Config?.Contains(self.GetLibraryConfig(allowDebugLibraries)) ?? true)
206 .Select(i => i.Value);
207
208 self.DefineDllList(dllDeps);
209 }
210}
A simplified copy of NUKE's own AbsolutePath class https://github.com/nuke-build/nuke/blob/develop/so...
Convenience utilities for module rules.
static void UseRuntimeDependencies(this ModuleRules self, bool allowDebugLibraries=true)
static void DefineDllPath(this ModuleRules self, string pluginRelativePath)
Propagate a search path for module-specific delay loaded DLL files to C++ source via preprocessor.
static AbsolutePath PluginPath(this ModuleRules self)
Path to the plugin folder to which this module belongs
static void DefineDllList(this ModuleRules self, IEnumerable< string > dlls)
Propagate delay loaded module-specific dynamic library files to C++ source via preprocessor.
static void PrepareRuntimeDependencies(this ModuleRules self, AbsolutePath libraryFolder, string filePattern=null, bool thirdParty=true, string destinationPostfix="")
Model for reading a collection of runtime dependencies gathered by an external tool into an XML docum...
static RuntimeDependencies Deserialize(FileStream stream)
static RuntimeDependencies Deserialize(string file)
Model for one runtime dependency.