A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
create-module.py
Go to the documentation of this file.
1#! /usr/bin/env python3
2import argparse
3import os
4import re
5import shutil
6import sys
7from pathlib import Path
8
9CMAKELISTS_TEMPLATE = """\
10set(examples_as_tests_sources)
11if(${{ENABLE_EXAMPLES}})
12 set(examples_as_tests_sources
13 #test/{MODULE}-examples-test-suite.cc
14 )
15endif()
16
17build_lib(
18 LIBNAME {MODULE}
19 SOURCE_FILES model/{MODULE}.cc
20 helper/{MODULE}-helper.cc
21 HEADER_FILES model/{MODULE}.h
22 helper/{MODULE}-helper.h
23 LIBRARIES_TO_LINK ${{libcore}}
24 TEST_SOURCES test/{MODULE}-test-suite.cc
25 ${{examples_as_tests_sources}}
26)
27"""
28
29
30MODEL_CC_TEMPLATE = """\
31#include "{MODULE}.h"
32
33namespace ns3
34{{
35
36/* ... */
37
38}} // namespace ns3
39"""
40
41
42MODEL_H_TEMPLATE = """\
43#ifndef {INCLUDE_GUARD}
44#define {INCLUDE_GUARD}
45
46// Add a doxygen group for this module.
47// If you have more than one file, this should be in only one of them.
48/**
49 * @defgroup {MODULE} Description of the {MODULE}
50 */
51
52namespace ns3
53{{
54
55// Each class should be documented using Doxygen,
56// and have an @ingroup {MODULE} directive
57
58/* ... */
59
60}} // namespace ns3
61
62#endif // {INCLUDE_GUARD}
63"""
64
65
66HELPER_CC_TEMPLATE = """\
67#include "{MODULE}-helper.h"
68
69namespace ns3
70{{
71
72/* ... */
73
74}} // namespace ns3
75"""
76
77
78HELPER_H_TEMPLATE = """\
79#ifndef {INCLUDE_GUARD}
80#define {INCLUDE_GUARD}
81
82#include "ns3/{MODULE}.h"
83
84namespace ns3
85{{
86
87// Each class should be documented using Doxygen,
88// and have an @ingroup {MODULE} directive
89
90/* ... */
91
92}} // namespace ns3
93
94#endif // {INCLUDE_GUARD}
95"""
96
97
98EXAMPLES_CMAKELISTS_TEMPLATE = """\
99build_lib_example(
100 NAME {MODULE}-example
101 SOURCE_FILES {MODULE}-example.cc
102 LIBRARIES_TO_LINK ${{lib{MODULE}}}
103)
104"""
105
106EXAMPLE_CC_TEMPLATE = """\
107#include "ns3/core-module.h"
108#include "ns3/{MODULE}-helper.h"
109
110/**
111 * @file
112 *
113 * Explain here what the example does.
114 */
115
116using namespace ns3;
117
118int
119main(int argc, char* argv[])
120{{
121 bool verbose = true;
122
123 CommandLine cmd(__FILE__);
124 cmd.AddValue("verbose", "Tell application to log if true", verbose);
125
126 cmd.Parse(argc, argv);
127
128 /* ... */
129
130 Simulator::Run();
131 Simulator::Destroy();
132 return 0;
133}}
134"""
135
136
137TEST_CC_TEMPLATE = """\
138// Include a header file from your module to test.
139#include "ns3/{MODULE}.h"
140
141// An essential include is test.h
142#include "ns3/test.h"
143
144// Do not put your test classes in namespace ns3. You may find it useful
145// to use the using directive to access the ns3 namespace directly
146using namespace ns3;
147
148// Add a doxygen group for tests.
149// If you have more than one test, this should be in only one of them.
150/**
151 * @defgroup {MODULE}-tests Tests for {MODULE}
152 * @ingroup {MODULE}
153 * @ingroup tests
154 */
155
156// This is an example TestCase.
157/**
158 * @ingroup {MODULE}-tests
159 * Test case for feature 1
160 */
161class {CAPITALIZED}TestCase1 : public TestCase
162{{
163 public:
164 {CAPITALIZED}TestCase1();
165 ~{CAPITALIZED}TestCase1() override;
166
167 private:
168 void DoRun() override;
169}};
170
171// Add some help text to this case to describe what it is intended to test
172{CAPITALIZED}TestCase1::{CAPITALIZED}TestCase1()
173 : TestCase("{CAPITALIZED} test case (does nothing)")
174{{
175}}
176
177// This destructor does nothing but we include it as a reminder that
178// the test case should clean up after itself
179{CAPITALIZED}TestCase1::~{CAPITALIZED}TestCase1()
180{{
181}}
182
183//
184// This method is the pure virtual method from class TestCase that every
185// TestCase must implement
186//
187void
188{CAPITALIZED}TestCase1::DoRun()
189{{
190 // A wide variety of test macros are available in src/core/test.h
191 NS_TEST_ASSERT_MSG_EQ(true, true, "true doesn't equal true for some reason");
192 // Use this one for floating point comparisons
193 NS_TEST_ASSERT_MSG_EQ_TOL(0.01, 0.01, 0.001, "Numbers are not equal within tolerance");
194}}
195
196// The TestSuite class names the TestSuite, identifies what type of TestSuite,
197// and enables the TestCases to be run. Typically, only the constructor for
198// this class must be defined
199
200/**
201 * @ingroup {MODULE}-tests
202 * TestSuite for module {MODULE}
203 */
204class {CAPITALIZED}TestSuite : public TestSuite
205{{
206 public:
207 {CAPITALIZED}TestSuite();
208}};
209
210// Type for TestSuite can be UNIT, SYSTEM, EXAMPLE, or PERFORMANCE
211{CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
212 : TestSuite("{MODULE}", Type::UNIT)
213{{
214 // Duration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER
215 AddTestCase(new {CAPITALIZED}TestCase1, TestCase::Duration::QUICK);
216}}
217
218// Do not forget to allocate an instance of this TestSuite
219/**
220 * @ingroup {MODULE}-tests
221 * Static variable for test initialization
222 */
223static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
224"""
225
226
227DOC_RST_TEMPLATE = """Example Module Documentation
228============================
229
230.. include:: replace.txt
231.. highlight:: cpp
232
233.. heading hierarchy:
234 ============= Module Name
235 ------------- Section (#.#)
236 ~~~~~~~~~~~~~ Subsection (#.#.#)
237
238This is the outline for adding new module documentation to |ns3|.
239See ``src/lr-wpan/doc/lr-wpan.rst`` for an example.
240
241This first part is for describing what the model is trying to
242accomplish. General descriptions and design, overview of the project
243goes in this part without the need of any subsections. A visual summary
244is also recommended.
245
246For consistency (italicized formatting), please use |ns3| to refer to
247ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros
248are defined in the file ``replace.txt``.
249
250Detailed |ns3| Sphinx documentation guidelines can be found `here <https://www.nsnam.org/docs/contributing/html/models.html>`_
251
252
253The source code for the new module lives in the directory ``{MODULE_DIR}``.
254
255
256Scope and Limitations
257---------------------
258
259This is should be your first section. Please use it to list the scope and
260limitations. What can the model do? What can it not do? Be brief and concise.
261
262Section A
263---------
264
265Free form. Your second section of your documentation and its subsections goes here.
266The documentation can contain any number of sections but be careful to not use
267more than two levels in subsections.
268
269Section B
270---------
271
272Free form. The last section of your documentation with content goes here.
273
274Usage
275-----
276
277A brief description of the module usage goes here. This section must be present.
278
279Helpers
280~~~~~~~
281
282A subsection with a description of the helpers used by the model goes here. Snippets of code are
283preferable when describing the helpers usage. This subsection must be present.
284If the model CANNOT provide helpers write "Not applicable".
285
286Attributes
287~~~~~~~~~~
288
289A subsection with a list of attributes used by the module, each attribute should include a small
290description. This subsection must be present.
291If the model CANNOT provide attributes write "Not applicable".
292
293Traces
294~~~~~~
295
296A subsection with a list of the source traces used by the module, each trace should include a small
297description. This subsection must be present.
298If the model CANNOT provide traces write "Not applicable".
299
300Examples and Tests
301------------------
302
303A brief description of each example and test present in the model must be here.
304Include both the name of the example file and a brief description of the example.
305This section must be present.
306
307Validation
308----------
309
310Describe how the model has been tested/validated. What tests run in the
311test suite? How much API and code is covered by the tests?
312
313This section must be present. Write ``No formal validation has been made`` if your
314model do not contain validations.
315
316References
317----------
318
319The reference material used in the construction of the model.
320This section must be the last section and must be present.
321Use numbers for the index not names when listing the references. When possible, include the link of the referenced material.
322
323Example:
324
325[`1 <https://ieeexplore.ieee.org/document/6012487>`_] IEEE Standard for Local and metropolitan area networks--Part 15.4: Low-Rate Wireless Personal Area Networks (LR-WPANs)," in IEEE Std 802.15.4-2011 (Revision of IEEE Std 802.15.4-2006) , vol., no., pp.1-314, 5 Sept. 2011, doi: 10.1109/IEEESTD.2011.6012487.
326
327"""
328
329
330def create_file(path, template, **kwargs):
331 artifact_path = Path(path)
332
333 # open file for (w)rite and in (t)ext mode
334 with artifact_path.open("wt", encoding="utf-8") as f:
335 f.write(template.format(**kwargs))
336
337
338def make_cmakelists(moduledir, modname):
339 path = Path(moduledir, "CMakeLists.txt")
340 macro = "build_lib"
341 create_file(path, CMAKELISTS_TEMPLATE, MODULE=modname)
342
343 return True
344
345
346def make_model(moduledir, modname):
347 modelpath = Path(moduledir, "model")
348 modelpath.mkdir(parents=True)
349
350 srcfile_path = modelpath.joinpath(modname).with_suffix(".cc")
351 create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
352
353 hfile_path = modelpath.joinpath(modname).with_suffix(".h")
354 guard = "{}_H".format(modname.replace("-", "_").upper())
355 create_file(hfile_path, MODEL_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
356
357 return True
358
359
360def make_test(moduledir, modname):
361 testpath = Path(moduledir, "test")
362 testpath.mkdir(parents=True)
363
364 file_path = testpath.joinpath(modname + "-test-suite").with_suffix(".cc")
365 name_parts = modname.split("-")
367 file_path,
368 TEST_CC_TEMPLATE,
369 MODULE=modname,
370 CAPITALIZED="".join([word.capitalize() for word in name_parts]),
371 COMPOUND="".join(
372 [word.capitalize() if index > 0 else word for index, word in enumerate(name_parts)]
373 ),
374 )
375
376 return True
377
378
379def make_helper(moduledir, modname):
380 helperpath = Path(moduledir, "helper")
381 helperpath.mkdir(parents=True)
382
383 srcfile_path = helperpath.joinpath(modname + "-helper").with_suffix(".cc")
384 create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
385
386 h_file_path = helperpath.joinpath(modname + "-helper").with_suffix(".h")
387 guard = "{}_HELPER_H".format(modname.replace("-", "_").upper())
388 create_file(h_file_path, HELPER_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
389
390 return True
391
392
393def make_examples(moduledir, modname):
394 examplespath = Path(moduledir, "examples")
395 examplespath.mkdir(parents=True)
396
397 cmakelistspath = Path(examplespath, "CMakeLists.txt")
398 create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
399
400 examplesfile_path = examplespath.joinpath(modname + "-example").with_suffix(".cc")
401 create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
402
403 return True
404
405
406def make_doc(moduledir, modname):
407 docpath = Path(moduledir, "doc")
408 docpath.mkdir(parents=True)
409
410 # the module_dir template parameter must be a relative path
411 # instead of an absolute path
412 mod_relpath = os.path.relpath(str(moduledir))
413
414 file_name = "{}.rst".format(modname)
415 file_path = Path(docpath, file_name)
416 create_file(file_path, DOC_RST_TEMPLATE, MODULE=modname, MODULE_DIR=mod_relpath)
417
418 return True
419
420
421def make_module(modpath, modname):
422 modulepath = Path(modpath, modname)
423
424 if modulepath.exists():
425 print("Module {!r} already exists".format(modname), file=sys.stderr)
426 return False
427
428 print("Creating module {}".format(modulepath))
429
430 functions = (make_cmakelists, make_model, make_test, make_helper, make_examples, make_doc)
431
432 try:
433 modulepath.mkdir(parents=True)
434
435 success = all(func(modulepath, modname) for func in functions)
436
437 if not success:
438 raise ValueError("Generating module artifacts failed")
439
440 except Exception as e:
441 if modulepath.exists():
442 shutil.rmtree(modulepath)
443
444 print("Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
445
446 return False
447
448 return True
449
450
452 description = """Generate scaffolding for ns-3 modules
453
454Generates the directory structure and skeleton files required for an ns-3
455module. All of the generated files are valid C/C++ and will compile successfully
456out of the box. ns3 configure must be run after creating new modules in order
457to integrate them into the ns-3 build system.
458
459The following directory structure is generated under the contrib directory:
460<modname>
461 |-- CMakeLists.txt
462 |-- doc
463 |-- <modname>.rst
464 |-- examples
465 |-- <modname>-example.cc
466 |-- CMakeLists.txt
467 |-- helper
468 |-- <modname>-helper.cc
469 |-- <modname>-helper.h
470 |-- model
471 |-- <modname>.cc
472 |-- <modname>.h
473 |-- test
474 |-- <modname>-test-suite.cc
475
476
477<modname> is the name of the module and is restricted to the following
478character groups: letters, numbers, -, _
479The script validates the module name and skips modules that have characters
480outside of the above groups. One exception to the naming rule is that src/
481or contrib/ may be added to the front of the module name to indicate where the
482module scaffold should be created. If the module name starts with src/, then
483the module is placed in the src directory. If the module name starts with
484contrib/, then the module is placed in the contrib directory. If the module
485name does not start with src/ or contrib/, then it defaults to contrib/.
486See the examples section for use cases.
487
488
489In some situations it can be useful to group multiple related modules under one
490directory. Use the --project option to specify a common parent directory where
491the modules should be generated. The value passed to --project is treated
492as a relative path. The path components have the same naming requirements as
493the module name: letters, numbers, -, _
494The project directory is placed under the contrib directory and any parts of the
495path that do not exist will be created. Creating projects in the src directory
496is not supported. Module names that start with src/ are not allowed when
497--project is used. Module names that start with contrib/ are treated the same
498as module names that don't start with contrib/ and are generated under the
499project directory.
500"""
501
502 epilog = """Examples:
503 %(prog)s module1
504 %(prog)s contrib/module1
505
506 Creates a new module named module1 under the contrib directory
507
508 %(prog)s src/module1
509
510 Creates a new module named module1 under the src directory
511
512 %(prog)s src/module1 contrib/module2, module3
513
514 Creates three modules, one under the src directory and two under the
515 contrib directory
516
517 %(prog)s --project myproject module1 module2
518
519 Creates two modules under contrib/myproject
520
521 %(prog)s --project myproject/sub_project module1 module2
522
523 Creates two modules under contrib/myproject/sub_project
524
525"""
526
527 formatter = argparse.RawDescriptionHelpFormatter
528
529 parser = argparse.ArgumentParser(
530 description=description, epilog=epilog, formatter_class=formatter
531 )
532
533 parser.add_argument(
534 "--project",
535 default="",
536 help=(
537 "Specify a relative path under the contrib directory "
538 "where the new modules will be generated. The path "
539 "will be created if it does not exist."
540 ),
541 )
542
543 parser.add_argument(
544 "modnames",
545 nargs="+",
546 help=(
547 "One or more modules to generate. Module names "
548 "are limited to the following: letters, numbers, -, "
549 "_. Modules are generated under the contrib directory "
550 "except when the module name starts with src/. Modules "
551 "that start with src/ are generated under the src "
552 "directory."
553 ),
554 )
555
556 return parser
557
558
559def main(argv):
560 parser = create_argument_parser()
561
562 args = parser.parse_args(argv[1:])
563
564 project = args.project
565 modnames = args.modnames
566
567 base_path = Path.cwd()
568
569 src_path = base_path.joinpath("src")
570 contrib_path = base_path.joinpath("contrib")
571
572 for p in (src_path, contrib_path):
573 if not p.is_dir():
574 parser.error(
575 "Cannot find the directory '{}'.\nPlease run this "
576 "script from the top level of the ns3 directory".format(p)
577 )
578
579 #
580 # Error check the arguments
581 #
582
583 # Alphanumeric and '-' only
584 allowedRE = re.compile(r"^(\w|-)+$")
585
586 project_path = None
587
588 if project:
589 # project may be a path in the form a/b/c
590 # remove any leading or trailing path separators
591 project_path = Path(project)
592
593 if project_path.is_absolute():
594 # remove leading separator
595 project_path = project_path.relative_to(os.sep)
596
597 if not all(allowedRE.match(part) for part in project_path.parts):
598 parser.error("Project path may only contain the characters [a-zA-Z0-9_-].")
599 #
600 # Create each module, if it doesn't exist
601 #
602 modules = []
603 for name in modnames:
604 if name:
605 # remove any leading or trailing directory separators
606 name = name.strip(os.sep)
607
608 if not name:
609 # skip empty modules
610 continue
611
612 name_path = Path(name)
613
614 if len(name_path.parts) > 2:
615 print("Skipping {}: module name can not be a path".format(name))
616 continue
617
618 # default target directory is contrib
619 modpath = contrib_path
620
621 if name_path.parts[0] == "src":
622 if project:
623 parser.error(
624 "{}: Cannot specify src/ in a module name when --project option is used".format(
625 name
626 )
627 )
628
629 modpath = src_path
630
631 # create a new path without the src part
632 name_path = name_path.relative_to("src")
633
634 elif name_path.parts[0] == "contrib":
635 modpath = contrib_path
636
637 # create a new path without the contrib part
638 name_path = name_path.relative_to("contrib")
639
640 if project_path:
641 # if a project path was specified, that overrides other paths
642 # project paths are always relative to the contrib path
643 modpath = contrib_path.joinpath(project_path)
644
645 modname = name_path.parts[0]
646
647 if not allowedRE.match(modname):
648 print(
649 "Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(
650 modname
651 )
652 )
653 continue
654
655 modules.append((modpath, modname))
656
657 if all(make_module(*module) for module in modules):
658 print()
659 print("Successfully created new modules")
660 print("Run './ns3 configure' to include them in the build")
661
662 return 0
663
664
665if __name__ == "__main__":
666 return_value = 0
667 try:
668 return_value = main(sys.argv)
669 except Exception as e:
670 print("Exception: '{}'".format(e), file=sys.stderr)
671 return_value = 1
672
673 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)