7 from pathlib
import Path
9 CMAKELISTS_TEMPLATE =
"""\
10 check_include_file_cxx(stdint.h HAVE_STDINT_H)
12 add_definitions(-DHAVE_STDINT_H)
15 set(examples_as_tests_sources)
16 if(${{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}}
35 MODEL_CC_TEMPLATE =
"""\
47 MODEL_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} */
71 HELPER_CC_TEMPLATE =
"""\
72 #include "{MODULE}-helper.h"
83 HELPER_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} */
103 EXAMPLES_CMAKELISTS_TEMPLATE =
"""\
105 NAME {MODULE}-example
106 SOURCE_FILES {MODULE}-example.cc
107 LIBRARIES_TO_LINK ${{lib{MODULE}}}
111 EXAMPLE_CC_TEMPLATE =
"""\
112 #include "ns3/core-module.h"
113 #include "ns3/{MODULE}-helper.h"
118 * Explain here what the example does.
124 main(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();
142 TEST_CC_TEMPLATE =
"""\
144 // Include a header file from your module to test.
145 #include "ns3/{MODULE}.h"
147 // An essential include is test.h
148 #include "ns3/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
167 class {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}
210 class {CAPITALIZED}TestSuite : public TestSuite
213 {CAPITALIZED}TestSuite();
216 {CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
217 : TestSuite("{MODULE}", UNIT)
219 // TestDuration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER
220 AddTestCase(new {CAPITALIZED}TestCase1, TestCase::QUICK);
223 // Do not forget to allocate an instance of this TestSuite
225 * \ingroup {MODULE}-tests
226 * Static variable for test initialization
228 static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
232 DOC_RST_TEMPLATE =
"""Example Module Documentation
233 ----------------------------
235 .. include:: replace.txt
238 .. heading hierarchy:
239 ------------- Chapter
240 ************* Section (#.#)
241 ============= Subsection (#.#.#)
242 ############# Paragraph (no number)
244 This is a suggested outline for adding new module documentation to |ns3|.
245 See ``src/click/doc/click.rst`` for an example.
247 The introductory paragraph is for describing what this code is trying to
250 For consistency (italicized formatting), please use |ns3| to refer to
251 ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros
252 are defined in the file ``replace.txt``.
257 The source code for the new module lives in the directory ``{MODULE_DIR}``.
259 Add here a basic description of what is being modeled.
264 Briefly describe the software design of the model and how it fits into
265 the existing ns-3 architecture.
267 Scope and Limitations
268 =====================
270 What can the model do? What can it not do? Please use this section to
271 describe the scope and limitations of the model.
276 Add academic citations here, such as if you published a paper on this
277 model, or if readers should read a particular specification or other work.
282 This section is principally concerned with the usage of your model, using
283 the public API. Focus first on most common usage patterns, then go
284 into more advanced topics.
289 Include this subsection only if there are special build instructions or
290 platform limitations.
295 What helper API will users typically use? Describe it here.
300 What classes hold attributes, and what are the key ones worth mentioning?
305 What kind of data does the model generate? What are the key trace
306 sources? What kind of logging output can be enabled?
311 Go into further details (such as using the API outside of the helpers)
312 in additional sections, as needed.
317 What examples using this new code are available? Describe them here.
322 Add any tips for avoiding pitfalls, etc.
327 Describe how the model has been tested/validated. What tests run in the
328 test suite? How much API and code is covered by the tests? Again,
329 references 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
457 Generates the directory structure and skeleton files required for an ns-3
458 module. All of the generated files are valid C/C++ and will compile successfully
459 out of the box. ns3 configure must be run after creating new modules in order
460 to integrate them into the ns-3 build system.
462 The 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
481 character groups: letters, numbers, -, _
482 The script validates the module name and skips modules that have characters
483 outside of the above groups. One exception to the naming rule is that src/
484 or contrib/ may be added to the front of the module name to indicate where the
485 module scaffold should be created. If the module name starts with src/, then
486 the module is placed in the src directory. If the module name starts with
487 contrib/, then the module is placed in the contrib directory. If the module
488 name does not start with src/ or contrib/, then it defaults to contrib/.
489 See the examples section for use cases.
492 In some situations it can be useful to group multiple related modules under one
493 directory. Use the --project option to specify a common parent directory where
494 the modules should be generated. The value passed to --project is treated
495 as a relative path. The path components have the same naming requirements as
496 the module name: letters, numbers, -, _
497 The project directory is placed under the contrib directory and any parts of the
498 path that do not exist will be created. Creating projects in the src directory
499 is 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
501 as 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")
668 if __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)
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()