Files
FlaxEngine/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs

1269 lines
62 KiB
C#

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Flax.Build.Bindings;
using Flax.Build.Graph;
using Flax.Build.NativeCpp;
using System.Text.Json.Serialization;
using System.Text.Json;
namespace Flax.Build
{
static partial class Builder
{
public sealed class BuildData
{
public ProjectInfo Project;
public TaskGraph Graph;
public RulesAssembly Rules;
public Target Target;
public BuildOptions TargetOptions;
public Platform Platform;
public Toolchain Toolchain;
public TargetArchitecture Architecture;
public TargetConfiguration Configuration;
public Dictionary<Module, BuildOptions> Modules = new Dictionary<Module, BuildOptions>(256);
public List<Module> ModulesOrderList = new List<Module>();
public Dictionary<Module, ModuleInfo> ModulesInfo = new Dictionary<Module, ModuleInfo>(256);
public Dictionary<TypeInfo, ApiTypeInfo> TypeCache = new Dictionary<TypeInfo, ApiTypeInfo>(1024);
public IGrouping<string, Module>[] BinaryModules;
public BuildTargetInfo BuildInfo;
public Dictionary<ProjectInfo, BuildData> ReferenceBuilds = new Dictionary<ProjectInfo, BuildData>();
public Dictionary<string, string> PrecompiledHeaderFiles = new();
public BuildTargetBinaryModuleInfo FinReferenceBuildModule(string name)
{
if (BuildInfo != null && BuildInfo.BinaryModules != null)
{
foreach (var e in BuildInfo.BinaryModules)
{
if (string.Equals(e.Name, name, StringComparison.Ordinal))
return e;
}
}
foreach (var e in ReferenceBuilds)
{
var q = e.Value.FinReferenceBuildModule(name);
if (q != null)
return q;
}
return null;
}
}
public class BuildTargetBinaryModuleInfo
{
public string Name;
[JsonIgnore]
public string NativePath;
[JsonPropertyName("NativePath")]
public string NativePathProcessed;
[JsonIgnore]
public string ManagedPath;
[JsonPropertyName("ManagedPath")]
public string ManagedPathProcessed;
}
public class BuildTargetReferenceInfo
{
[JsonIgnore]
public string ProjectPath;
[JsonPropertyName("ProjectPath")]
public string ProjectPathProcessed;
[JsonIgnore]
public string Path;
[JsonPropertyName("Path")]
public string PathProcessed;
}
[JsonSourceGenerationOptions(IncludeFields = true)]
[JsonSerializable(typeof(BuildTargetBinaryModuleInfo))]
internal partial class BuildTargetBinaryModuleInfoSourceGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(IncludeFields = true)]
[JsonSerializable(typeof(BuildTargetReferenceInfo))]
internal partial class BuildTargetReferenceInfoSourceGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(IncludeFields = true)]
[JsonSerializable(typeof(BuildTargetInfo))]
internal partial class BuildTargetInfoSourceGenerationContext : JsonSerializerContext
{
}
public class BuildTargetInfo
{
public string Name;
public string Platform;
public string Architecture;
public string Configuration;
public string HotReloadPostfix;
public BuildTargetBinaryModuleInfo[] BinaryModules;
public BuildTargetReferenceInfo[] References;
internal void AddExternalBinaryModules(ref int i, string projectPath, HashSet<BuildOptions.ExternalModule> externalModules)
{
foreach (var externalModule in externalModules)
{
var binaryModuleInfo = new BuildTargetBinaryModuleInfo
{
NativePath = string.Empty,
ManagedPath = string.Empty,
Name = externalModule.Name,
};
switch (externalModule.Type)
{
case BuildOptions.ExternalModule.Types.Native:
binaryModuleInfo.NativePath = externalModule.Path;
binaryModuleInfo.NativePathProcessed = ProcessPath(binaryModuleInfo.NativePath, projectPath);
break;
case BuildOptions.ExternalModule.Types.CSharp:
binaryModuleInfo.ManagedPath = externalModule.Path;
binaryModuleInfo.ManagedPathProcessed = ProcessPath(binaryModuleInfo.ManagedPath, projectPath);
break;
}
binaryModuleInfo.NativePathProcessed = ProcessPath(binaryModuleInfo.NativePath, projectPath);
binaryModuleInfo.ManagedPathProcessed = ProcessPath(binaryModuleInfo.ManagedPath, projectPath);
BinaryModules[i++] = binaryModuleInfo;
}
}
internal void AddReferencedBuilds(ref int i, string projectPath, Dictionary<ProjectInfo, BuildData> referencedBuilds)
{
foreach (var referenceBuild in referencedBuilds)
{
var reference = referenceBuild.Value;
var referenceOutputTargetFilePath = reference.Target.GetOutputFilePath(reference.TargetOptions);
var referenceOutputPath = Path.GetDirectoryName(referenceOutputTargetFilePath);
var referenceInfo = new BuildTargetReferenceInfo
{
ProjectPath = reference.Project.ProjectPath,
Path = Path.Combine(referenceOutputPath, reference.Target.Name + ".Build.json"),
};
referenceInfo.ProjectPathProcessed = ProcessPath(referenceInfo.ProjectPath, projectPath);
referenceInfo.PathProcessed = ProcessPath(referenceInfo.Path, projectPath);
References[i++] = referenceInfo;
}
}
public string Serialize()
{
// Null any empty fields to exclude them from serialization
if (HotReloadPostfix?.Length == 0)
HotReloadPostfix = null;
foreach (var binaryModule in BinaryModules)
{
if (binaryModule.NativePathProcessed?.Length == 0)
binaryModule.NativePathProcessed = null;
if (binaryModule.ManagedPathProcessed?.Length == 0)
binaryModule.ManagedPathProcessed = null;
}
// Convert to Json
var options = new JsonSerializerOptions
{
WriteIndented = true,
IncludeFields = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
TypeInfoResolver = BuildTargetInfoSourceGenerationContext.Default,
};
return JsonSerializer.Serialize<BuildTargetInfo>(this, options);
}
public static string ProcessPath(string path, string projectPath)
{
if (string.IsNullOrEmpty(path))
return string.Empty;
if (path.StartsWith(Globals.EngineRoot))
path = "$(EnginePath)" + path.Substring(Globals.EngineRoot.Length);
else if (path.StartsWith(projectPath))
path = "$(ProjectPath)" + path.Substring(projectPath.Length);
return Utilities.NormalizePath(path);
}
}
private static BuildOptions CollectModules(BuildData buildData, Module module, bool withApi)
{
if (!buildData.Modules.TryGetValue(module, out var moduleOptions))
{
// Setup module build options
var outputPath = Path.Combine(buildData.TargetOptions.IntermediateFolder, module.Name);
if (!buildData.Target.IsPreBuilt && !Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);
moduleOptions = new BuildOptions
{
Target = buildData.Target,
Platform = buildData.Platform,
Toolchain = buildData.Toolchain,
Architecture = buildData.Architecture,
Configuration = buildData.Configuration,
CompileEnv = (CompileEnvironment)buildData.TargetOptions.CompileEnv.Clone(),
LinkEnv = (LinkEnvironment)buildData.TargetOptions.LinkEnv.Clone(),
IntermediateFolder = outputPath,
OutputFolder = outputPath,
WorkingDirectory = buildData.TargetOptions.WorkingDirectory,
HotReloadPostfix = buildData.TargetOptions.HotReloadPostfix,
Flags = buildData.TargetOptions.Flags,
};
moduleOptions.SourcePaths.Add(module.FolderPath);
module.Setup(moduleOptions);
moduleOptions.MergeSourcePathsIntoSourceFiles();
// Collect dependent modules (private)
foreach (var moduleName in moduleOptions.PrivateDependencies)
{
// TODO: check circular references
var dependencyModule = buildData.Rules.GetModule(moduleName);
if (dependencyModule != null)
{
var dependencyOptions = CollectModules(buildData, dependencyModule, withApi);
foreach (var e in dependencyOptions.PublicDefinitions)
moduleOptions.PrivateDefinitions.Add(e);
}
else
{
Log.Warning(string.Format("Missing module {0} referenced by module {1} (or invalid name specified)", moduleName, module.Name));
}
}
// Collect dependent modules (public)
foreach (var moduleName in moduleOptions.PublicDependencies)
{
// TODO: check circular references
var dependencyModule = buildData.Rules.GetModule(moduleName);
if (dependencyModule != null)
{
var dependencyOptions = CollectModules(buildData, dependencyModule, withApi);
foreach (var e in dependencyOptions.PublicDefinitions)
moduleOptions.PublicDefinitions.Add(e);
}
else
{
Log.Warning(string.Format("Missing module {0} referenced by module {1} (or invalid name specified)", moduleName, module.Name));
}
}
if (withApi && !string.IsNullOrEmpty(module.BinaryModuleName))
{
// Process module scripting API
BindingsGenerator.ParseModule(buildData, module, moduleOptions);
}
// Cache build module
buildData.Modules.Add(module, moduleOptions);
buildData.ModulesOrderList.Add(module);
}
return moduleOptions;
}
public static event Action<TaskGraph, BuildOptions> PreBuild;
public static event Action<TaskGraph, BuildOptions> PostBuild;
public static event Action<TaskGraph, BuildData> BuildBindings;
/// <summary>
/// Collects the modules required by the given target to build (includes dependencies).
/// </summary>
/// <param name="rules">The rules.</param>
/// <param name="target">The target.</param>
/// <param name="targetBuildOptions">The target build options.</param>
/// <param name="platform">The platform.</param>
/// <param name="toolchain">The toolchain.</param>
/// <param name="architecture">The architecture.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>The list of modules to use for build (unique items).</returns>
public static Dictionary<Module, BuildOptions> CollectModules(RulesAssembly rules, Platform platform, Target target, BuildOptions targetBuildOptions, Toolchain toolchain, TargetArchitecture architecture, TargetConfiguration configuration)
{
return CollectModules(rules, platform, target, targetBuildOptions, toolchain, architecture, configuration, target.Modules);
}
/// <summary>
/// Collects the modules required to build (includes dependencies).
/// </summary>
/// <param name="rules">The rules.</param>
/// <param name="target">The target.</param>
/// <param name="targetBuildOptions">The target build options.</param>
/// <param name="platform">The platform.</param>
/// <param name="toolchain">The toolchain.</param>
/// <param name="architecture">The architecture.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="moduleNames">The list of root modules to start collection.</param>
/// <returns>The list of modules to use for build (unique items).</returns>
public static Dictionary<Module, BuildOptions> CollectModules(RulesAssembly rules, Platform platform, Target target, BuildOptions targetBuildOptions, Toolchain toolchain, TargetArchitecture architecture, TargetConfiguration configuration, IEnumerable<string> moduleNames)
{
var buildData = new BuildData
{
Rules = rules,
Target = target,
TargetOptions = targetBuildOptions,
Platform = platform,
Toolchain = toolchain,
Architecture = architecture,
Configuration = configuration,
};
// Collect all modules
foreach (var moduleName in moduleNames)
{
var module = rules.GetModule(moduleName);
if (module != null)
{
CollectModules(buildData, module, false);
}
else
{
Log.Warning(string.Format("Missing module {0} (or invalid name specified)", moduleName));
}
}
return buildData.Modules;
}
private static void OnBuildBindings(BuildData buildData, TaskGraph graph)
{
if (buildData.Target.IsPreBuilt)
return;
using (new ProfileEventScope("BuildBindings"))
{
if (EngineConfiguration.WithCSharp(buildData.TargetOptions))
{
BuildTargetBindings(graph, buildData);
}
BuildBindings?.Invoke(graph, buildData);
}
}
private static void LinkNativeBinary(BuildData buildData, BuildOptions buildOptions, string outputPath)
{
using (new ProfileEventScope("LinkNativeBinary"))
{
buildData.Toolchain.LinkFiles(buildData.Graph, buildOptions, outputPath);
// Produce additional import library if will use binary module references
var linkerOutput = buildOptions.LinkEnv.Output;
if (buildData.Toolchain.UseImportLibraryWhenLinking &&
!buildData.Toolchain.GeneratesImportLibraryWhenLinking &&
buildOptions.Target.UseSymbolsExports &&
(linkerOutput == LinkerOutput.Executable || linkerOutput == LinkerOutput.SharedLibrary))
{
buildOptions.LinkEnv.Output = LinkerOutput.ImportLibrary;
buildData.Toolchain.LinkFiles(buildData.Graph, buildOptions, Path.ChangeExtension(outputPath, buildData.Toolchain.Platform.StaticLibraryFileExtension));
buildOptions.LinkEnv.Output = linkerOutput;
}
}
}
private static IGrouping<string, Module>[] GetBinaryModules(ProjectInfo project, Target target, Dictionary<Module, BuildOptions> buildModules)
{
var modules = new List<Module>();
switch (target.LinkType)
{
case TargetLinkType.Monolithic:
// Include all modules
modules.AddRange(buildModules.Keys);
break;
case TargetLinkType.Modular:
{
// Include all modules from the project that contains this target
var sourcePath = Path.Combine(project.ProjectFolderPath, "Source");
foreach (var module in buildModules.Keys)
{
if (module.FolderPath.StartsWith(sourcePath))
modules.Add(module);
}
break;
}
default: throw new ArgumentOutOfRangeException();
}
modules.RemoveAll(x => x == null || string.IsNullOrEmpty(x.BinaryModuleName));
return modules.GroupBy(x => x.BinaryModuleName).ToArray();
}
private static BuildOptions BuildModule(BuildData buildData, Module module)
{
if (buildData.Modules.TryGetValue(module, out var moduleOptions))
{
using (new ProfileEventScope(module.Name))
{
Log.Verbose(string.Format("Building module {0}", module.Name));
BuildModuleInner(buildData, module, moduleOptions);
}
}
return moduleOptions;
}
internal static void BuildModuleInner(BuildData buildData, Module module, BuildOptions moduleOptions, bool withApi = true)
{
// Inherit build environment from dependent modules
foreach (var moduleName in moduleOptions.PrivateDependencies)
{
var dependencyModule = buildData.Rules.GetModule(moduleName);
if (dependencyModule != null && buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions))
{
moduleOptions.LinkEnv.InputFiles.AddRange(dependencyOptions.OutputFiles);
moduleOptions.DependencyFiles.AddRange(dependencyOptions.DependencyFiles);
moduleOptions.OptionalDependencyFiles.AddRange(dependencyOptions.OptionalDependencyFiles);
moduleOptions.PrivateIncludePaths.AddRange(dependencyOptions.PublicIncludePaths);
moduleOptions.Libraries.AddRange(dependencyOptions.Libraries);
moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries);
moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI);
moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules);
}
}
foreach (var moduleName in moduleOptions.PublicDependencies)
{
var dependencyModule = buildData.Rules.GetModule(moduleName);
if (dependencyModule != null && buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions))
{
moduleOptions.LinkEnv.InputFiles.AddRange(dependencyOptions.OutputFiles);
moduleOptions.DependencyFiles.AddRange(dependencyOptions.DependencyFiles);
moduleOptions.OptionalDependencyFiles.AddRange(dependencyOptions.OptionalDependencyFiles);
moduleOptions.PublicIncludePaths.AddRange(dependencyOptions.PublicIncludePaths);
moduleOptions.Libraries.AddRange(dependencyOptions.Libraries);
moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries);
moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI);
moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules);
}
}
// Special case some module types
if (module is DepsModule || module is HeaderOnlyModule)
{
// Skip build
}
else
{
// Setup actual build environment
module.SetupEnvironment(moduleOptions);
moduleOptions.MergeSourcePathsIntoSourceFiles();
// Skip build for C#-only modules
if (!module.BuildNativeCode)
return;
// Skip build for pre-built targets
if (buildData.Target.IsPreBuilt)
{
// Setup output file for further linking by referencing targets
if (buildData.Target.LinkType != TargetLinkType.Monolithic)
{
var outputLib = Path.Combine(buildData.TargetOptions.OutputFolder, buildData.Toolchain.Platform.GetLinkOutputFileName(module.Name, moduleOptions.LinkEnv.Output));
if (moduleOptions.LinkEnv.Output == LinkerOutput.Executable || moduleOptions.LinkEnv.Output == LinkerOutput.SharedLibrary)
moduleOptions.OutputFiles.Add(Path.ChangeExtension(outputLib, buildData.Platform.StaticLibraryFileExtension));
else
moduleOptions.Libraries.Add(outputLib);
}
return;
}
// Collect all files to compile
var cppFiles = new List<string>(moduleOptions.SourceFiles.Count / 2);
for (int i = 0; i < moduleOptions.SourceFiles.Count; i++)
{
if (moduleOptions.SourceFiles[i].EndsWith(".cpp", StringComparison.OrdinalIgnoreCase) || moduleOptions.SourceFiles[i].EndsWith(".c", StringComparison.OrdinalIgnoreCase))
cppFiles.Add(moduleOptions.SourceFiles[i]);
}
if (!string.IsNullOrEmpty(module.BinaryModuleName) && withApi)
{
// Generate scripting bindings
using (new ProfileEventScope("GenerateBindings"))
{
BindingsGenerator.GenerateBindings(buildData, module, ref moduleOptions, out var bindings);
if (bindings.UseBindings)
{
// Compile C++ file with bindings
cppFiles.Add(bindings.GeneratedCppFilePath);
}
}
// Compile C++ file with binary module (only for first module in the binary module to prevent multiple implementations)
if (buildData.BinaryModules.FirstOrDefault(x => x.Key == module.BinaryModuleName)?.First(x => !(x is DepsModule || x is HeaderOnlyModule)) == module)
{
var project = GetModuleProject(module, buildData);
var binaryModuleSourcePath = Path.Combine(project.ProjectFolderPath, "Source", module.BinaryModuleName + ".Gen.cpp");
if (!cppFiles.Contains(binaryModuleSourcePath) && File.Exists(binaryModuleSourcePath))
cppFiles.Add(binaryModuleSourcePath);
}
}
// If the PCH was already created (eg. by other engine module) then simply reuse the same file
if (moduleOptions.CompileEnv.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.CreateManual && buildData.PrecompiledHeaderFiles.TryGetValue(moduleOptions.CompileEnv.PrecompiledHeaderSource, out var pch))
{
moduleOptions.CompileEnv.PrecompiledHeaderUsage = PrecompiledHeaderFileUsage.UseManual;
moduleOptions.CompileEnv.PrecompiledHeaderFile = pch;
}
// Compile all source files
var compilationOutput = buildData.Toolchain.CompileCppFiles(buildData.Graph, moduleOptions, cppFiles, moduleOptions.OutputFolder);
foreach (var e in compilationOutput.ObjectFiles)
moduleOptions.LinkEnv.InputFiles.Add(e);
if (buildData.TargetOptions.LinkEnv.GenerateDocumentation)
{
// TODO: find better way to add generated doc files to the target linker (module exports the output doc files?)
buildData.TargetOptions.LinkEnv.DocumentationFiles.AddRange(compilationOutput.DocumentationFiles);
}
if (moduleOptions.CompileEnv.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.CreateManual && !string.IsNullOrEmpty(compilationOutput.PrecompiledHeaderFile))
{
// Cache PCH file to be used by other modules that reference the same file
buildData.PrecompiledHeaderFiles.Add(moduleOptions.CompileEnv.PrecompiledHeaderSource, compilationOutput.PrecompiledHeaderFile);
}
if (buildData.Target.LinkType != TargetLinkType.Monolithic)
{
// Use the library includes required by this module
moduleOptions.LinkEnv.InputLibraries.AddRange(moduleOptions.Libraries);
// Link all object files into module library
var outputLib = Path.Combine(buildData.TargetOptions.OutputFolder, buildData.Platform.GetLinkOutputFileName(module.Name + moduleOptions.HotReloadPostfix, moduleOptions.LinkEnv.Output));
LinkNativeBinary(buildData, moduleOptions, outputLib);
// Expose the import library to modules that include this module
if (moduleOptions.LinkEnv.Output == LinkerOutput.Executable || moduleOptions.LinkEnv.Output == LinkerOutput.SharedLibrary)
{
if (buildData.Toolchain.UseImportLibraryWhenLinking)
moduleOptions.OutputFiles.Add(Path.ChangeExtension(outputLib, buildData.Platform.StaticLibraryFileExtension));
else
moduleOptions.Libraries.Add(outputLib);
}
}
else
{
// Use direct linking of the module object files into the target
moduleOptions.OutputFiles.AddRange(compilationOutput.ObjectFiles);
// Forward the library includes required by this module
moduleOptions.OutputFiles.AddRange(moduleOptions.LinkEnv.InputFiles);
}
}
}
internal static void BuildModuleInnerBindingsOnly(BuildData buildData, Module module, BuildOptions moduleOptions)
{
// Inherit build environment from dependent modules
foreach (var moduleName in moduleOptions.PrivateDependencies)
{
var dependencyModule = buildData.Rules.GetModule(moduleName);
if (dependencyModule != null && buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions))
{
moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI);
moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules);
}
}
foreach (var moduleName in moduleOptions.PublicDependencies)
{
var dependencyModule = buildData.Rules.GetModule(moduleName);
if (dependencyModule != null && buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions))
{
moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI);
moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules);
}
}
// Special case some module types
if (module is DepsModule || module is HeaderOnlyModule)
{
// Skip build
}
else
{
// Setup actual build environment
module.SetupEnvironment(moduleOptions);
moduleOptions.MergeSourcePathsIntoSourceFiles();
// Skip build for C#-only modules
if (!module.BuildNativeCode)
return;
// Skip build for pre-built targets
if (buildData.Target.IsPreBuilt)
return;
if (!string.IsNullOrEmpty(module.BinaryModuleName))
{
// Generate scripting bindings
using (new ProfileEventScope("GenerateBindings"))
{
BindingsGenerator.GenerateBindings(buildData, module, ref moduleOptions, out _);
}
}
}
}
private static void BuildTargetReferenceNativeCpp(Dictionary<Target, BuildData> buildContext, BuildData buildData, ProjectInfo.Reference reference)
{
// Find the target from that project that is being referenced
// Note: projects reference each other but build system uses targets to produce binaries (project can have multiple targets)
var projectTargets = GetProjectTargets(reference.Project);
var target = buildData.Target.SelectReferencedTarget(reference.Project, projectTargets);
if (target == null)
{
Log.Verbose("No target selected for build");
return;
}
if (!target.Platforms.Contains(buildData.Platform.Target) || !target.Architectures.Contains(buildData.Architecture))
{
Log.Verbose($"Referenced target {reference.Project.Name} doesn't support {buildData.Platform.Target} {buildData.Architecture}");
return;
}
if (!buildContext.TryGetValue(target, out var referencedBuildData))
{
Log.Info($"Building referenced target {reference.Project.Name}");
// Build target
// Note: use build environment local to the referenced project workspace (separate build cache, separate task graph)
var graph = new TaskGraph(reference.Project.ProjectFolderPath);
var skipBuild = target.IsPreBuilt || (Configuration.SkipTargets != null && Configuration.SkipTargets.Contains(target.Name));
target.PreBuild();
referencedBuildData = BuildTargetNativeCpp(buildData.Rules, graph, target, buildContext, buildData.Toolchain, buildData.Configuration, true, skipBuild);
if (!skipBuild)
{
using (new ProfileEventScope("PrepareTasks"))
{
using (new ProfileEventScope("Setup"))
graph.Setup();
using (new ProfileEventScope("SortTasks"))
graph.SortTasks();
using (new ProfileEventScope("LoadCache"))
graph.LoadCache();
}
int executedTasksCount;
bool failed;
using (new ProfileEventScope("ExecuteTasks"))
{
failed = graph.Execute(out executedTasksCount);
}
if (executedTasksCount != 0)
{
using (new ProfileEventScope("SaveCache"))
{
graph.SaveCache();
}
}
if (failed)
throw new BuildException($"Failed to build target {target.Name}. See log.");
}
else
{
Log.Verbose($"Skipping build for target {target.Name}");
}
target.PostBuild();
}
// Cache build data to be used in calling target build as reference
buildData.ReferenceBuilds.Add(reference.Project, referencedBuildData);
}
private static void BuildTargetReferenceNativeCppBindingsOnly(Dictionary<Target, BuildData> buildContext, BuildData buildData, ProjectInfo.Reference reference)
{
// Find the target from that project that is being referenced
// Note: projects reference each other but build system uses targets to produce binaries (project can have multiple targets)
var projectTargets = GetProjectTargets(reference.Project);
var target = buildData.Target.SelectReferencedTarget(reference.Project, projectTargets);
if (target == null)
{
Log.Verbose("No target selected for build");
return;
}
if (!buildContext.TryGetValue(target, out var referencedBuildData))
{
Log.Info($"Building referenced target {reference.Project.Name}");
// Build target
// Note: use build environment local to the referenced project workspace (separate build cache, separate task graph)
var graph = new TaskGraph(reference.Project.ProjectFolderPath);
var skipBuild = target.IsPreBuilt || (Configuration.SkipTargets != null && Configuration.SkipTargets.Contains(target.Name));
target.PreBuild();
referencedBuildData = BuildTargetNativeCppBindingsOnly(buildData.Rules, graph, target, buildContext, buildData.Platform, buildData.Architecture, buildData.Configuration, skipBuild);
if (!skipBuild)
{
using (new ProfileEventScope("PrepareTasks"))
{
using (new ProfileEventScope("Setup"))
graph.Setup();
using (new ProfileEventScope("SortTasks"))
graph.SortTasks();
using (new ProfileEventScope("LoadCache"))
graph.LoadCache();
}
int executedTasksCount;
bool failed;
using (new ProfileEventScope("ExecuteTasks"))
{
failed = graph.Execute(out executedTasksCount);
}
if (executedTasksCount != 0)
{
using (new ProfileEventScope("SaveCache"))
{
graph.SaveCache();
}
}
if (failed)
throw new BuildException($"Failed to build target {target.Name}. See log.");
}
else
{
Log.Verbose($"Skipping build for target {target.Name}");
}
target.PostBuild();
}
// Cache build data to be used in calling target build as reference
buildData.ReferenceBuilds.Add(reference.Project, referencedBuildData);
}
private static BuildData BuildTargetNativeCpp(RulesAssembly rules, TaskGraph graph, Target target, Dictionary<Target, BuildData> buildContext, Toolchain toolchain, TargetConfiguration configuration, bool isBuildingReference = false, bool skipBuild = false)
{
if (buildContext.TryGetValue(target, out var buildData))
return buildData;
// Warn if target has no valid modules
if (target.Modules.Count == 0)
Log.Warning(string.Format("Target {0} has no modules to build", target.Name));
// Pick a project
var project = Globals.Project;
if (target is ProjectTarget projectTarget)
project = projectTarget.Project;
if (project == null)
throw new Exception($"Cannot build target {target.Name}. The project file is missing (.flaxproj located in the folder above).");
// Setup build environment for the target
var targetBuildOptions = GetBuildOptions(target, toolchain.Platform, toolchain, toolchain.Architecture, configuration, project.ProjectFolderPath, skipBuild ? string.Empty : Configuration.HotReloadPostfix);
if (!isBuildingReference)
toolchain.LogInfo();
using (new ProfileEventScope("PreBuild"))
{
// Pre build
toolchain.PreBuild(graph, targetBuildOptions);
target.PreBuild(graph, targetBuildOptions);
PreBuild?.Invoke(graph, targetBuildOptions);
// Ensure that target build directories exist
if (!target.IsPreBuilt && !Directory.Exists(targetBuildOptions.IntermediateFolder))
Directory.CreateDirectory(targetBuildOptions.IntermediateFolder);
if (!target.IsPreBuilt && !Directory.Exists(targetBuildOptions.OutputFolder))
Directory.CreateDirectory(targetBuildOptions.OutputFolder);
}
// Setup building common data container
buildData = new BuildData
{
Project = project,
Graph = graph,
Rules = rules,
Target = target,
TargetOptions = targetBuildOptions,
Platform = toolchain.Platform,
Toolchain = toolchain,
Architecture = toolchain.Architecture,
Configuration = configuration,
};
buildContext.Add(target, buildData);
// Firstly build all referenced projects (the current targets depends on the referenced projects binaries)
if (buildData.Target.LinkType == TargetLinkType.Modular)
{
using (new ProfileEventScope("References"))
{
foreach (var reference in buildData.Project.References)
{
using (new ProfileEventScope(reference.Project.Name))
{
BuildTargetReferenceNativeCpp(buildContext, buildData, reference);
}
}
}
}
// Collect all modules
using (new ProfileEventScope("CollectModules"))
{
foreach (var moduleName in target.Modules)
{
var module = rules.GetModule(moduleName);
if (module != null)
{
CollectModules(buildData, module, true);
}
else
{
Log.Warning(string.Format("Missing module {0} (or invalid name specified)", moduleName));
}
}
}
using (new ProfileEventScope("SetupBinaryModules"))
{
// Collect all binary modules to include into target
buildData.BinaryModules = GetBinaryModules(project, target, buildData.Modules);
// Inject binary modules symbols import/export defines
var moduleNamesUsed = new HashSet<string>();
for (int i = 0; i < buildData.BinaryModules.Length; i++)
{
var binaryModule = buildData.BinaryModules[i];
var binaryModuleNameUpper = binaryModule.Key.ToUpperInvariant();
foreach (var module in binaryModule)
{
if (buildData.Modules.TryGetValue(module, out var moduleOptions))
{
if (buildData.Target.LinkType == TargetLinkType.Modular)
{
// Export symbols from binary module
moduleOptions.CompileEnv.PreprocessorDefinitions.Add(binaryModuleNameUpper + (target.UseSymbolsExports ? "_API=" + toolchain.DllExport : "_API="));
// Import symbols from binary modules containing the referenced modules (from this project only, external ones are handled via ReferenceBuilds below)
foreach (var moduleName in moduleOptions.PrivateDependencies.Concat(moduleOptions.PublicDependencies))
{
var dependencyModule = buildData.Rules.GetModule(moduleName);
if (dependencyModule != null &&
!string.IsNullOrEmpty(dependencyModule.BinaryModuleName) &&
dependencyModule.BinaryModuleName != binaryModule.Key &&
!moduleNamesUsed.Contains(dependencyModule.BinaryModuleName) &&
GetModuleProject(dependencyModule, project) != null &&
buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions))
{
// Import symbols from referenced binary module
moduleOptions.CompileEnv.PreprocessorDefinitions.Add(dependencyModule.BinaryModuleName.ToUpperInvariant() + "_API=" + toolchain.DllImport);
var dependencyModuleBuild = buildData.FinReferenceBuildModule(moduleName);
if (dependencyModuleBuild != null && !string.IsNullOrEmpty(dependencyModuleBuild.NativePath))
{
// Link against the referenced binary module
if (toolchain.UseImportLibraryWhenLinking)
moduleOptions.LinkEnv.InputLibraries.Add(Path.ChangeExtension(dependencyModuleBuild.NativePath, toolchain.Platform.StaticLibraryFileExtension));
else
moduleOptions.LinkEnv.InputLibraries.Add(dependencyModuleBuild.NativePath);
}
}
}
}
else
{
// Export symbols from all binary modules in the build
foreach (var q in buildData.BinaryModules)
{
moduleOptions.CompileEnv.PreprocessorDefinitions.Add(q.Key.ToUpperInvariant() + (target.UseSymbolsExports ? "_API=" + toolchain.DllExport : "_API="));
}
}
foreach (var e in buildData.ReferenceBuilds)
{
foreach (var q in e.Value.BuildInfo.BinaryModules)
{
if (!string.IsNullOrEmpty(q.NativePath))
{
if (buildData.Target.LinkType == TargetLinkType.Modular)
{
// Import symbols from referenced binary module
moduleOptions.CompileEnv.PreprocessorDefinitions.Add(q.Name.ToUpperInvariant() + "_API=" + toolchain.DllImport);
// Link against the referenced binary module
if (toolchain.UseImportLibraryWhenLinking)
moduleOptions.LinkEnv.InputLibraries.Add(Path.ChangeExtension(q.NativePath, toolchain.Platform.StaticLibraryFileExtension));
else
moduleOptions.LinkEnv.InputLibraries.Add(q.NativePath);
}
else if (target.UseSymbolsExports)
{
// Export symbols from referenced binary module to be visible further
moduleOptions.CompileEnv.PreprocessorDefinitions.Add(q.Name.ToUpperInvariant() + "_API=" + toolchain.DllExport);
}
else
{
// Skip symbols from referenced binary module (module code injected into monolithic build)
moduleOptions.CompileEnv.PreprocessorDefinitions.Add(q.Name.ToUpperInvariant() + "_API=");
}
}
}
}
}
}
moduleNamesUsed.Clear();
}
}
// Generate code for binary modules included in the target
using (new ProfileEventScope("GenerateBindings"))
{
if (!target.IsPreBuilt)
{
BindingsGenerator.GenerateBindings(buildData);
}
}
// Build all modules from target binary modules but in order of collecting (from independent to more dependant ones)
using (new ProfileEventScope("BuildModules"))
{
foreach (var module in buildData.ModulesOrderList)
{
if (buildData.BinaryModules.Any(x => x.Contains(module)))
{
var moduleOptions = BuildModule(buildData, module);
// Merge module into target environment
buildData.TargetOptions.LinkEnv.InputFiles.AddRange(moduleOptions.OutputFiles);
buildData.TargetOptions.DependencyFiles.AddRange(moduleOptions.DependencyFiles);
buildData.TargetOptions.OptionalDependencyFiles.AddRange(moduleOptions.OptionalDependencyFiles);
buildData.TargetOptions.Libraries.AddRange(moduleOptions.Libraries);
buildData.TargetOptions.DelayLoadLibraries.AddRange(moduleOptions.DelayLoadLibraries);
buildData.TargetOptions.ScriptingAPI.Add(moduleOptions.ScriptingAPI);
buildData.TargetOptions.ExternalModules.AddRange(moduleOptions.ExternalModules);
}
}
}
// Build scripting API bindings
OnBuildBindings(buildData, graph);
// Link modules into a target
var outputTargetFilePath = target.GetOutputFilePath(targetBuildOptions);
var outputPath = Path.GetDirectoryName(outputTargetFilePath);
switch (target.LinkType)
{
case TargetLinkType.Monolithic:
{
if (!buildData.Target.IsPreBuilt)
LinkNativeBinary(buildData, targetBuildOptions, outputTargetFilePath);
break;
}
}
// Generate target build output info
using (new ProfileEventScope("GenerateBuildInfo"))
{
buildData.BuildInfo = new BuildTargetInfo
{
Name = target.Name,
Platform = toolchain.Platform.Target.ToString(),
Architecture = toolchain.Architecture.ToString(),
Configuration = configuration.ToString(),
HotReloadPostfix = targetBuildOptions.HotReloadPostfix,
BinaryModules = new BuildTargetBinaryModuleInfo[buildData.BinaryModules.Length + targetBuildOptions.ExternalModules.Count],
References = new BuildTargetReferenceInfo[buildData.ReferenceBuilds.Count],
};
int i;
// Binary modules
for (i = 0; i < buildData.BinaryModules.Length; i++)
{
var binaryModule = buildData.BinaryModules[i];
var binaryModuleInfo = new BuildTargetBinaryModuleInfo
{
Name = binaryModule.Key,
ManagedPath = EngineConfiguration.WithCSharp(targetBuildOptions) ? Path.Combine(outputPath, binaryModule.Key + ".CSharp.dll") : string.Empty,
};
switch (target.LinkType)
{
case TargetLinkType.Monolithic:
{
if (binaryModule.Any(x => x.BuildNativeCode))
{
// Target merges all modules into a one native binary
binaryModuleInfo.NativePath = outputTargetFilePath;
}
else
{
// C#-only binary module
binaryModuleInfo.NativePath = string.Empty;
}
if (!binaryModule.Any(x => x.BuildCSharp))
{
// Skip C#
binaryModuleInfo.ManagedPath = string.Empty;
}
break;
}
case TargetLinkType.Modular:
{
// Every module produces own set of binaries
if (binaryModule.Count() != 1)
throw new Exception("Cannot output binary if it uses multiple modules.");
var module = binaryModule.First();
if (module.BuildNativeCode)
{
var moduleOptions = buildData.Modules[module];
var outputLib = Path.Combine(buildData.TargetOptions.OutputFolder, buildData.Platform.GetLinkOutputFileName(module.Name + moduleOptions.HotReloadPostfix, moduleOptions.LinkEnv.Output));
binaryModuleInfo.NativePath = outputLib;
}
else
{
// C#-only binary module
binaryModuleInfo.NativePath = string.Empty;
}
if (!module.BuildCSharp)
{
// Skip C#
binaryModuleInfo.ManagedPath = string.Empty;
}
break;
}
default: throw new ArgumentOutOfRangeException();
}
binaryModuleInfo.NativePathProcessed = BuildTargetInfo.ProcessPath(binaryModuleInfo.NativePath, project.ProjectFolderPath);
binaryModuleInfo.ManagedPathProcessed = BuildTargetInfo.ProcessPath(binaryModuleInfo.ManagedPath, project.ProjectFolderPath);
buildData.BuildInfo.BinaryModules[i] = binaryModuleInfo;
}
buildData.BuildInfo.AddExternalBinaryModules(ref i, project.ProjectFolderPath, targetBuildOptions.ExternalModules);
i = 0;
buildData.BuildInfo.AddReferencedBuilds(ref i, project.ProjectFolderPath, buildData.ReferenceBuilds);
if (!buildData.Target.IsPreBuilt)
Utilities.WriteFileIfChanged(Path.Combine(outputPath, target.Name + ".Build.json"), buildData.BuildInfo.Serialize());
}
// Deploy files
if (!buildData.Target.IsPreBuilt)
{
using (new ProfileEventScope("DeployFiles"))
{
foreach (var srcFile in targetBuildOptions.OptionalDependencyFiles.Where(File.Exists).Union(targetBuildOptions.DependencyFiles))
{
var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile));
graph.AddCopyFile(dstFile, srcFile);
}
}
}
using (new ProfileEventScope("PostBuild"))
{
// Post build
PostBuild?.Invoke(graph, targetBuildOptions);
target.PostBuild(graph, targetBuildOptions);
toolchain.PostBuild(graph, targetBuildOptions);
}
return buildData;
}
private static BuildData BuildTargetNativeCppBindingsOnly(RulesAssembly rules, TaskGraph graph, Target target, Dictionary<Target, BuildData> buildContext, Platform platform, TargetArchitecture architecture, TargetConfiguration configuration, bool skipBuild = false)
{
if (buildContext.TryGetValue(target, out var buildData))
return buildData;
// Warn if target has no valid modules
if (target.Modules.Count == 0)
Log.Warning(string.Format("Target {0} has no modules to build", target.Name));
// Pick a project
var project = Globals.Project;
if (target is ProjectTarget projectTarget)
project = projectTarget.Project;
if (project == null)
throw new Exception($"Cannot build target {target.Name}. The project file is missing (.flaxproj located in the folder above).");
// Setup build environment for the target
var targetBuildOptions = GetBuildOptions(target, platform, platform.TryGetToolchain(architecture), architecture, configuration, project.ProjectFolderPath, skipBuild ? string.Empty : Configuration.HotReloadPostfix);
using (new ProfileEventScope("PreBuild"))
{
// Pre build
target.PreBuild(graph, targetBuildOptions);
// Ensure that target build directories exist
if (!target.IsPreBuilt && !Directory.Exists(targetBuildOptions.IntermediateFolder))
Directory.CreateDirectory(targetBuildOptions.IntermediateFolder);
if (!target.IsPreBuilt && !Directory.Exists(targetBuildOptions.OutputFolder))
Directory.CreateDirectory(targetBuildOptions.OutputFolder);
}
// Setup building common data container
buildData = new BuildData
{
Project = project,
Graph = graph,
Rules = rules,
Target = target,
TargetOptions = targetBuildOptions,
Platform = platform,
Toolchain = targetBuildOptions.Toolchain,
Architecture = architecture,
Configuration = configuration,
};
buildContext.Add(target, buildData);
// Firstly build all referenced projects (the current targets depends on the referenced projects binaries)
if (buildData.Target.LinkType == TargetLinkType.Modular)
{
using (new ProfileEventScope("References"))
{
foreach (var reference in buildData.Project.References)
{
using (new ProfileEventScope(reference.Project.Name))
{
if (Configuration.BuildBindingsOnly || reference.Project.IsCSharpOnlyProject || !platform.HasRequiredSDKsInstalled)
{
BuildTargetReferenceNativeCppBindingsOnly(buildContext, buildData, reference);
}
else
{
if (buildData.Toolchain == null)
buildData.Toolchain = platform.GetToolchain(architecture);
BuildTargetReferenceNativeCpp(buildContext, buildData, reference);
}
}
}
}
}
// Collect all modules
using (new ProfileEventScope("CollectModules"))
{
foreach (var moduleName in target.Modules)
{
var module = rules.GetModule(moduleName);
if (module != null)
{
CollectModules(buildData, module, true);
}
else
{
Log.Warning(string.Format("Missing module {0} (or invalid name specified)", moduleName));
}
}
}
using (new ProfileEventScope("SetupBinaryModules"))
{
// Collect all binary modules to include into target
buildData.BinaryModules = GetBinaryModules(project, target, buildData.Modules);
}
// Generate code for binary modules included in the target
using (new ProfileEventScope("GenerateBindings"))
{
if (!target.IsPreBuilt)
{
BindingsGenerator.GenerateBindings(buildData);
}
}
// Build all modules from target binary modules but in order of collecting (from independent to more dependant ones)
using (new ProfileEventScope("BuildModules"))
{
foreach (var module in buildData.ModulesOrderList)
{
if (buildData.BinaryModules.Any(x => x.Contains(module)))
{
if (buildData.Modules.TryGetValue(module, out var moduleOptions))
{
if (!target.IsPreBuilt)
{
using (new ProfileEventScope(module.Name))
{
Log.Verbose(string.Format("Building module {0}", module.Name));
BuildModuleInnerBindingsOnly(buildData, module, moduleOptions);
}
}
// Merge module into target environment
buildData.TargetOptions.ScriptingAPI.Add(moduleOptions.ScriptingAPI);
buildData.TargetOptions.ExternalModules.AddRange(moduleOptions.ExternalModules);
buildData.TargetOptions.DependencyFiles.AddRange(moduleOptions.DependencyFiles);
buildData.TargetOptions.OptionalDependencyFiles.AddRange(moduleOptions.OptionalDependencyFiles);
}
}
}
}
// Build scripting API bindings
OnBuildBindings(buildData, graph);
// Link modules into a target
var outputTargetFilePath = target.GetOutputFilePath(targetBuildOptions);
var outputPath = Path.GetDirectoryName(outputTargetFilePath);
// Generate target build output info
using (new ProfileEventScope("GenerateBuildInfo"))
{
buildData.BuildInfo = new BuildTargetInfo
{
Name = target.Name,
Platform = platform.Target.ToString(),
Architecture = architecture.ToString(),
Configuration = configuration.ToString(),
HotReloadPostfix = targetBuildOptions.HotReloadPostfix,
BinaryModules = new BuildTargetBinaryModuleInfo[buildData.BinaryModules.Length + targetBuildOptions.ExternalModules.Count],
References = new BuildTargetReferenceInfo[buildData.ReferenceBuilds.Count],
};
int i;
// Binary modules
for (i = 0; i < buildData.BuildInfo.BinaryModules.Length; i++)
{
var binaryModule = buildData.BinaryModules[i];
var binaryModuleInfo = new BuildTargetBinaryModuleInfo
{
Name = binaryModule.Key,
ManagedPath = EngineConfiguration.WithCSharp(targetBuildOptions) ? Path.Combine(outputPath, binaryModule.Key + ".CSharp.dll") : string.Empty,
};
binaryModuleInfo.NativePathProcessed = string.Empty;
binaryModuleInfo.ManagedPathProcessed = BuildTargetInfo.ProcessPath(binaryModuleInfo.ManagedPath, project.ProjectFolderPath);
buildData.BuildInfo.BinaryModules[i] = binaryModuleInfo;
}
buildData.BuildInfo.AddExternalBinaryModules(ref i, project.ProjectFolderPath, targetBuildOptions.ExternalModules);
i = 0;
buildData.BuildInfo.AddReferencedBuilds(ref i, project.ProjectFolderPath, buildData.ReferenceBuilds);
if (!buildData.Target.IsPreBuilt)
Utilities.WriteFileIfChanged(Path.Combine(outputPath, target.Name + ".Build.json"), buildData.BuildInfo.Serialize());
}
// Deploy files
if (!buildData.Target.IsPreBuilt)
{
using (new ProfileEventScope("DeployFiles"))
{
foreach (var srcFile in targetBuildOptions.OptionalDependencyFiles.Where(File.Exists).Union(targetBuildOptions.DependencyFiles))
{
var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile));
graph.AddCopyFile(dstFile, srcFile);
}
}
}
using (new ProfileEventScope("PostBuild"))
{
// Post build
target.PostBuild(graph, targetBuildOptions);
}
return buildData;
}
}
}