// Copyright (c) 2012-2021 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; /// /// The build configuration files postfix. /// public static string BuildFilesPostfix = ".Build.cs"; /// /// 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 typeof(Program).Assembly.GetTypes().Where(x => !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(); assembler.SourceFiles.AddRange(files); assembly = assembler.Build(); } // Prepare targets and modules objects Type[] types; Target[] targetObjects; Module[] moduleObjects; Plugin[] pluginObjects; using (new ProfileEventScope("GetTypes")) { types = assembly.GetTypes(); targetObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Target))).Select(type => { 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(); return target; }).ToArray(); moduleObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Module))).Select(type => { 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(); return module; }).ToArray(); pluginObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin))).Select(type => { var plugin = (Plugin)Activator.CreateInstance(type); plugin.Init(); return plugin; }).ToArray(); } _rules = new RulesAssembly(assembly, targetObjects, moduleObjects, pluginObjects); } 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); } } } }