A Discrete-Event Network Simulator
API
create-module.py
Go to the documentation of this file.
1 #! /usr/bin/env python3
2 import argparse
3 import os
4 import re
5 import shutil
6 import sys
7 from pathlib import Path
8 
9 CMAKELISTS_TEMPLATE = """\
10 check_include_file_cxx(stdint.h HAVE_STDINT_H)
11 if(HAVE_STDINT_H)
12  add_definitions(-DHAVE_STDINT_H)
13 endif()
14 
15 set(examples_as_tests_sources)
16 if(${{ENABLE_EXAMPLES}})
17  set(examples_as_tests_sources
18  #test/{MODULE}-examples-test-suite.cc
19  )
20 endif()
21 
22 build_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 
35 MODEL_CC_TEMPLATE = """\
36 #include "{MODULE}.h"
37 
38 namespace ns3
39 {{
40 
41 /* ... */
42 
43 }}
44 """
45 
46 
47 MODEL_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 
57 namespace 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 
71 HELPER_CC_TEMPLATE = """\
72 #include "{MODULE}-helper.h"
73 
74 namespace ns3
75 {{
76 
77 /* ... */
78 
79 }}
80 """
81 
82 
83 HELPER_H_TEMPLATE = """\
84 #ifndef {INCLUDE_GUARD}
85 #define {INCLUDE_GUARD}
86 
87 #include "ns3/{MODULE}.h"
88 
89 namespace 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 
103 EXAMPLES_CMAKELISTS_TEMPLATE = """\
104 build_lib_example(
105  NAME {MODULE}-example
106  SOURCE_FILES {MODULE}-example.cc
107  LIBRARIES_TO_LINK ${{lib{MODULE}}}
108 )
109 """
110 
111 EXAMPLE_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 
121 using namespace ns3;
122 
123 int
124 main(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 
142 TEST_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
152 using 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  */
167 class {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 //
193 void
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  */
210 class {CAPITALIZED}TestSuite : public TestSuite
211 {{
212  public:
213  {CAPITALIZED}TestSuite();
214 }};
215 
216 {CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
217  : TestSuite("{MODULE}", UNIT)
218 {{
219  // TestDuration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER
220  AddTestCase(new {CAPITALIZED}TestCase1, TestCase::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  */
228 static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
229 """
230 
231 
232 DOC_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 
244 This is a suggested outline for adding new module documentation to |ns3|.
245 See ``src/click/doc/click.rst`` for an example.
246 
247 The introductory paragraph is for describing what this code is trying to
248 model.
249 
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``.
253 
254 Model Description
255 *****************
256 
257 The source code for the new module lives in the directory ``{MODULE_DIR}``.
258 
259 Add here a basic description of what is being modeled.
260 
261 Design
262 ======
263 
264 Briefly describe the software design of the model and how it fits into
265 the existing ns-3 architecture.
266 
267 Scope and Limitations
268 =====================
269 
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.
272 
273 References
274 ==========
275 
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.
278 
279 Usage
280 *****
281 
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.
285 
286 Building New Module
287 ===================
288 
289 Include this subsection only if there are special build instructions or
290 platform limitations.
291 
292 Helpers
293 =======
294 
295 What helper API will users typically use? Describe it here.
296 
297 Attributes
298 ==========
299 
300 What classes hold attributes, and what are the key ones worth mentioning?
301 
302 Output
303 ======
304 
305 What kind of data does the model generate? What are the key trace
306 sources? What kind of logging output can be enabled?
307 
308 Advanced Usage
309 ==============
310 
311 Go into further details (such as using the API outside of the helpers)
312 in additional sections, as needed.
313 
314 Examples
315 ========
316 
317 What examples using this new code are available? Describe them here.
318 
319 Troubleshooting
320 ===============
321 
322 Add any tips for avoiding pitfalls, etc.
323 
324 Validation
325 **********
326 
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.
330 """
331 
332 
333 def 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 
341 def 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 
349 def 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 
363 def 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("-")
369  create_file(
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 
382 def 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 
396 def 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 
409 def 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 
424 def 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 
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.
461 
462 The 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
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.
490 
491 
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
502 project 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 
562 def 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 
668 if __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)
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()