8from pathlib
import Path
10CMAKELISTS_TEMPLATE =
'''\
11check_include_file_cxx(stdint.h HAVE_STDINT_H)
13 add_definitions(-DHAVE_STDINT_H)
16set(examples_as_tests_sources)
17if(${{ENABLE_EXAMPLES}})
18 set(examples_as_tests_sources
25 SOURCE_FILES model/{MODULE}.cc
26 helper/{MODULE}-helper.cc
27 HEADER_FILES model/{MODULE}.h
28 helper/{MODULE}-helper.h
29 LIBRARIES_TO_LINK ${{libcore}}
30 TEST_SOURCES test/{MODULE}-test-suite.cc
31 ${{examples_as_tests_sources}}
37MODEL_CC_TEMPLATE = '''\
49MODEL_H_TEMPLATE = '''\
64HELPER_CC_TEMPLATE = '''\
76HELPER_H_TEMPLATE = '''\
93EXAMPLES_CMAKELISTS_TEMPLATE = '''\
96 SOURCE_FILES {MODULE}-example.cc
97 LIBRARIES_TO_LINK ${{lib{MODULE}}}
102EXAMPLE_CC_TEMPLATE = '''\
109main(int argc, char* argv[])
113 CommandLine
cmd(__FILE__);
114 cmd.AddValue(
"verbose",
"Tell application to log if true", verbose);
116 cmd.Parse(argc, argv);
121 Simulator::Destroy();
127TEST_CC_TEMPLATE = '''\
129// Include a header file
from your module to test.
132// An essential include
is test.h
135// Do
not put your test classes
in namespace ns3. You may find it useful
136// to use the using directive to access the ns3 namespace directly
139// This
is an example TestCase.
140class {CAPITALIZED}TestCase1 : public TestCase
143 {CAPITALIZED}TestCase1();
144 virtual ~{CAPITALIZED}TestCase1();
147 void DoRun() override;
150// Add some help text to this case to describe what it
is intended to test
151{CAPITALIZED}TestCase1::{CAPITALIZED}TestCase1()
152 : TestCase(
"{CAPITALIZED} test case (does nothing)")
156// This destructor does nothing but we include it
as a reminder that
157// the test case should clean up after itself
158{CAPITALIZED}TestCase1::~{CAPITALIZED}TestCase1()
163// This method
is the pure virtual method
from class TestCase that every
164// TestCase must implement
167{CAPITALIZED}TestCase1::DoRun()
169 // A wide variety of test macros are available
in src/core/test.h
171 // Use this one
for floating point comparisons
175// The TestSuite
class names the TestSuite, identifies what
type of TestSuite,
176//
and enables the TestCases to be run. Typically, only the constructor
for
177// this
class must be defined
179class {CAPITALIZED}TestSuite : public TestSuite
182 {CAPITALIZED}TestSuite();
185{CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
186 : TestSuite(
"{MODULE}", UNIT)
188 // TestDuration
for TestCase can be QUICK, EXTENSIVE
or TAKES_FOREVER
189 AddTestCase(new {CAPITALIZED}TestCase1, TestCase::QUICK);
192// Do
not forget to allocate an instance of this TestSuite
193static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
197DOC_RST_TEMPLATE = '''Example Module Documentation
198----------------------------
200.. include:: replace.txt
204 ------------- Chapter
205 ************* Section (
206 ============= Subsection (
209This
is a suggested outline
for adding new module documentation to |ns3|.
210See ``src/click/doc/click.rst``
for an example.
212The introductory paragraph
is for describing what this code
is trying to
215For consistency (italicized formatting), please use |ns3| to refer to
216ns-3
in the documentation (
and likewise, |ns2|
for ns-2). These macros
217are defined
in the file ``replace.txt``.
222The source code
for the new module lives
in the directory ``{MODULE_DIR}``.
224Add here a basic description of what
is being modeled.
229Briefly describe the software design of the model
and how it fits into
230the existing ns-3 architecture.
235What can the model do? What can it
not do? Please use this section to
236describe the scope
and limitations of the model.
241Add academic citations here, such
as if you published a paper on this
242model,
or if readers should read a particular specification
or other work.
247This section
is principally concerned
with the usage of your model, using
248the public API. Focus first on most common usage patterns, then go
249into more advanced topics.
254Include this subsection only
if there are special build instructions
or
260What helper API will users typically use? Describe it here.
265What classes hold attributes,
and what are the key ones worth mentioning?
270What kind of data does the model generate? What are the key trace
271sources? What kind of logging output can be enabled?
276Go into further details (such
as using the API outside of the helpers)
277in additional sections,
as needed.
282What examples using this new code are available? Describe them here.
287Add any tips
for avoiding pitfalls, etc.
292Describe how the model has been tested/validated. What tests run
in the
293test suite? How much API
and code
is covered by the tests? Again,
294references to outside published work may help here.
297def create_file(path, template, **kwargs):
298 artifact_path = Path(path)
301 with artifact_path.open(
"wt")
as f:
302 f.write(template.format(**kwargs))
306 path = Path(moduledir,
'CMakeLists.txt')
308 create_file(path, CMAKELISTS_TEMPLATE, MODULE=modname)
314 modelpath = Path(moduledir,
"model")
315 modelpath.mkdir(parents=
True)
317 srcfile_path = modelpath.joinpath(modname).with_suffix(
'.cc')
318 create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
320 hfile_path = modelpath.joinpath(modname).with_suffix(
'.h')
321 guard =
"{}_H".format(modname.replace(
'-',
'_').upper())
330 testpath = Path(moduledir,
"test")
331 testpath.mkdir(parents=
True)
333 file_path = testpath.joinpath(modname+
'-test-suite').with_suffix(
'.cc')
334 name_parts = modname.split(
'-')
335 create_file(file_path, TEST_CC_TEMPLATE, MODULE=modname,
336 CAPITALIZED=
''.join([word.capitalize()
for word
in name_parts]),
337 COMPOUND=
''.join([word.capitalize()
if index > 0
else word
for index, word
in enumerate(name_parts)]))
343 helperpath = Path(moduledir,
"helper")
344 helperpath.mkdir(parents=
True)
346 srcfile_path = helperpath.joinpath(modname+
'-helper').with_suffix(
'.cc')
347 create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
349 h_file_path = helperpath.joinpath(modname+
'-helper').with_suffix(
'.h')
350 guard =
"{}_HELPER_H".format(modname.replace(
'-',
'_').upper())
351 create_file(h_file_path, HELPER_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
357 examplespath = Path(moduledir,
"examples")
358 examplespath.mkdir(parents=
True)
360 cmakelistspath = Path(examplespath,
'CMakeLists.txt')
361 create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
363 examplesfile_path = examplespath.joinpath(modname+
'-example').with_suffix(
'.cc')
364 create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
370 docpath = Path(moduledir,
"doc")
371 docpath.mkdir(parents=
True)
375 mod_relpath = os.path.relpath(str(moduledir))
377 file_name =
'{}.rst'.format(modname)
378 file_path = Path(docpath, file_name)
379 create_file(file_path, DOC_RST_TEMPLATE, MODULE=modname, MODULE_DIR=mod_relpath)
385 modulepath = Path(modpath, modname)
387 if modulepath.exists():
388 print(
"Module {!r} already exists".format(modname), file=sys.stderr)
391 print(
"Creating module {}".format(modulepath))
393 functions = (make_cmakelists, make_model, make_test,
394 make_helper, make_examples, make_doc)
397 modulepath.mkdir(parents=
True)
399 success = all(func(modulepath, modname)
for func
in functions)
402 raise ValueError(
"Generating module artifacts failed")
404 except Exception
as e:
405 if modulepath.exists():
406 shutil.rmtree(modulepath)
408 print(
"Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
415 description =
"""Generate scaffolding for ns-3 modules
417Generates the directory structure and skeleton files required for an ns-3
418module. All of the generated files are valid C/C++ and will compile successfully
419out of the box. ns3 configure must be run after creating new modules
in order
420to integrate them into the ns-3 build system.
422The following directory structure
is generated under the contrib directory:
428 |-- <modname>-example.cc
431 |-- <modname>-helper.cc
432 |-- <modname>-helper.h
437 |-- <modname>-test-suite.cc
440<modname>
is the name of the module
and is restricted to the following
441character groups: letters, numbers, -, _
442The script validates the module name
and skips modules that have characters
443outside of the above groups. One exception to the naming rule
is that src/
444or contrib/ may be added to the front of the module name to indicate where the
445module scaffold should be created. If the module name starts
with src/, then
446the module
is placed
in the src directory. If the module name starts
with
447contrib/, then the module
is placed
in the contrib directory. If the module
448name does
not start
with src/
or contrib/, then it defaults to contrib/.
449See the examples section
for use cases.
452In some situations it can be useful to group multiple related modules under one
453directory. Use the --project option to specify a common parent directory where
454the modules should be generated. The value passed to --project
is treated
455as a relative path. The path components have the same naming requirements
as
456the module name: letters, numbers, -, _
457The project directory
is placed under the contrib directory
and any parts of the
458path that do
not exist will be created. Creating projects
in the src directory
459is not supported. Module names that start
with src/ are
not allowed when
460--project
is used. Module names that start
with contrib/ are treated the same
461as module names that don
't start with contrib/ and are generated under the
465 epilog = """Examples:
467 %(prog)s contrib/module1
469 Creates a new module named module1 under the contrib directory
473 Creates a new module named module1 under the src directory
475 %(prog)s src/module1 contrib/module2, module3
477 Creates three modules, one under the src directory and two under the
480 %(prog)s --project myproject module1 module2
482 Creates two modules under contrib/myproject
484 %(prog)s --project myproject/sub_project module1 module2
486 Creates two modules under contrib/myproject/sub_project
490 formatter = argparse.RawDescriptionHelpFormatter
492 parser = argparse.ArgumentParser(description=description,
494 formatter_class=formatter)
496 parser.add_argument('--project', default=
'',
497 help=(
"Specify a relative path under the contrib directory "
498 "where the new modules will be generated. The path "
499 "will be created if it does not exist."))
501 parser.add_argument(
'modnames', nargs=
'+',
502 help=(
"One or more modules to generate. Module names "
503 "are limited to the following: letters, numbers, -, "
504 "_. Modules are generated under the contrib directory "
505 "except when the module name starts with src/. Modules "
506 "that start with src/ are generated under the src "
514 args = parser.parse_args(argv[1:])
516 project = args.project
517 modnames = args.modnames
519 base_path = Path.cwd()
521 src_path = base_path.joinpath(
'src')
522 contrib_path = base_path.joinpath(
'contrib')
524 for p
in (src_path, contrib_path):
526 parser.error(
"Cannot find the directory '{}'.\nPlease run this "
527 "script from the top level of the ns3 directory".format(
535 allowedRE = re.compile(
'^(\w|-)+$')
542 project_path = Path(project)
544 if project_path.is_absolute():
546 project_path = project_path.relative_to(os.sep)
548 if not all(allowedRE.match(part)
for part
in project_path.parts):
549 parser.error(
'Project path may only contain the characters [a-zA-Z0-9_-].')
554 for name
in modnames:
557 name = name.strip(os.sep)
563 name_path = Path(name)
565 if len(name_path.parts) > 2:
566 print(
"Skipping {}: module name can not be a path".format(name))
570 modpath = contrib_path
572 if name_path.parts[0] ==
'src':
574 parser.error(
"{}: Cannot specify src/ in a module name when --project option is used".format(name))
579 name_path = name_path.relative_to(
'src')
581 elif name_path.parts[0] ==
'contrib':
582 modpath = contrib_path
585 name_path = name_path.relative_to(
'contrib')
590 modpath = contrib_path.joinpath(project_path)
592 modname = name_path.parts[0]
594 if not allowedRE.match(modname):
595 print(
"Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(modname))
598 modules.append((modpath, modname))
600 if all(
make_module(*module)
for module
in modules):
602 print(
"Successfully created new modules")
603 print(
"Run './ns3 configure' to include them in the build")
607if __name__ ==
'__main__':
610 return_value = main(sys.argv)
611 except Exception
as e:
612 print(
"Exception: '{}'".format(e), file=sys.stderr)
615 sys.exit(return_value)
#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report and abort if not.
#define NS_TEST_ASSERT_MSG_EQ_TOL(actual, limit, tol, msg)
Test that actual and expected (limit) values are equal to plus or minus some tolerance and report and...
def make_module(modpath, modname)
def make_helper(moduledir, modname)
def make_test(moduledir, modname)
def create_file(path, template, **kwargs)
def make_model(moduledir, modname)
def make_examples(moduledir, modname)
def make_cmakelists(moduledir, modname)
def make_doc(moduledir, modname)
def create_argument_parser()