8 from pathlib
import Path
10 CMAKELISTS_TEMPLATE =
'''\
11 check_include_file_cxx(stdint.h HAVE_STDINT_H)
13 add_definitions(-DHAVE_STDINT_H)
16 set(examples_as_tests_sources)
17 if(${{ENABLE_EXAMPLES}})
18 set(examples_as_tests_sources
19 #test/{MODULE}-examples-test-suite.cc
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}}
37 MODEL_CC_TEMPLATE =
'''\
49 MODEL_H_TEMPLATE =
'''\
50 #ifndef {INCLUDE_GUARD}
51 #define {INCLUDE_GUARD}
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
69 #endif /* {INCLUDE_GUARD} */
73 HELPER_CC_TEMPLATE =
'''\
74 #include "{MODULE}-helper.h"
85 HELPER_H_TEMPLATE =
'''\
86 #ifndef {INCLUDE_GUARD}
87 #define {INCLUDE_GUARD}
89 #include "ns3/{MODULE}.h"
94 // Each class should be documented using Doxygen,
95 // and have an \ingroup {MODULE} directive
101 #endif /* {INCLUDE_GUARD} */
105 EXAMPLES_CMAKELISTS_TEMPLATE =
'''\
107 NAME {MODULE}-example
108 SOURCE_FILES {MODULE}-example.cc
109 LIBRARIES_TO_LINK ${{lib{MODULE}}}
114 EXAMPLE_CC_TEMPLATE =
'''\
115 #include "ns3/core-module.h"
116 #include "ns3/{MODULE}-helper.h"
121 * Explain here what the example does.
127 main(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();
145 TEST_CC_TEMPLATE =
'''\
147 // Include a header file from your module to test.
148 #include "ns3/{MODULE}.h"
150 // An essential include is test.h
151 #include "ns3/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
170 class {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
200 NS_TEST_ASSERT_MSG_EQ(true, true, "true doesn\'t equal true for some reason");
201 // Use this one for floating point comparisons
202 NS_TEST_ASSERT_MSG_EQ_TOL(0.01, 0.01, 0.001, "Numbers are not equal within tolerance");
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}
213 class {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
231 static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
235 DOC_RST_TEMPLATE =
'''Example Module Documentation
236 ----------------------------
238 .. include:: replace.txt
241 .. heading hierarchy:
242 ------------- Chapter
243 ************* Section (#.#)
244 ============= Subsection (#.#.#)
245 ############# Paragraph (no number)
247 This is a suggested outline for adding new module documentation to |ns3|.
248 See ``src/click/doc/click.rst`` for an example.
250 The introductory paragraph is for describing what this code is trying to
253 For consistency (italicized formatting), please use |ns3| to refer to
254 ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros
255 are defined in the file ``replace.txt``.
260 The source code for the new module lives in the directory ``{MODULE_DIR}``.
262 Add here a basic description of what is being modeled.
267 Briefly describe the software design of the model and how it fits into
268 the existing ns-3 architecture.
270 Scope and Limitations
271 =====================
273 What can the model do? What can it not do? Please use this section to
274 describe the scope and limitations of the model.
279 Add academic citations here, such as if you published a paper on this
280 model, or if readers should read a particular specification or other work.
285 This section is principally concerned with the usage of your model, using
286 the public API. Focus first on most common usage patterns, then go
287 into more advanced topics.
292 Include this subsection only if there are special build instructions or
293 platform limitations.
298 What helper API will users typically use? Describe it here.
303 What classes hold attributes, and what are the key ones worth mentioning?
308 What kind of data does the model generate? What are the key trace
309 sources? What kind of logging output can be enabled?
314 Go into further details (such as using the API outside of the helpers)
315 in additional sections, as needed.
320 What examples using this new code are available? Describe them here.
325 Add any tips for avoiding pitfalls, etc.
330 Describe how the model has been tested/validated. What tests run in the
331 test suite? How much API and code is covered by the tests? Again,
332 references to outside published work may help here.
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
455 Generates the directory structure and skeleton files required for an ns-3
456 module. All of the generated files are valid C/C++ and will compile successfully
457 out of the box. ns3 configure must be run after creating new modules in order
458 to integrate them into the ns-3 build system.
460 The 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
479 character groups: letters, numbers, -, _
480 The script validates the module name and skips modules that have characters
481 outside of the above groups. One exception to the naming rule is that src/
482 or contrib/ may be added to the front of the module name to indicate where the
483 module scaffold should be created. If the module name starts with src/, then
484 the module is placed in the src directory. If the module name starts with
485 contrib/, then the module is placed in the contrib directory. If the module
486 name does not start with src/ or contrib/, then it defaults to contrib/.
487 See the examples section for use cases.
490 In some situations it can be useful to group multiple related modules under one
491 directory. Use the --project option to specify a common parent directory where
492 the modules should be generated. The value passed to --project is treated
493 as a relative path. The path components have the same naming requirements as
494 the module name: letters, numbers, -, _
495 The project directory is placed under the contrib directory and any parts of the
496 path that do not exist will be created. Creating projects in the src directory
497 is 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
499 as 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")
645 if __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)
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()