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 = '''\
53// Add a doxygen group
for this module.
54// If you have more than one file, this should be
in only one of them.
56 * \defgroup {MODULE} Description of the {MODULE}
62// Each
class should be documented using Doxygen,
63//
and have an \ingroup {MODULE} directive
73HELPER_CC_TEMPLATE = '''\
85HELPER_H_TEMPLATE = '''\
94// Each
class should be documented using Doxygen,
95//
and have an \ingroup {MODULE} directive
105EXAMPLES_CMAKELISTS_TEMPLATE = '''\
107 NAME {MODULE}-example
108 SOURCE_FILES {MODULE}-example.cc
109 LIBRARIES_TO_LINK ${{lib{MODULE}}}
114EXAMPLE_CC_TEMPLATE = '''\
121 * Explain here what the example does.
127main(int argc, char* argv[])
131 CommandLine cmd(__FILE__);
132 cmd.AddValue(
"verbose",
"Tell application to log if true", verbose);
134 cmd.Parse(argc, argv);
139 Simulator::Destroy();
145TEST_CC_TEMPLATE = '''\
147// Include a header file
from your module to test.
150// An essential include
is test.h
153// Do
not put your test classes
in namespace ns3. You may find it useful
154// to use the using directive to access the ns3 namespace directly
157// Add a doxygen group
for tests.
158// If you have more than one test, this should be
in only one of them.
160 * \defgroup {MODULE}-tests Tests
for {MODULE}
165// This
is an example TestCase.
167 * \ingroup {MODULE}-tests
168 * Test case
for feature 1
170class {CAPITALIZED}TestCase1 : public TestCase
173 {CAPITALIZED}TestCase1();
174 virtual ~{CAPITALIZED}TestCase1();
177 void DoRun() override;
180// Add some help text to this case to describe what it
is intended to test
181{CAPITALIZED}TestCase1::{CAPITALIZED}TestCase1()
182 : TestCase(
"{CAPITALIZED} test case (does nothing)")
186// This destructor does nothing but we include it
as a reminder that
187// the test case should clean up after itself
188{CAPITALIZED}TestCase1::~{CAPITALIZED}TestCase1()
193// This method
is the pure virtual method
from class TestCase that every
194// TestCase must implement
197{CAPITALIZED}TestCase1::DoRun()
199 // A wide variety of test macros are available
in src/core/test.h
201 // Use this one
for floating point comparisons
205// The TestSuite
class names the TestSuite, identifies what type of TestSuite,
206//
and enables the TestCases to be run. Typically, only the constructor
for
207// this
class must be defined
210 * \ingroup {MODULE}-tests
211 * TestSuite
for module {MODULE}
213class {CAPITALIZED}TestSuite : public TestSuite
216 {CAPITALIZED}TestSuite();
219{CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
220 : TestSuite(
"{MODULE}", UNIT)
222 // TestDuration
for TestCase can be QUICK, EXTENSIVE
or TAKES_FOREVER
223 AddTestCase(new {CAPITALIZED}TestCase1, TestCase::QUICK);
226// Do
not forget to allocate an instance of this TestSuite
228 * \ingroup {MODULE}-tests
229 * Static variable
for test initialization
231static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
235DOC_RST_TEMPLATE = '''Example Module Documentation
236----------------------------
238.. include:: replace.txt
242 ------------- Chapter
243 ************* Section (
244 ============= Subsection (
247This
is a suggested outline
for adding new module documentation to |ns3|.
248See ``src/click/doc/click.rst``
for an example.
250The introductory paragraph
is for describing what this code
is trying to
253For consistency (italicized formatting), please use |ns3| to refer to
254ns-3
in the documentation (
and likewise, |ns2|
for ns-2). These macros
255are defined
in the file ``replace.txt``.
260The source code
for the new module lives
in the directory ``{MODULE_DIR}``.
262Add here a basic description of what
is being modeled.
267Briefly describe the software design of the model
and how it fits into
268the existing ns-3 architecture.
273What can the model do? What can it
not do? Please use this section to
274describe the scope
and limitations of the model.
279Add academic citations here, such
as if you published a paper on this
280model,
or if readers should read a particular specification
or other work.
285This section
is principally concerned
with the usage of your model, using
286the public API. Focus first on most common usage patterns, then go
287into more advanced topics.
292Include this subsection only
if there are special build instructions
or
298What helper API will users typically use? Describe it here.
303What classes hold attributes,
and what are the key ones worth mentioning?
308What kind of data does the model generate? What are the key trace
309sources? What kind of logging output can be enabled?
314Go into further details (such
as using the API outside of the helpers)
315in additional sections,
as needed.
320What examples using this new code are available? Describe them here.
325Add any tips
for avoiding pitfalls, etc.
330Describe how the model has been tested/validated. What tests run
in the
331test suite? How much API
and code
is covered by the tests? Again,
332references to outside published work may help here.
335def create_file(path, template, **kwargs):
336 artifact_path = Path(path)
339 with artifact_path.open(
"wt")
as f:
340 f.write(template.format(**kwargs))
344 path = Path(moduledir,
'CMakeLists.txt')
346 create_file(path, CMAKELISTS_TEMPLATE, MODULE=modname)
352 modelpath = Path(moduledir,
"model")
353 modelpath.mkdir(parents=
True)
355 srcfile_path = modelpath.joinpath(modname).with_suffix(
'.cc')
356 create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
358 hfile_path = modelpath.joinpath(modname).with_suffix(
'.h')
359 guard =
"{}_H".format(modname.replace(
'-',
'_').upper())
368 testpath = Path(moduledir,
"test")
369 testpath.mkdir(parents=
True)
371 file_path = testpath.joinpath(modname+
'-test-suite').with_suffix(
'.cc')
372 name_parts = modname.split(
'-')
373 create_file(file_path, TEST_CC_TEMPLATE, MODULE=modname,
374 CAPITALIZED=
''.join([word.capitalize()
for word
in name_parts]),
375 COMPOUND=
''.join([word.capitalize()
if index > 0
else word
for index, word
in enumerate(name_parts)]))
381 helperpath = Path(moduledir,
"helper")
382 helperpath.mkdir(parents=
True)
384 srcfile_path = helperpath.joinpath(modname+
'-helper').with_suffix(
'.cc')
385 create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
387 h_file_path = helperpath.joinpath(modname+
'-helper').with_suffix(
'.h')
388 guard =
"{}_HELPER_H".format(modname.replace(
'-',
'_').upper())
389 create_file(h_file_path, HELPER_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
395 examplespath = Path(moduledir,
"examples")
396 examplespath.mkdir(parents=
True)
398 cmakelistspath = Path(examplespath,
'CMakeLists.txt')
399 create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
401 examplesfile_path = examplespath.joinpath(modname+
'-example').with_suffix(
'.cc')
402 create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
408 docpath = Path(moduledir,
"doc")
409 docpath.mkdir(parents=
True)
413 mod_relpath = os.path.relpath(str(moduledir))
415 file_name =
'{}.rst'.format(modname)
416 file_path = Path(docpath, file_name)
417 create_file(file_path, DOC_RST_TEMPLATE, MODULE=modname, MODULE_DIR=mod_relpath)
423 modulepath = Path(modpath, modname)
425 if modulepath.exists():
426 print(
"Module {!r} already exists".format(modname), file=sys.stderr)
429 print(
"Creating module {}".format(modulepath))
431 functions = (make_cmakelists, make_model, make_test,
432 make_helper, make_examples, make_doc)
435 modulepath.mkdir(parents=
True)
437 success = all(func(modulepath, modname)
for func
in functions)
440 raise ValueError(
"Generating module artifacts failed")
442 except Exception
as e:
443 if modulepath.exists():
444 shutil.rmtree(modulepath)
446 print(
"Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
453 description =
"""Generate scaffolding for ns-3 modules
455Generates the directory structure and skeleton files required for an ns-3
456module. All of the generated files are valid C/C++ and will compile successfully
457out of the box. ns3 configure must be run after creating new modules
in order
458to integrate them into the ns-3 build system.
460The following directory structure
is generated under the contrib directory:
466 |-- <modname>-example.cc
469 |-- <modname>-helper.cc
470 |-- <modname>-helper.h
475 |-- <modname>-test-suite.cc
478<modname>
is the name of the module
and is restricted to the following
479character groups: letters, numbers, -, _
480The script validates the module name
and skips modules that have characters
481outside of the above groups. One exception to the naming rule
is that src/
482or contrib/ may be added to the front of the module name to indicate where the
483module scaffold should be created. If the module name starts
with src/, then
484the module
is placed
in the src directory. If the module name starts
with
485contrib/, then the module
is placed
in the contrib directory. If the module
486name does
not start
with src/
or contrib/, then it defaults to contrib/.
487See the examples section
for use cases.
490In some situations it can be useful to group multiple related modules under one
491directory. Use the --project option to specify a common parent directory where
492the modules should be generated. The value passed to --project
is treated
493as a relative path. The path components have the same naming requirements
as
494the module name: letters, numbers, -, _
495The project directory
is placed under the contrib directory
and any parts of the
496path that do
not exist will be created. Creating projects
in the src directory
497is not supported. Module names that start
with src/ are
not allowed when
498--project
is used. Module names that start
with contrib/ are treated the same
499as module names that don
't start with contrib/ and are generated under the
503 epilog = """Examples:
505 %(prog)s contrib/module1
507 Creates a new module named module1 under the contrib directory
511 Creates a new module named module1 under the src directory
513 %(prog)s src/module1 contrib/module2, module3
515 Creates three modules, one under the src directory and two under the
518 %(prog)s --project myproject module1 module2
520 Creates two modules under contrib/myproject
522 %(prog)s --project myproject/sub_project module1 module2
524 Creates two modules under contrib/myproject/sub_project
528 formatter = argparse.RawDescriptionHelpFormatter
530 parser = argparse.ArgumentParser(description=description,
532 formatter_class=formatter)
534 parser.add_argument('--project', default=
'',
535 help=(
"Specify a relative path under the contrib directory "
536 "where the new modules will be generated. The path "
537 "will be created if it does not exist."))
539 parser.add_argument(
'modnames', nargs=
'+',
540 help=(
"One or more modules to generate. Module names "
541 "are limited to the following: letters, numbers, -, "
542 "_. Modules are generated under the contrib directory "
543 "except when the module name starts with src/. Modules "
544 "that start with src/ are generated under the src "
552 args = parser.parse_args(argv[1:])
554 project = args.project
555 modnames = args.modnames
557 base_path = Path.cwd()
559 src_path = base_path.joinpath(
'src')
560 contrib_path = base_path.joinpath(
'contrib')
562 for p
in (src_path, contrib_path):
564 parser.error(
"Cannot find the directory '{}'.\nPlease run this "
565 "script from the top level of the ns3 directory".format(
573 allowedRE = re.compile(
'^(\w|-)+$')
580 project_path = Path(project)
582 if project_path.is_absolute():
584 project_path = project_path.relative_to(os.sep)
586 if not all(allowedRE.match(part)
for part
in project_path.parts):
587 parser.error(
'Project path may only contain the characters [a-zA-Z0-9_-].')
592 for name
in modnames:
595 name = name.strip(os.sep)
601 name_path = Path(name)
603 if len(name_path.parts) > 2:
604 print(
"Skipping {}: module name can not be a path".format(name))
608 modpath = contrib_path
610 if name_path.parts[0] ==
'src':
612 parser.error(
"{}: Cannot specify src/ in a module name when --project option is used".format(name))
617 name_path = name_path.relative_to(
'src')
619 elif name_path.parts[0] ==
'contrib':
620 modpath = contrib_path
623 name_path = name_path.relative_to(
'contrib')
628 modpath = contrib_path.joinpath(project_path)
630 modname = name_path.parts[0]
632 if not allowedRE.match(modname):
633 print(
"Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(modname))
636 modules.append((modpath, modname))
638 if all(
make_module(*module)
for module
in modules):
640 print(
"Successfully created new modules")
641 print(
"Run './ns3 configure' to include them in the build")
645if __name__ ==
'__main__':
648 return_value = main(sys.argv)
649 except Exception
as e:
650 print(
"Exception: '{}'".format(e), file=sys.stderr)
653 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()