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