Skip to content

Commit 2716c54

Browse files
author
Andy Schwerin
committed
SERVER-7118 Change "mongo modules" to use SConscript files for modules.
Also allows you to have modules in mongos and the shell, as well as mongod. Requires changes to the modules, to have SConscript files, and define libraries. Allows modules to have unit tests, interesting linking rules, dependencies into mongo, etc. Still may need to do some work on includes. The mongo-enterprise module has very simple include requirements, today.
1 parent 435ad16 commit 2716c54

File tree

5 files changed

+135
-123
lines changed

5 files changed

+135
-123
lines changed

SConscript.buildinfo

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ namespace mongo {
3232

3333
def generate_buildinfo(env, target, source, **kw):
3434
git_version = buildscripts.utils.getGitVersion()
35-
if env["MONGO_MODULES"]:
36-
git_version += " modules: %s" % (", ".join(env["MONGO_MODULES"].keys()))
35+
if len(env["MONGO_MODULES"]):
36+
git_version += " modules: " + ", ".join(env["MONGO_MODULES"])
3737

3838
contents = str(source[0]) % dict(git_version=git_version,
3939
sys_info=getSysInfo())

SConstruct

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ env = Environment( BUILD_DIR=variantDir,
282282
CLIENT_SCONSTRUCT='#distsrc/client/SConstruct',
283283
DIST_ARCHIVE_SUFFIX='.tgz',
284284
EXTRAPATH=get_option("extrapath"),
285+
MODULE_LIBDEPS_MONGOD=[],
286+
MODULE_LIBDEPS_MONGOS=[],
287+
MODULE_LIBDEPS_MONGOSHELL=[],
285288
MODULETEST_LIST='#build/moduletests.txt',
286289
MSVS_ARCH=msarch ,
287290
PYTHON=utils.find_python(),
@@ -768,8 +771,13 @@ if not use_system_version_of_library("boost"):
768771
env.Append( CPPPATH=['$EXTRACPPPATH'],
769772
LIBPATH=['$EXTRALIBPATH'] )
770773

774+
# discover modules, and load the (python) module for each module's build.py
775+
mongo_modules = moduleconfig.discover_modules('src/mongo/db/modules')
776+
env['MONGO_MODULES'] = [m.name for m in mongo_modules]
777+
771778
# --- check system ---
772779

780+
773781
def doConfigure(myenv):
774782
conf = Configure(myenv)
775783

@@ -847,13 +855,8 @@ def doConfigure(myenv):
847855
myenv.Append( CPPDEFINES=[ "HEAP_CHECKING" ] )
848856
myenv.Append( CCFLAGS=["-fno-omit-frame-pointer"] )
849857

850-
# discover modules (subdirectories of db/modules/), and
851-
# load the (python) module for each module's build.py
852-
modules = moduleconfig.discover_modules('src/mongo/')
853-
854-
# ask each module to configure itself, and return a
855-
# dictionary of name => list_of_sources for each module.
856-
env["MONGO_MODULES"] = moduleconfig.configure_modules(modules, conf, env)
858+
# ask each module to configure itself and the build environment.
859+
moduleconfig.configure_modules(mongo_modules, conf, env)
857860

858861
return conf.Finish()
859862

@@ -938,8 +941,8 @@ def getSystemInstallName():
938941
if nix and os.uname()[2].startswith("8."):
939942
n += "-tiger"
940943

941-
if len(env.get("MONGO_MODULES", None)):
942-
n += "-" + "-".join(env["MONGO_MODULES"].keys())
944+
if len(mongo_modules):
945+
n += "-" + "-".join(m.name for m in mongo_modules)
943946

944947
try:
945948
findSettingsSetup()
@@ -1081,6 +1084,8 @@ if not use_system_version_of_library("boost"):
10811084
clientEnv.Append(LIBS=['boost_thread', 'boost_filesystem', 'boost_system'])
10821085
clientEnv.Prepend(LIBPATH=['$BUILD_DIR/third_party/boost/'])
10831086

1087+
module_sconscripts = moduleconfig.get_module_sconscripts(mongo_modules)
1088+
10841089
# The following symbols are exported for use in subordinate SConscript files.
10851090
# Ideally, the SConscript files would be purely declarative. They would only
10861091
# import build environment objects, and would contain few or no conditional
@@ -1097,10 +1102,11 @@ Export("has_option use_system_version_of_library")
10971102
Export("installSetup")
10981103
Export("usesm usev8")
10991104
Export("darwin windows solaris linux nix")
1105+
Export('module_sconscripts')
11001106

1101-
env.SConscript( 'src/SConscript', variant_dir='$BUILD_DIR', duplicate=False )
1102-
env.SConscript( 'src/SConscript.client', variant_dir='$BUILD_DIR/client_build', duplicate=False )
1103-
env.SConscript( ['SConscript.buildinfo', 'SConscript.smoke'] )
1107+
env.SConscript('src/SConscript', variant_dir='$BUILD_DIR', duplicate=False)
1108+
env.SConscript('src/SConscript.client', variant_dir='$BUILD_DIR/client_build', duplicate=False)
1109+
env.SConscript(['SConscript.buildinfo', 'SConscript.smoke'])
11041110

11051111
def clean_old_dist_builds(env, target, source):
11061112
prefix = "mongodb-%s-%s" % (platform, processor)

buildscripts/moduleconfig.py

Lines changed: 107 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,143 @@
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.
1525
"""
1626

1727
__all__ = ('discover_modules', 'configure_modules', 'register_module_test')
1828

1929
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.
2837
"""
29-
found_modules = {}
38+
found_modules = []
3039

31-
module_root = abspath(join(mongo_root, 'db', 'modules'))
32-
if not isdir(module_root):
40+
if not os.path.isdir(module_root):
3341
return found_modules
3442

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):
3846
continue
3947

40-
build_py = join(root, 'build.py')
48+
build_py = os.path.join(root, 'build.py')
4149
module = None
4250

43-
if isfile(build_py):
51+
if os.path.isfile(build_py):
4452
print "adding module: %s" % name
4553
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()
4962

5063
return found_modules
5164

5265
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).
7168
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
7373
print "configuring module: %s" % name
7474

75-
root = dirname(module.__file__)
76-
module_sources = []
75+
root = os.path.dirname(module.__file__)
76+
module.configure(conf, env)
7777

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
8784

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.
9287
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))
94105

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.
96108
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))
102113

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.
104116
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))
107121

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
109126
"""
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.
112131
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
117133
"""
118-
if not module_tests:
119-
return
120134

121-
folder = dirname(test_file)
122-
if not isdir(folder):
123-
makedirs(folder)
135+
return __get_module_build_path(1)
124136

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.
131140
141+
May only meaningfully be called from within build.py
142+
"""
143+
return os.path.join(__get_module_build_path(1), libdep_rel_path)

src/SConscript

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
# This is the principle SConscript file, invoked by the SConstruct. Its job is
44
# to delegate to any and all per-module SConscript files.
55

6-
SConscript( [ 'mongo/SConscript',
7-
'third_party/SConscript' ] )
6+
Import('module_sconscripts')
7+
8+
SConscript(['mongo/SConscript',
9+
'third_party/SConscript'] +
10+
module_sconscripts)

src/mongo/SConscript

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -415,15 +415,6 @@ env.StaticLibrary("notmongodormongos", everythingButMongodAndMongosFiles)
415415

416416
mongodOnlyFiles = [ "db/db.cpp", "db/compact.cpp", "db/commands/touch.cpp" ]
417417

418-
# create a library per module, and add it as a dependency
419-
# for the mongod target; as of now, modules are only included
420-
# in mongod, as though they were part of serverOnlyFiles
421-
modules = []
422-
for modName, modSources in env["MONGO_MODULES"].items():
423-
libName = "mod%s" % modName
424-
env.StaticLibrary(libName, modSources)
425-
modules.append(libName)
426-
427418
# ----- TARGETS ------
428419

429420
env.StaticLibrary("gridfs", "client/gridfs.cpp")
@@ -443,7 +434,7 @@ mongod = env.Install(
443434
"mongodandmongos",
444435
"ntservice",
445436
"serveronly",
446-
] + modules ) )
437+
] + env['MODULE_LIBDEPS_MONGOD'] ) )
447438
Default( mongod )
448439

449440
# tools
@@ -473,7 +464,7 @@ env.Install( '#/', [
473464
mongos = env.Program(
474465
"mongos", [ "s/server.cpp"] ,
475466
LIBDEPS=["mongoscore", "coreserver", "coredb", "mongocommon", "coreshard", "dbcmdline", "ntservice",
476-
"mongodandmongos", "db/auth/auth"])
467+
"mongodandmongos", "db/auth/auth"] + env['MODULE_LIBDEPS_MONGOS'])
477468
env.Install( '#/', mongos )
478469

479470
env.Library("clientandshell", ["client/clientAndShell.cpp"], LIBDEPS=["mongocommon", "defaultversion", "gridfs", "notmongodormongos"])
@@ -551,7 +542,7 @@ if shellEnv is not None:
551542
"mongo",
552543
coreShellFiles,
553544
LIBDEPS=["coreserver", "clientandshell",
554-
"$BUILD_DIR/third_party/pcrecpp"] )
545+
"$BUILD_DIR/third_party/pcrecpp"] + env['MODULE_LIBDEPS_MONGOSHELL'] )
555546

556547
shellEnv.Install( '#/', mongo_shell )
557548

0 commit comments

Comments
 (0)