|
1 | | -"""Utility functions for SCons to discover and configure |
2 | | -MongoDB modules (sub-trees of db/modules/). This file exports |
3 | | -two functions: |
4 | | -
|
5 | | - discover_modules, which returns a dictionary of module name |
6 | | - to the imported python module object for the module's |
7 | | - build.py file |
8 | | -
|
9 | | - configure_modules, which runs per-module configuration, and |
10 | | - is given the SCons environment, its own path, etc |
11 | | -
|
12 | | -Each module must have a "build.py" script, which is expected to |
13 | | -have a "configure" function, and optionally a "test" function |
14 | | -if the module exposes per-module tests. |
| 1 | +"""Utility functions for SCons to discover and configure MongoDB modules. |
| 2 | +
|
| 3 | +A MongoDB module is an organized collection of source code and build rules that can be provided at |
| 4 | +compile-time to alter or extend the behavior of MongoDB. The files comprising a single MongoDB |
| 5 | +module are arranged in a directory hierarchy, rooted in a directory whose name is by convention the |
| 6 | +module name, and containing in that root directory at least two files: a build.py file and a |
| 7 | +SConscript file. |
| 8 | +
|
| 9 | +MongoDB modules are discovered by a call to the discover_modules() function, whose sole parameter is |
| 10 | +the directory which is the immediate parent of all module directories. The exact directory is |
| 11 | +chosen by the SConstruct file, which is the direct consumer of this python module. The only rule is |
| 12 | +that it must be a subdirectory of the src/ directory, to correctly work with the SCons variant |
| 13 | +directory system that separates build products for source. |
| 14 | +
|
| 15 | +Once discovered, modules are configured by the configure_modules() function, and the build system |
| 16 | +integrates their SConscript files into the rest of the build. |
| 17 | +
|
| 18 | +MongoDB module build.py files implement a single function, configure(conf, env), which they may use |
| 19 | +to configure the supplied "env" object. The configure functions may add extra LIBDEPS to mongod, |
| 20 | +mongos and the mongo shell (TODO: other mongo tools and the C++ client), and through those libraries |
| 21 | +alter those programs' behavior. |
| 22 | +
|
| 23 | +MongoDB module SConscript files can describe libraries, programs and unit tests, just as other |
| 24 | +MongoDB SConscript files do. |
15 | 25 | """ |
16 | 26 |
|
17 | 27 | __all__ = ('discover_modules', 'configure_modules', 'register_module_test') |
18 | 28 |
|
19 | 29 | import imp |
20 | | -from os import listdir, makedirs |
21 | | -from os.path import abspath, dirname, join, isdir, isfile |
22 | | - |
23 | | -def discover_modules(mongo_root): |
24 | | - """Scan <mongo_root>/db/modules/ for directories that |
25 | | - look like MongoDB modules (i.e. they contain a "build.py" |
26 | | - file), and return a dictionary of module name (the directory |
27 | | - name) to build.py python modules. |
| 30 | +import inspect |
| 31 | +import os |
| 32 | + |
| 33 | +def discover_modules(module_root): |
| 34 | + """Scans module_root for subdirectories that look like MongoDB modules. |
| 35 | +
|
| 36 | + Returns a list of imported build.py module objects. |
28 | 37 | """ |
29 | | - found_modules = {} |
| 38 | + found_modules = [] |
30 | 39 |
|
31 | | - module_root = abspath(join(mongo_root, 'db', 'modules')) |
32 | | - if not isdir(module_root): |
| 40 | + if not os.path.isdir(module_root): |
33 | 41 | return found_modules |
34 | 42 |
|
35 | | - for name in listdir(module_root): |
36 | | - root = join(module_root, name) |
37 | | - if '.' in name or not isdir(root): |
| 43 | + for name in os.listdir(module_root): |
| 44 | + root = os.path.join(module_root, name) |
| 45 | + if name.startswith('.') or not os.path.isdir(root): |
38 | 46 | continue |
39 | 47 |
|
40 | | - build_py = join(root, 'build.py') |
| 48 | + build_py = os.path.join(root, 'build.py') |
41 | 49 | module = None |
42 | 50 |
|
43 | | - if isfile(build_py): |
| 51 | + if os.path.isfile(build_py): |
44 | 52 | print "adding module: %s" % name |
45 | 53 | fp = open(build_py, "r") |
46 | | - module = imp.load_module("module_" + name, fp, build_py, (".py", "r", imp.PY_SOURCE)) |
47 | | - found_modules[name] = module |
48 | | - fp.close() |
| 54 | + try: |
| 55 | + module = imp.load_module("module_" + name, fp, build_py, |
| 56 | + (".py", "r", imp.PY_SOURCE)) |
| 57 | + if getattr(module, "name", None) is None: |
| 58 | + module.name = name |
| 59 | + found_modules.append(module) |
| 60 | + finally: |
| 61 | + fp.close() |
49 | 62 |
|
50 | 63 | return found_modules |
51 | 64 |
|
52 | 65 | def configure_modules(modules, conf, env): |
53 | | - """ |
54 | | - Run the configure() function in the build.py python modules |
55 | | - for each module listed in the modules dictionary (as created |
56 | | - by discover_modules). The configure() function should use the |
57 | | - prepare the Mongo build system for building the module. |
58 | | -
|
59 | | - build.py files may specify a "customIncludes" flag, which, if |
60 | | - True, causes configure() to be called with three arguments: |
61 | | - the SCons Configure() object, the SCons environment, and an |
62 | | - empty list which should be modified in-place by the configure() |
63 | | - function; if false, configure() is called with only the first |
64 | | - two arguments, and the source files are discovered with a |
65 | | - glob against the <module_root>/src/*.cpp. |
66 | | -
|
67 | | - Returns a dictionary mapping module name to a list of source |
68 | | - files to be compiled for the module. |
69 | | - """ |
70 | | - source_map = {} |
| 66 | + """ Run the configure() function in the build.py python modules for each module in "modules" |
| 67 | + (as created by discover_modules). |
71 | 68 |
|
72 | | - for name, module in modules.items(): |
| 69 | + The configure() function should prepare the Mongo build system for building the module. |
| 70 | + """ |
| 71 | + for module in modules: |
| 72 | + name = module.name |
73 | 73 | print "configuring module: %s" % name |
74 | 74 |
|
75 | | - root = dirname(module.__file__) |
76 | | - module_sources = [] |
| 75 | + root = os.path.dirname(module.__file__) |
| 76 | + module.configure(conf, env) |
77 | 77 |
|
78 | | - if getattr(module, "customIncludes", False): |
79 | | - # then the module configures itself and its |
80 | | - # configure() takes 3 args |
81 | | - module.configure(conf, env, module_sources) |
82 | | - else: |
83 | | - # else we glob the files in the module's src/ |
84 | | - # subdirectory, and its configure() takes 2 args |
85 | | - module.configure(conf, env) |
86 | | - module_sources.extend(env.Glob(join(root, "src/*.cpp"))) |
| 78 | +def get_module_sconscripts(modules): |
| 79 | + sconscripts = [] |
| 80 | + for m in modules: |
| 81 | + module_dir_path = __get_src_relative_path(os.path.join(os.path.dirname(m.__file__))) |
| 82 | + sconscripts.append(os.path.join(module_dir_path, 'SConscript')) |
| 83 | + return sconscripts |
87 | 84 |
|
88 | | - if not module_sources: |
89 | | - print "WARNING: no source files for module %s, module will not be built." % name |
90 | | - else: |
91 | | - source_map[name] = module_sources |
| 85 | +def __get_src_relative_path(path): |
| 86 | + """Return a path relative to ./src. |
92 | 87 |
|
93 | | - _setup_module_tests_file(str(env.File(env['MODULETEST_LIST']))) |
| 88 | + The src directory is important because of its relationship to BUILD_DIR, |
| 89 | + established in the SConstruct file. For variant directories to work properly |
| 90 | + in SCons, paths relative to the src or BUILD_DIR must often be generated. |
| 91 | + """ |
| 92 | + src_dir = os.path.abspath('src') |
| 93 | + path = os.path.abspath(os.path.normpath(path)) |
| 94 | + if not path.startswith(src_dir): |
| 95 | + raise ValueError('Path "%s" is not relative to the src directory "%s"' % (path, src_dir)) |
| 96 | + result = path[len(src_dir) + 1:] |
| 97 | + return result |
| 98 | + |
| 99 | +def __get_module_path(module_frame_depth): |
| 100 | + """Return the path to the MongoDB module whose build.py is executing "module_frame_depth" frames |
| 101 | + above this function, relative to the "src" directory. |
| 102 | + """ |
| 103 | + module_filename = inspect.stack()[module_frame_depth + 1][1] |
| 104 | + return os.path.dirname(__get_src_relative_path(module_filename)) |
94 | 105 |
|
95 | | - return source_map |
| 106 | +def __get_module_src_path(module_frame_depth): |
| 107 | + """Return the path relative to the SConstruct file of the MongoDB module's source tree. |
96 | 108 |
|
97 | | -module_tests = [] |
98 | | -def register_module_test(*command): |
99 | | - """Modules can register tests as part of their configure(), which |
100 | | - are commands whose exit status indicates the success or failure of |
101 | | - the test. |
| 109 | + module_frame_depth is the number of frames above the current one in which one can find a |
| 110 | + function from the MongoDB module's build.py function. |
| 111 | + """ |
| 112 | + return os.path.join('src', __get_module_path(module_frame_depth + 1)) |
102 | 113 |
|
103 | | - Use this function from configure() like: |
| 114 | +def __get_module_build_path(module_frame_depth): |
| 115 | + """Return the path relative to the SConstruct file of the MongoDB module's build tree. |
104 | 116 |
|
105 | | - register_module_test('/usr/bin/python', '/path/to/module/tests/foo.py') |
106 | | - register_module_test('/bin/bash', '/path/to/module/tests/bar.sh') |
| 117 | + module_frame_depth is the number of frames above the current one in which one can find a |
| 118 | + function from the MongoDB module's build.py function. |
| 119 | + """ |
| 120 | + return os.path.join('$BUILD_DIR', __get_module_path(module_frame_depth + 1)) |
107 | 121 |
|
108 | | - The registered test commands can be run with "scons smokeModuleTests" |
| 122 | +def get_current_module_src_path(): |
| 123 | + """Return the path relative to the SConstruct file of the current MongoDB module's source tree. |
| 124 | +
|
| 125 | + May only meaningfully be called from within build.py |
109 | 126 | """ |
110 | | - command = ' '.join(command) |
111 | | - module_tests.append(command) |
| 127 | + return __get_module_src_path(1) |
| 128 | + |
| 129 | +def get_current_module_build_path(): |
| 130 | + """Return the path relative to the SConstruct file of the current MongoDB module's build tree. |
112 | 131 |
|
113 | | -def _setup_module_tests_file(test_file): |
114 | | - """Modules' configure() functions may have called register_module_test, |
115 | | - in which case, we need to record the registered tests' commands into |
116 | | - a text file which smoke.py and SCons know how to work with. |
| 132 | + May only meaningfully be called from within build.py |
117 | 133 | """ |
118 | | - if not module_tests: |
119 | | - return |
120 | 134 |
|
121 | | - folder = dirname(test_file) |
122 | | - if not isdir(folder): |
123 | | - makedirs(folder) |
| 135 | + return __get_module_build_path(1) |
124 | 136 |
|
125 | | - fp = file(test_file, 'w') |
126 | | - for test in module_tests: |
127 | | - fp.write(test) |
128 | | - fp.write('\n') |
129 | | - fp.close() |
130 | | - print "Generated %s" % test_file |
| 137 | +def get_current_module_libdep_name(libdep_rel_path): |
| 138 | + """Return a $BUILD_DIR relative path to a "libdep_rel_path", where "libdep_rel_path" |
| 139 | + is specified relative to the MongoDB module's build.py file. |
131 | 140 |
|
| 141 | + May only meaningfully be called from within build.py |
| 142 | + """ |
| 143 | + return os.path.join(__get_module_build_path(1), libdep_rel_path) |
0 commit comments