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}}
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}}
66
67#endif /* {INCLUDE_GUARD} */
68"""
69
70
71HELPER_CC_TEMPLATE = """\
72#include "{MODULE}-helper.h"
73
74namespace ns3
75{{
76
77/* ... */
78
79}}
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}}
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
144// Include a header file from your module to test.
145#include "ns3/{MODULE}.h"
146
147// An essential include is test.h
148#include "ns3/test.h"
149
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
152using namespace ns3;
153
154// Add a doxygen group for tests.
155// If you have more than one test, this should be in only one of them.
156/**
157 * \defgroup {MODULE}-tests Tests for {MODULE}
158 * \ingroup {MODULE}
159 * \ingroup tests
160 */
161
162// This is an example TestCase.
163/**
164 * \ingroup {MODULE}-tests
165 * Test case for feature 1
166 */
167class {CAPITALIZED}TestCase1 : public TestCase
168{{
169 public:
170 {CAPITALIZED}TestCase1();
171 virtual ~{CAPITALIZED}TestCase1();
172
173 private:
174 void DoRun() override;
175}};
176
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)")
180{{
181}}
182
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()
186{{
187}}
188
189//
190// This method is the pure virtual method from class TestCase that every
191// TestCase must implement
192//
193void
194{CAPITALIZED}TestCase1::DoRun()
195{{
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");
200}}
201
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
205
206/**
207 * \ingroup {MODULE}-tests
208 * TestSuite for module {MODULE}
209 */
210class {CAPITALIZED}TestSuite : public TestSuite
211{{
212 public:
213 {CAPITALIZED}TestSuite();
214}};
215
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 ------------- Chapter
240 ************* Section (#.#)
241 ============= Subsection (#.#.#)
242 ############# Paragraph (no number)
243
244This is a suggested outline for adding new module documentation to |ns3|.
245See ``src/click/doc/click.rst`` for an example.
246
247The introductory paragraph is for describing what this code is trying to
248model.
249
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``.
253
254Model Description
255*****************
256
257The source code for the new module lives in the directory ``{MODULE_DIR}``.
258
259Add here a basic description of what is being modeled.
260
261Design
262======
263
264Briefly describe the software design of the model and how it fits into
265the existing ns-3 architecture.
266
267Scope and Limitations
268=====================
269
270What can the model do? What can it not do? Please use this section to
271describe the scope and limitations of the model.
272
273References
274==========
275
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.
278
279Usage
280*****
281
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.
285
286Building New Module
287===================
288
289Include this subsection only if there are special build instructions or
290platform limitations.
291
292Helpers
293=======
294
295What helper API will users typically use? Describe it here.
296
297Attributes
298==========
299
300What classes hold attributes, and what are the key ones worth mentioning?
301
302Output
303======
304
305What kind of data does the model generate? What are the key trace
306sources? What kind of logging output can be enabled?
307
308Advanced Usage
309==============
310
311Go into further details (such as using the API outside of the helpers)
312in additional sections, as needed.
313
314Examples
315========
316
317What examples using this new code are available? Describe them here.
318
319Troubleshooting
320===============
321
322Add any tips for avoiding pitfalls, etc.
323
324Validation
325**********
326
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.
330"""
331
332
333def create_file(path, template, **kwargs):
334 artifact_path = Path(path)
335
336 # open file for (w)rite and in (t)ext mode
337 with artifact_path.open("wt", encoding="utf-8") as f:
338 f.write(template.format(**kwargs))
339
340
341def make_cmakelists(moduledir, modname):
342 path = Path(moduledir, "CMakeLists.txt")
343 macro = "build_lib"
344 create_file(path, CMAKELISTS_TEMPLATE, MODULE=modname)
345
346 return True
347
348
349def make_model(moduledir, modname):
350 modelpath = Path(moduledir, "model")
351 modelpath.mkdir(parents=True)
352
353 srcfile_path = modelpath.joinpath(modname).with_suffix(".cc")
354 create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
355
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)
359
360 return True
361
362
363def make_test(moduledir, modname):
364 testpath = Path(moduledir, "test")
365 testpath.mkdir(parents=True)
366
367 file_path = testpath.joinpath(modname + "-test-suite").with_suffix(".cc")
368 name_parts = modname.split("-")
370 file_path,
371 TEST_CC_TEMPLATE,
372 MODULE=modname,
373 CAPITALIZED="".join([word.capitalize() for word in name_parts]),
374 COMPOUND="".join(
375 [word.capitalize() if index > 0 else word for index, word in enumerate(name_parts)]
376 ),
377 )
378
379 return True
380
381
382def make_helper(moduledir, modname):
383 helperpath = Path(moduledir, "helper")
384 helperpath.mkdir(parents=True)
385
386 srcfile_path = helperpath.joinpath(modname + "-helper").with_suffix(".cc")
387 create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
388
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)
392
393 return True
394
395
396def make_examples(moduledir, modname):
397 examplespath = Path(moduledir, "examples")
398 examplespath.mkdir(parents=True)
399
400 cmakelistspath = Path(examplespath, "CMakeLists.txt")
401 create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
402
403 examplesfile_path = examplespath.joinpath(modname + "-example").with_suffix(".cc")
404 create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
405
406 return True
407
408
409def make_doc(moduledir, modname):
410 docpath = Path(moduledir, "doc")
411 docpath.mkdir(parents=True)
412
413 # the module_dir template parameter must be a relative path
414 # instead of an absolute path
415 mod_relpath = os.path.relpath(str(moduledir))
416
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)
420
421 return True
422
423
424def make_module(modpath, modname):
425 modulepath = Path(modpath, modname)
426
427 if modulepath.exists():
428 print("Module {!r} already exists".format(modname), file=sys.stderr)
429 return False
430
431 print("Creating module {}".format(modulepath))
432
433 functions = (make_cmakelists, make_model, make_test, make_helper, make_examples, make_doc)
434
435 try:
436 modulepath.mkdir(parents=True)
437
438 success = all(func(modulepath, modname) for func in functions)
439
440 if not success:
441 raise ValueError("Generating module artifacts failed")
442
443 except Exception as e:
444 if modulepath.exists():
445 shutil.rmtree(modulepath)
446
447 print("Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
448
449 return False
450
451 return True
452
453
455 description = """Generate scaffolding for ns-3 modules
456
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.
461
462The following directory structure is generated under the contrib directory:
463<modname>
464 |-- CMakeLists.txt
465 |-- doc
466 |-- <modname>.rst
467 |-- examples
468 |-- <modname>-example.cc
469 |-- CMakeLists.txt
470 |-- helper
471 |-- <modname>-helper.cc
472 |-- <modname>-helper.h
473 |-- model
474 |-- <modname>.cc
475 |-- <modname>.h
476 |-- test
477 |-- <modname>-test-suite.cc
478
479
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.
490
491
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
502project directory.
503"""
504
505 epilog = """Examples:
506 %(prog)s module1
507 %(prog)s contrib/module1
508
509 Creates a new module named module1 under the contrib directory
510
511 %(prog)s src/module1
512
513 Creates a new module named module1 under the src directory
514
515 %(prog)s src/module1 contrib/module2, module3
516
517 Creates three modules, one under the src directory and two under the
518 contrib directory
519
520 %(prog)s --project myproject module1 module2
521
522 Creates two modules under contrib/myproject
523
524 %(prog)s --project myproject/sub_project module1 module2
525
526 Creates two modules under contrib/myproject/sub_project
527
528"""
529
530 formatter = argparse.RawDescriptionHelpFormatter
531
532 parser = argparse.ArgumentParser(
533 description=description, epilog=epilog, formatter_class=formatter
534 )
535
536 parser.add_argument(
537 "--project",
538 default="",
539 help=(
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."
543 ),
544 )
545
546 parser.add_argument(
547 "modnames",
548 nargs="+",
549 help=(
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 "
555 "directory."
556 ),
557 )
558
559 return parser
560
561
562def main(argv):
563 parser = create_argument_parser()
564
565 args = parser.parse_args(argv[1:])
566
567 project = args.project
568 modnames = args.modnames
569
570 base_path = Path.cwd()
571
572 src_path = base_path.joinpath("src")
573 contrib_path = base_path.joinpath("contrib")
574
575 for p in (src_path, contrib_path):
576 if not p.is_dir():
577 parser.error(
578 "Cannot find the directory '{}'.\nPlease run this "
579 "script from the top level of the ns3 directory".format(p)
580 )
581
582 #
583 # Error check the arguments
584 #
585
586 # Alphanumeric and '-' only
587 allowedRE = re.compile("^(\w|-)+$")
588
589 project_path = None
590
591 if project:
592 # project may be a path in the form a/b/c
593 # remove any leading or trailing path separators
594 project_path = Path(project)
595
596 if project_path.is_absolute():
597 # remove leading separator
598 project_path = project_path.relative_to(os.sep)
599
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_-].")
602 #
603 # Create each module, if it doesn't exist
604 #
605 modules = []
606 for name in modnames:
607 if name:
608 # remove any leading or trailing directory separators
609 name = name.strip(os.sep)
610
611 if not name:
612 # skip empty modules
613 continue
614
615 name_path = Path(name)
616
617 if len(name_path.parts) > 2:
618 print("Skipping {}: module name can not be a path".format(name))
619 continue
620
621 # default target directory is contrib
622 modpath = contrib_path
623
624 if name_path.parts[0] == "src":
625 if project:
626 parser.error(
627 "{}: Cannot specify src/ in a module name when --project option is used".format(
628 name
629 )
630 )
631
632 modpath = src_path
633
634 # create a new path without the src part
635 name_path = name_path.relative_to("src")
636
637 elif name_path.parts[0] == "contrib":
638 modpath = contrib_path
639
640 # create a new path without the contrib part
641 name_path = name_path.relative_to("contrib")
642
643 if project_path:
644 # if a project path was specified, that overrides other paths
645 # project paths are always relative to the contrib path
646 modpath = contrib_path.joinpath(project_path)
647
648 modname = name_path.parts[0]
649
650 if not allowedRE.match(modname):
651 print(
652 "Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(
653 modname
654 )
655 )
656 continue
657
658 modules.append((modpath, modname))
659
660 if all(make_module(*module) for module in modules):
661 print()
662 print("Successfully created new modules")
663 print("Run './ns3 configure' to include them in the build")
664
665 return 0
666
667
668if __name__ == "__main__":
669 return_value = 0
670 try:
671 return_value = main(sys.argv)
672 except Exception as e:
673 print("Exception: '{}'".format(e), file=sys.stderr)
674 return_value = 1
675
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)