// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Flax.Build.NativeCpp; namespace Flax.Build { partial class Builder { private static RulesAssembly _rules; private static Type[] _buildTypes; /// /// The build configuration files postfix. /// public static string BuildFilesPostfix = ".Build.cs"; /// /// The cached list of types from Flax.Build assembly. Reused by other build tool utilities to improve performance. /// internal static Type[] BuildTypes { get { if (_buildTypes == null) { using (new ProfileEventScope("CacheBuildTypes")) { _buildTypes = typeof(Program).Assembly.GetTypes(); } } return _buildTypes; } } /// /// The rules assembly data. /// public class RulesAssembly { private readonly Dictionary _modulesLookup; /// /// The rules assembly. /// public readonly Assembly Assembly; /// /// The targets objects. /// public readonly Target[] Targets; /// /// The modules objects. /// public readonly Module[] Modules; /// /// The plugin objects. /// public readonly Plugin[] Plugins; internal RulesAssembly(Assembly assembly, Target[] targets, Module[] modules, Plugin[] plugins) { Assembly = assembly; Targets = targets; Modules = modules; Plugins = plugins; _modulesLookup = new Dictionary(modules.Length); for (int i = 0; i < modules.Length; i++) { var module = modules[i]; _modulesLookup.Add(module.Name, module); } } /// /// Gets the target of the given name. /// /// The name. /// The target or null if not found. public Target GetTarget(string name) { return Targets.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); } /// /// Gets the module of the given name. /// /// The name. /// The module or null if not found. public Module GetModule(string name) { if (!_modulesLookup.TryGetValue(name, out var module)) module = Modules.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); return module; } } /// /// Gets the build options for the given target and the configuration. /// /// The target. /// The platform. /// The toolchain. /// The build architecture. /// The build configuration. /// The build workspace root folder path. /// The output binaries postfix added for hot-reload builds in Editor to prevent file names collisions. /// The build options. public static BuildOptions GetBuildOptions(Target target, Platform platform, Toolchain toolchain, TargetArchitecture architecture, TargetConfiguration configuration, string workingDirectory, string hotReloadPostfix = "") { var platformName = platform.Target.ToString(); var architectureName = architecture.ToString(); var configurationName = configuration.ToString(); var options = new BuildOptions { Target = target, Platform = platform, Toolchain = toolchain, Architecture = architecture, Configuration = configuration, CompileEnv = new CompileEnvironment(), LinkEnv = new LinkEnvironment(), IntermediateFolder = Path.Combine(workingDirectory, Configuration.IntermediateFolder, target.Name, platformName, architectureName, configurationName), OutputFolder = Path.Combine(workingDirectory, Configuration.BinariesFolder, target.Name, platformName, architectureName, configurationName), WorkingDirectory = workingDirectory, HotReloadPostfix = hotReloadPostfix, }; toolchain?.SetupEnvironment(options); target.SetupTargetEnvironment(options); return options; } /// /// Generates the rules assembly (from Module and Target files in the workspace directory). /// /// The compiled rules assembly. public static RulesAssembly GenerateRulesAssembly() { if (_rules != null) return _rules; using (new ProfileEventScope("InitInBuildPlugins")) { foreach (var type in BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Plugin)))) { var plugin = (Plugin)Activator.CreateInstance(type); plugin.Init(); } } using (new ProfileEventScope("GenerateRulesAssembly")) { // Find source files var files = new List(); using (new ProfileEventScope("FindRules")) { // Use rules from any of the included projects workspace if (Globals.Project != null) { var projects = Globals.Project.GetAllProjects(); foreach (var project in projects) { var sourceFolder = Path.Combine(project.ProjectFolderPath, "Source"); if (Directory.Exists(sourceFolder)) FindRules(sourceFolder, files); } } } // Log info if (Configuration.Verbose) { Log.Verbose("Build files:"); using (new LogIndentScope()) { foreach (var e in files) Log.Verbose(e); } } // Compile code Assembly assembly; using (new ProfileEventScope("CompileRules")) { var assembler = new Assembler(files, Path.Combine(Globals.Root, Configuration.IntermediateFolder)); EngineTarget.AddVersionDefines(assembler.PreprocessorSymbols); assembly = assembler.Build(); } // Prepare targets and modules objects Type[] types; var targetObjects = new List(16); var moduleObjects = new List(256); var pluginObjects = new List(); using (new ProfileEventScope("GetTypes")) { types = assembly.GetTypes(); for (var i = 0; i < types.Length; i++) { var type = types[i]; if (!type.IsClass || type.IsAbstract) continue; if (type.IsSubclassOf(typeof(Target))) { var target = (Target)Activator.CreateInstance(type); var targetFilename = target.Name + BuildFilesPostfix; target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); if (target.FilePath == null) { targetFilename = target.Name + "Target" + BuildFilesPostfix; target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); if (target.FilePath == null) { if (target.Name.EndsWith("Target")) { targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix; target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); } if (target.FilePath == null) { throw new Exception(string.Format("Failed to find source file path for {0}", target)); } } } target.FolderPath = Path.GetDirectoryName(target.FilePath); target.Init(); targetObjects.Add(target); } else if (type.IsSubclassOf(typeof(Module))) { var module = (Module)Activator.CreateInstance(type); var moduleFilename = module.Name + BuildFilesPostfix; module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); if (module.FilePath == null) { moduleFilename = module.Name + "Module" + BuildFilesPostfix; module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); if (module.FilePath == null) { throw new Exception(string.Format("Failed to find source file path for {0}", module)); } } module.FolderPath = Path.GetDirectoryName(module.FilePath); module.Init(); moduleObjects.Add(module); } else if (type.IsSubclassOf(typeof(Plugin))) { var plugin = (Plugin)Activator.CreateInstance(type); plugin.Init(); pluginObjects.Add(plugin); } } } _rules = new RulesAssembly(assembly, targetObjects.ToArray(), moduleObjects.ToArray(), pluginObjects.ToArray()); } return _rules; } private static void FindRules(string directory, List result) { // Optional way: result.AddRange(Directory.GetFiles(directory, '*' + BuildFilesPostfix, SearchOption.AllDirectories)); /* var files = Directory.GetFiles(directory); for (int i = 0; i < files.Length; i++) { var file = files[i]; if (file.EndsWith(BuildFilesPostfix, StringComparison.OrdinalIgnoreCase)) { result.Add(file); } } var directories = Directory.GetDirectories(directory); for (int i = 0; i < directories.Length; i++) { FindRules(directories[i], result); } */ } } }