/* Copyright 2022 René Ferdinand Rivera Morell Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt) */ #ifndef B2_MOD_MODULES_H #define B2_MOD_MODULES_H #include "config.h" #include "bind.h" #include "lists.h" #include "value.h" #include "mod_path.h" #include /* tag::reference[] [[b2.reference.modules.modules]] = `modules` module. The `modules` module defines basic functionality for handling modules. A module defines a number of rules that can be used in other modules. Modules can contain code at the top level to initialize the module. This code is executed the first time the module is loaded. NOTE: A Jamfile is a special kind of module which is managed by the build system. Although they cannot be loaded directly by users, the other features of modules are still useful for Jamfiles. Each module has its own namespaces for variables and rules. If two modules A and B both use a variable named X, each one gets its own copy of X. They won't interfere with each other in any way. Similarly, importing rules into one module has no effect on any other module. Every module has two special variables. `$(__file__)` contains the name of the file that the module was loaded from and `$(__name__)` contains the name of the module. NOTE: `$(__file__)` does not contain the full path to the file. If you need this, use `modules.binding`. end::reference[] */ namespace b2 { namespace jam { namespace modules { /* tag::reference[] == `b2::modules::binding` ==== [horizontal] Jam:: `rule binding ( module_name )` {CPP}:: `value_ref binding(std::string module_name);` ==== Returns the filesystem binding of the given module. For example, a module can get its own location with: [source,jam] ---- me = [ modules.binding $(__name__) ] ; ---- end::reference[] */ list_ref binding(std::string module_name); /* tag::reference[] == `b2::modules::record_binding` ==== [horizontal] Jam:: `rule record-binding ( module_name : binding )` {CPP}:: `void record_binding(std::string module_name, value_ref value);` ==== This helper is used by load to record the binding (path) of each loaded module. WARNING: The `module_name` is ignored. Instead the internal tracking of the currently loading module is used to record the binding. end::reference[] */ void record_binding(std::string module_name, value_ref value); /* tag::reference[] == `b2::modules::poke` ==== [horizontal] Jam:: `rule poke ( module_name ? : variables + : value * )` {CPP}:: `void poke(std::string module_name, list_cref variables, list_cref value);` ==== Sets the module-local value of a variable. This is the most reliable way to set a module-local variable in a different module; it eliminates issues of name shadowing due to dynamic scoping. For example, to set a variable in the global module: [source,jam] ---- modules.poke : ZLIB_INCLUDE : /usr/local/include ; ---- end::reference[] */ void poke(std::string module_name, list_cref variables, list_cref value); /* tag::reference[] == `b2::modules::peek` ==== [horizontal] Jam:: `rule peek ( module_name ? : variables + )` {CPP}:: `list_ref peek(std::string module_name, list_cref variables);` ==== Returns the module-local value of a variable. This is the most reliable way to examine a module-local variable in a different module; it eliminates issues of name shadowing due to dynamic scoping. For example, to read a variable from the global module: [source,jam] ---- local ZLIB_INCLUDE = [ modules.peek : ZLIB_INCLUDE ] ; ---- end::reference[] */ list_ref peek(std::string module_name, list_cref variables); /* tag::reference[] == `b2::modules::clone_rules` ==== [horizontal] Jam:: `rule clone-rules ( source_module target_module )` {CPP}:: `void clone_rules(std::tuple source_target_modules);` ==== Define exported copies in `target_module` of all rules exported from `source_module`. Also make them available in the global module with qualification, so that it is just as though the rules were defined originally in `target_module`. end::reference[] */ void clone_rules(std::tuple source_target_modules); /* tag::reference[] == `b2::modules::call_in` ==== [horizontal] Jam:: `rule call-in ( module-name ? : rule-name args * : * )` {CPP}:: `list_ref call_in(value_ref module_name, std::tuple const lists & rest, bind::context_ref_ context_ref);` ==== Call the given rule locally in the given module. Use this for rules accepting rule names as arguments, so that the passed rule may be invoked in the context of the rule's caller (for example, if the rule accesses module globals or is a local rule). Note that rules called this way may accept at most 18 parameters. Example: [source,jam] ---- rule filter ( f : values * ) { local m = [ CALLER_MODULE ] ; local result ; for v in $(values) { if [ modules.call-in $(m) : $(f) $(v) ] { result += $(v) ; } } return result ; } ---- end::reference[] */ list_ref call_in(value_ref module_name, std::tuple rule_name_a1, const lists & rest, bind::context_ref_ context_ref); /* tag::reference[] == `b2::modules::call_locally` ==== [horizontal] Jam:: `rule call-locally ( qualified-rule-name args * : * )` {CPP}:: `list_ref call_locally(std::tuple rule_name_a1, const lists & rest, const bind::context_ref_ & context_ref);` ==== Given a possibly qualified rule name and arguments, remove any initial module qualification from the rule and invoke it in that module. If there is no module qualification, the rule is invoked in the global module. Note that rules called this way may accept at most 18 parameters. end::reference[] */ list_ref call_locally(std::tuple rule_name_a1, const lists & rest, bind::context_ref_ context_ref); /* tag::reference[] == `b2::modules::run_tests` ==== [horizontal] Jam:: `rule run-tests ( m )` {CPP}:: `void run_tests(value_ref m, bind::context_ref_ context_ref);` ==== Runs internal B2 unit tests for the specified module. The module's `__test__` rule is executed in its own module to eliminate any inadvertent effects of testing module dependencies (such as assert) on the module itself. end::reference[] */ void run_tests(value_ref m, bind::context_ref_ context_ref); /* tag::reference[] == `b2::modules::load` ==== [horizontal] Jam:: `rule load ( module-name : filename ? : search * )` {CPP}:: `void load(value_ref module_name, value_ref filename, list_cref search, bind::context_ref_ context_ref);` ==== Load the indicated module if it is not already loaded. `module-name`:: Name of module to load. `filename`:: (partial) path to file; Defaults to `$(module-name).jam` `search`:: Directories in which to search for filename. Defaults to `$(BOOST_BUILD_PATH)`. end::reference[] */ void load(value_ref module_name, value_ref filename, list_cref search, bind::context_ref_ context_ref); /* tag::reference[] == `b2::modules::import` ==== [horizontal] Jam:: `rule import ( module-names + : rules-opt * : rename-opt * )` {CPP}:: `void import(list_cref module_names, list_cref rules_opt, list_cref rename_opt, bind::context_ref_ context_ref);` ==== Load the indicated module and import rule names into the current module. Any members of `rules-opt` will be available without qualification in the caller's module. Any members of `rename-opt` will be taken as the names of the rules in the caller's module, in place of the names they have in the imported module. If `rules-opt` = `*`, all rules from the indicated module are imported into the caller's module. If `rename-opt` is supplied, it must have the same number of elements as `rules-opt`. NOTE: The `import` rule is available without qualification in all modules. Examples: [source,jam] ---- import path ; import path : * ; import path : join ; import path : native make : native-path make-path ; ---- end::reference[] */ void import(list_cref module_names, list_cref rules_opt, list_cref rename_opt, bind::context_ref_ context_ref); struct modules_module : b2::bind::module_ { const char * module_name = "modules"; static const char * init_code; template void def(Binder & binder) { binder.def(&binding, "binding", "module_name" * _1) .def(&record_binding, "record-binding", "module_name" * _1 | "value" * _1) .def(&poke, "poke", "module_name" * _01 | "variables" * _1n | "value" * _n) .def(&peek, "peek", "module_name" * _01 | "variables" * _1n) .def(&clone_rules, "clone-rules", "source_module" * _1 + "target_module" * _1) .def(&call_in, "call-in", "module_name" * _01 | ("rule_name" * _1 + "a1" * _n) | "rest" * _r) .def(&call_locally, "call-locally", ("rule_name" * _1 + "a1" * _n) | "rest" * _r) .def(&b2::paths::normalize_all, "normalize-paths", "paths" * _n) .def(&run_tests, "run-tests", "m" * _1) .def(&load, "load", "module-name" * _1 | "filename" * _01 | "search" * _n) .def(&import, "import", "module-names" * _1n | "rules-opt" * _n | "rename-opt" * _n); binder.eval(init_code); binder.loaded(); } }; }}} // namespace b2::jam::modules #endif