7from pathlib
import Path
9CMAKELISTS_TEMPLATE =
"""\
10check_include_file_cxx(stdint.h HAVE_STDINT_H)
12 add_definitions(-DHAVE_STDINT_H)
15set(examples_as_tests_sources)
16if(${{ENABLE_EXAMPLES}})
17 set(examples_as_tests_sources
18 #test/{MODULE}-examples-test-suite.cc
24 SOURCE_FILES model/{MODULE}.cc
25 helper/{MODULE}-helper.cc
26 HEADER_FILES model/{MODULE}.h
27 helper/{MODULE}-helper.h
28 LIBRARIES_TO_LINK ${{libcore}}
29 TEST_SOURCES test/{MODULE}-test-suite.cc
30 ${{examples_as_tests_sources}}
35MODEL_CC_TEMPLATE =
"""\
47MODEL_H_TEMPLATE =
"""\
48#ifndef {INCLUDE_GUARD}
49#define {INCLUDE_GUARD}
51// Add a doxygen group for this module.
52// If you have more than one file, this should be in only one of them.
54 * \defgroup {MODULE} Description of the {MODULE}
60// Each class should be documented using Doxygen,
61// and have an \ingroup {MODULE} directive
67#endif /* {INCLUDE_GUARD} */
71HELPER_CC_TEMPLATE =
"""\
72#include "{MODULE}-helper.h"
83HELPER_H_TEMPLATE =
"""\
84#ifndef {INCLUDE_GUARD}
85#define {INCLUDE_GUARD}
87#include "ns3/{MODULE}.h"
92// Each class should be documented using Doxygen,
93// and have an \ingroup {MODULE} directive
99#endif /* {INCLUDE_GUARD} */
103EXAMPLES_CMAKELISTS_TEMPLATE =
"""\
105 NAME {MODULE}-example
106 SOURCE_FILES {MODULE}-example.cc
107 LIBRARIES_TO_LINK ${{lib{MODULE}}}
111EXAMPLE_CC_TEMPLATE =
"""\
112#include "ns3/core-module.h"
113#include "ns3/{MODULE}-helper.h"
118 * Explain here what the example does.
124main(int argc, char* argv[])
128 CommandLine cmd(__FILE__);
129 cmd.AddValue("verbose", "Tell application to log if true", verbose);
131 cmd.Parse(argc, argv);
136 Simulator::Destroy();
142TEST_CC_TEMPLATE =
"""\
144// Include a header file from your module to test.
145#include "ns3/{MODULE}.h"
147// An essential include is test.h
150// Do not put your test classes in namespace ns3. You may find it useful
151// to use the using directive to access the ns3 namespace directly
154// Add a doxygen group for tests.
155// If you have more than one test, this should be in only one of them.
157 * \defgroup {MODULE}-tests Tests for {MODULE}
162// This is an example TestCase.
164 * \ingroup {MODULE}-tests
165 * Test case for feature 1
167class {CAPITALIZED}TestCase1 : public TestCase
170 {CAPITALIZED}TestCase1();
171 virtual ~{CAPITALIZED}TestCase1();
174 void DoRun() override;
177// Add some help text to this case to describe what it is intended to test
178{CAPITALIZED}TestCase1::{CAPITALIZED}TestCase1()
179 : TestCase("{CAPITALIZED} test case (does nothing)")
183// This destructor does nothing but we include it as a reminder that
184// the test case should clean up after itself
185{CAPITALIZED}TestCase1::~{CAPITALIZED}TestCase1()
190// This method is the pure virtual method from class TestCase that every
191// TestCase must implement
194{CAPITALIZED}TestCase1::DoRun()
196 // A wide variety of test macros are available in src/core/test.h
197 NS_TEST_ASSERT_MSG_EQ(true, true, "true doesn\'t equal true for some reason");
198 // Use this one for floating point comparisons
199 NS_TEST_ASSERT_MSG_EQ_TOL(0.01, 0.01, 0.001, "Numbers are not equal within tolerance");
202// The TestSuite class names the TestSuite, identifies what type of TestSuite,
203// and enables the TestCases to be run. Typically, only the constructor for
204// this class must be defined
207 * \ingroup {MODULE}-tests
208 * TestSuite for module {MODULE}
210class {CAPITALIZED}TestSuite : public TestSuite
213 {CAPITALIZED}TestSuite();
216{CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
217 : TestSuite("{MODULE}", Type::UNIT)
219 // Duration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER
220 AddTestCase(new {CAPITALIZED}TestCase1, TestCase::Duration::QUICK);
223// Do not forget to allocate an instance of this TestSuite
225 * \ingroup {MODULE}-tests
226 * Static variable for test initialization
228static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
232DOC_RST_TEMPLATE =
"""Example Module Documentation
233----------------------------
235.. include:: replace.txt
239 ------------- Chapter
240 ************* Section (#.#)
241 ============= Subsection (#.#.#)
242 ############# Paragraph (no number)
244This is a suggested outline for adding new module documentation to |ns3|.
245See ``src/click/doc/click.rst`` for an example.
247The introductory paragraph is for describing what this code is trying to
250For consistency (italicized formatting), please use |ns3| to refer to
251ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros
252are defined in the file ``replace.txt``.
257The source code for the new module lives in the directory ``{MODULE_DIR}``.
259Add here a basic description of what is being modeled.
264Briefly describe the software design of the model and how it fits into
265the existing ns-3 architecture.
270What can the model do? What can it not do? Please use this section to
271describe the scope and limitations of the model.
276Add academic citations here, such as if you published a paper on this
277model, or if readers should read a particular specification or other work.
282This section is principally concerned with the usage of your model, using
283the public API. Focus first on most common usage patterns, then go
284into more advanced topics.
289Include this subsection only if there are special build instructions or
295What helper API will users typically use? Describe it here.
300What classes hold attributes, and what are the key ones worth mentioning?
305What kind of data does the model generate? What are the key trace
306sources? What kind of logging output can be enabled?
311Go into further details (such as using the API outside of the helpers)
312in additional sections, as needed.
317What examples using this new code are available? Describe them here.
322Add any tips for avoiding pitfalls, etc.
327Describe how the model has been tested/validated. What tests run in the
328test suite? How much API and code is covered by the tests? Again,
329references to outside published work may help here.
334 artifact_path = Path(path)
337 with artifact_path.open(
"wt", encoding=
"utf-8")
as f:
338 f.write(template.format(**kwargs))
342 path = Path(moduledir,
"CMakeLists.txt")
344 create_file(path, CMAKELISTS_TEMPLATE, MODULE=modname)
350 modelpath = Path(moduledir,
"model")
351 modelpath.mkdir(parents=
True)
353 srcfile_path = modelpath.joinpath(modname).with_suffix(
".cc")
354 create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
356 hfile_path = modelpath.joinpath(modname).with_suffix(
".h")
357 guard =
"{}_H".format(modname.replace(
"-",
"_").upper())
358 create_file(hfile_path, MODEL_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
364 testpath = Path(moduledir,
"test")
365 testpath.mkdir(parents=
True)
367 file_path = testpath.joinpath(modname +
"-test-suite").with_suffix(
".cc")
368 name_parts = modname.split(
"-")
373 CAPITALIZED=
"".join([word.capitalize()
for word
in name_parts]),
375 [word.capitalize()
if index > 0
else word
for index, word
in enumerate(name_parts)]
383 helperpath = Path(moduledir,
"helper")
384 helperpath.mkdir(parents=
True)
386 srcfile_path = helperpath.joinpath(modname +
"-helper").with_suffix(
".cc")
387 create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
389 h_file_path = helperpath.joinpath(modname +
"-helper").with_suffix(
".h")
390 guard =
"{}_HELPER_H".format(modname.replace(
"-",
"_").upper())
391 create_file(h_file_path, HELPER_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
397 examplespath = Path(moduledir,
"examples")
398 examplespath.mkdir(parents=
True)
400 cmakelistspath = Path(examplespath,
"CMakeLists.txt")
401 create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
403 examplesfile_path = examplespath.joinpath(modname +
"-example").with_suffix(
".cc")
404 create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
410 docpath = Path(moduledir,
"doc")
411 docpath.mkdir(parents=
True)
415 mod_relpath = os.path.relpath(str(moduledir))
417 file_name =
"{}.rst".format(modname)
418 file_path = Path(docpath, file_name)
419 create_file(file_path, DOC_RST_TEMPLATE, MODULE=modname, MODULE_DIR=mod_relpath)
425 modulepath = Path(modpath, modname)
427 if modulepath.exists():
428 print(
"Module {!r} already exists".format(modname), file=sys.stderr)
431 print(
"Creating module {}".format(modulepath))
433 functions = (make_cmakelists, make_model, make_test, make_helper, make_examples, make_doc)
436 modulepath.mkdir(parents=
True)
438 success = all(func(modulepath, modname)
for func
in functions)
441 raise ValueError(
"Generating module artifacts failed")
443 except Exception
as e:
444 if modulepath.exists():
445 shutil.rmtree(modulepath)
447 print(
"Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
455 description =
"""Generate scaffolding for ns-3 modules
457Generates the directory structure and skeleton files required for an ns-3
458module. All of the generated files are valid C/C++ and will compile successfully
459out of the box. ns3 configure must be run after creating new modules in order
460to integrate them into the ns-3 build system.
462The following directory structure is generated under the contrib directory:
468 |-- <modname>-example.cc
471 |-- <modname>-helper.cc
472 |-- <modname>-helper.h
477 |-- <modname>-test-suite.cc
480<modname> is the name of the module and is restricted to the following
481character groups: letters, numbers, -, _
482The script validates the module name and skips modules that have characters
483outside of the above groups. One exception to the naming rule is that src/
484or contrib/ may be added to the front of the module name to indicate where the
485module scaffold should be created. If the module name starts with src/, then
486the module is placed in the src directory. If the module name starts with
487contrib/, then the module is placed in the contrib directory. If the module
488name does not start with src/ or contrib/, then it defaults to contrib/.
489See the examples section for use cases.
492In some situations it can be useful to group multiple related modules under one
493directory. Use the --project option to specify a common parent directory where
494the modules should be generated. The value passed to --project is treated
495as a relative path. The path components have the same naming requirements as
496the module name: letters, numbers, -, _
497The project directory is placed under the contrib directory and any parts of the
498path that do not exist will be created. Creating projects in the src directory
499is not supported. Module names that start with src/ are not allowed when
500--project is used. Module names that start with contrib/ are treated the same
501as module names that don't start with contrib/ and are generated under the
505 epilog =
"""Examples:
507 %(prog)s contrib/module1
509 Creates a new module named module1 under the contrib directory
513 Creates a new module named module1 under the src directory
515 %(prog)s src/module1 contrib/module2, module3
517 Creates three modules, one under the src directory and two under the
520 %(prog)s --project myproject module1 module2
522 Creates two modules under contrib/myproject
524 %(prog)s --project myproject/sub_project module1 module2
526 Creates two modules under contrib/myproject/sub_project
530 formatter = argparse.RawDescriptionHelpFormatter
532 parser = argparse.ArgumentParser(
533 description=description, epilog=epilog, formatter_class=formatter
540 "Specify a relative path under the contrib directory "
541 "where the new modules will be generated. The path "
542 "will be created if it does not exist."
550 "One or more modules to generate. Module names "
551 "are limited to the following: letters, numbers, -, "
552 "_. Modules are generated under the contrib directory "
553 "except when the module name starts with src/. Modules "
554 "that start with src/ are generated under the src "
565 args = parser.parse_args(argv[1:])
567 project = args.project
568 modnames = args.modnames
570 base_path = Path.cwd()
572 src_path = base_path.joinpath(
"src")
573 contrib_path = base_path.joinpath(
"contrib")
575 for p
in (src_path, contrib_path):
578 "Cannot find the directory '{}'.\nPlease run this "
579 "script from the top level of the ns3 directory".format(p)
587 allowedRE = re.compile(
"^(\w|-)+$")
594 project_path = Path(project)
596 if project_path.is_absolute():
598 project_path = project_path.relative_to(os.sep)
600 if not all(allowedRE.match(part)
for part
in project_path.parts):
601 parser.error(
"Project path may only contain the characters [a-zA-Z0-9_-].")
606 for name
in modnames:
609 name = name.strip(os.sep)
615 name_path = Path(name)
617 if len(name_path.parts) > 2:
618 print(
"Skipping {}: module name can not be a path".format(name))
622 modpath = contrib_path
624 if name_path.parts[0] ==
"src":
627 "{}: Cannot specify src/ in a module name when --project option is used".format(
635 name_path = name_path.relative_to(
"src")
637 elif name_path.parts[0] ==
"contrib":
638 modpath = contrib_path
641 name_path = name_path.relative_to(
"contrib")
646 modpath = contrib_path.joinpath(project_path)
648 modname = name_path.parts[0]
650 if not allowedRE.match(modname):
652 "Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(
658 modules.append((modpath, modname))
660 if all(
make_module(*module)
for module
in modules):
662 print(
"Successfully created new modules")
663 print(
"Run './ns3 configure' to include them in the build")
668if __name__ ==
"__main__":
671 return_value = main(sys.argv)
672 except Exception
as e:
673 print(
"Exception: '{}'".format(e), file=sys.stderr)
676 sys.exit(return_value)
create_file(path, template, **kwargs)
make_doc(moduledir, modname)
make_test(moduledir, modname)
make_cmakelists(moduledir, modname)
make_examples(moduledir, modname)
make_module(modpath, modname)
make_model(moduledir, modname)
make_helper(moduledir, modname)