A Discrete-Event Network Simulator
API
create-module.py
Go to the documentation of this file.
1 #! /usr/bin/env python3
2 import sys
3 import argparse
4 import os
5 import re
6 import shutil
7 
8 from pathlib import Path
9 
10 CMAKELISTS_TEMPLATE = '''\
11 check_include_file_cxx(stdint.h HAVE_STDINT_H)
12 if(HAVE_STDINT_H)
13  add_definitions(-DHAVE_STDINT_H)
14 endif()
15 
16 set(examples_as_tests_sources)
17 if(${{ENABLE_EXAMPLES}})
18  set(examples_as_tests_sources
19  #test/{MODULE}-examples-test-suite.cc
20  )
21 endif()
22 
23 build_lib(
24  LIBNAME {MODULE}
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}}
32 )
33 
34 '''
35 
36 
37 MODEL_CC_TEMPLATE = '''\
38 #include "{MODULE}.h"
39 
40 namespace ns3
41 {{
42 
43 /* ... */
44 
45 }}
46 '''
47 
48 
49 MODEL_H_TEMPLATE = '''\
50 #ifndef {INCLUDE_GUARD}
51 #define {INCLUDE_GUARD}
52 
53 // Add a doxygen group for this module.
54 // If you have more than one file, this should be in only one of them.
55 /**
56  * \defgroup {MODULE} Description of the {MODULE}
57  */
58 
59 namespace ns3
60 {{
61 
62 // Each class should be documented using Doxygen,
63 // and have an \ingroup {MODULE} directive
64 
65 /* ... */
66 
67 }}
68 
69 #endif /* {INCLUDE_GUARD} */
70 '''
71 
72 
73 HELPER_CC_TEMPLATE = '''\
74 #include "{MODULE}-helper.h"
75 
76 namespace ns3
77 {{
78 
79 /* ... */
80 
81 }}
82 '''
83 
84 
85 HELPER_H_TEMPLATE = '''\
86 #ifndef {INCLUDE_GUARD}
87 #define {INCLUDE_GUARD}
88 
89 #include "ns3/{MODULE}.h"
90 
91 namespace ns3
92 {{
93 
94 // Each class should be documented using Doxygen,
95 // and have an \ingroup {MODULE} directive
96 
97 /* ... */
98 
99 }}
100 
101 #endif /* {INCLUDE_GUARD} */
102 '''
103 
104 
105 EXAMPLES_CMAKELISTS_TEMPLATE = '''\
106 build_lib_example(
107  NAME {MODULE}-example
108  SOURCE_FILES {MODULE}-example.cc
109  LIBRARIES_TO_LINK ${{lib{MODULE}}}
110 )
111 
112 '''
113 
114 EXAMPLE_CC_TEMPLATE = '''\
115 #include "ns3/core-module.h"
116 #include "ns3/{MODULE}-helper.h"
117 
118 /**
119  * \\file
120  *
121  * Explain here what the example does.
122  */
123 
124 using namespace ns3;
125 
126 int
127 main(int argc, char* argv[])
128 {{
129  bool verbose = true;
130 
131  CommandLine cmd(__FILE__);
132  cmd.AddValue("verbose", "Tell application to log if true", verbose);
133 
134  cmd.Parse(argc, argv);
135 
136  /* ... */
137 
138  Simulator::Run();
139  Simulator::Destroy();
140  return 0;
141 }}
142 '''
143 
144 
145 TEST_CC_TEMPLATE = '''\
146 
147 // Include a header file from your module to test.
148 #include "ns3/{MODULE}.h"
149 
150 // An essential include is test.h
151 #include "ns3/test.h"
152 
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
155 using namespace ns3;
156 
157 // Add a doxygen group for tests.
158 // If you have more than one test, this should be in only one of them.
159 /**
160  * \defgroup {MODULE}-tests Tests for {MODULE}
161  * \ingroup {MODULE}
162  * \ingroup tests
163  */
164 
165 // This is an example TestCase.
166 /**
167  * \ingroup {MODULE}-tests
168  * Test case for feature 1
169  */
170 class {CAPITALIZED}TestCase1 : public TestCase
171 {{
172  public:
173  {CAPITALIZED}TestCase1();
174  virtual ~{CAPITALIZED}TestCase1();
175 
176  private:
177  void DoRun() override;
178 }};
179 
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)")
183 {{
184 }}
185 
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()
189 {{
190 }}
191 
192 //
193 // This method is the pure virtual method from class TestCase that every
194 // TestCase must implement
195 //
196 void
197 {CAPITALIZED}TestCase1::DoRun()
198 {{
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");
203 }}
204 
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
208 
209 /**
210  * \ingroup {MODULE}-tests
211  * TestSuite for module {MODULE}
212  */
213 class {CAPITALIZED}TestSuite : public TestSuite
214 {{
215  public:
216  {CAPITALIZED}TestSuite();
217 }};
218 
219 {CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
220  : TestSuite("{MODULE}", UNIT)
221 {{
222  // TestDuration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER
223  AddTestCase(new {CAPITALIZED}TestCase1, TestCase::QUICK);
224 }}
225 
226 // Do not forget to allocate an instance of this TestSuite
227 /**
228  * \ingroup {MODULE}-tests
229  * Static variable for test initialization
230  */
231 static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
232 '''
233 
234 
235 DOC_RST_TEMPLATE = '''Example Module Documentation
236 ----------------------------
237 
238 .. include:: replace.txt
239 .. highlight:: cpp
240 
241 .. heading hierarchy:
242  ------------- Chapter
243  ************* Section (#.#)
244  ============= Subsection (#.#.#)
245  ############# Paragraph (no number)
246 
247 This is a suggested outline for adding new module documentation to |ns3|.
248 See ``src/click/doc/click.rst`` for an example.
249 
250 The introductory paragraph is for describing what this code is trying to
251 model.
252 
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``.
256 
257 Model Description
258 *****************
259 
260 The source code for the new module lives in the directory ``{MODULE_DIR}``.
261 
262 Add here a basic description of what is being modeled.
263 
264 Design
265 ======
266 
267 Briefly describe the software design of the model and how it fits into
268 the existing ns-3 architecture.
269 
270 Scope and Limitations
271 =====================
272 
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.
275 
276 References
277 ==========
278 
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.
281 
282 Usage
283 *****
284 
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.
288 
289 Building New Module
290 ===================
291 
292 Include this subsection only if there are special build instructions or
293 platform limitations.
294 
295 Helpers
296 =======
297 
298 What helper API will users typically use? Describe it here.
299 
300 Attributes
301 ==========
302 
303 What classes hold attributes, and what are the key ones worth mentioning?
304 
305 Output
306 ======
307 
308 What kind of data does the model generate? What are the key trace
309 sources? What kind of logging output can be enabled?
310 
311 Advanced Usage
312 ==============
313 
314 Go into further details (such as using the API outside of the helpers)
315 in additional sections, as needed.
316 
317 Examples
318 ========
319 
320 What examples using this new code are available? Describe them here.
321 
322 Troubleshooting
323 ===============
324 
325 Add any tips for avoiding pitfalls, etc.
326 
327 Validation
328 **********
329 
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.
333 '''
334 
335 def 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") as f:
340  f.write(template.format(**kwargs))
341 
342 
343 def 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 
351 def 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,
361  MODULE=modname,
362  INCLUDE_GUARD=guard)
363 
364  return True
365 
366 
367 def make_test(moduledir, modname):
368  testpath = Path(moduledir, "test")
369  testpath.mkdir(parents=True)
370 
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)]))
376 
377  return True
378 
379 
380 def make_helper(moduledir, modname):
381  helperpath = Path(moduledir, "helper")
382  helperpath.mkdir(parents=True)
383 
384  srcfile_path = helperpath.joinpath(modname+'-helper').with_suffix('.cc')
385  create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
386 
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)
390 
391  return True
392 
393 
394 def make_examples(moduledir, modname):
395  examplespath = Path(moduledir, "examples")
396  examplespath.mkdir(parents=True)
397 
398  cmakelistspath = Path(examplespath, 'CMakeLists.txt')
399  create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
400 
401  examplesfile_path = examplespath.joinpath(modname+'-example').with_suffix('.cc')
402  create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
403 
404  return True
405 
406 
407 def make_doc(moduledir, modname):
408  docpath = Path(moduledir, "doc")
409  docpath.mkdir(parents=True)
410 
411  #the module_dir template parameter must be a relative path
412  #instead of an absolute path
413  mod_relpath = os.path.relpath(str(moduledir))
414 
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)
418 
419  return True
420 
421 
422 def make_module(modpath, modname):
423  modulepath = Path(modpath, modname)
424 
425  if modulepath.exists():
426  print("Module {!r} already exists".format(modname), file=sys.stderr)
427  return False
428 
429  print("Creating module {}".format(modulepath))
430 
431  functions = (make_cmakelists, make_model, make_test,
432  make_helper, make_examples, make_doc)
433 
434  try:
435  modulepath.mkdir(parents=True)
436 
437  success = all(func(modulepath, modname) for func in functions)
438 
439  if not success:
440  raise ValueError("Generating module artifacts failed")
441 
442  except Exception as e:
443  if modulepath.exists():
444  shutil.rmtree(modulepath)
445 
446  print("Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
447 
448  return False
449 
450  return True
451 
453  description = """Generate scaffolding for ns-3 modules
454 
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.
459 
460 The following directory structure is generated under the contrib directory:
461 <modname>
462  |-- CMakeLists.txt
463  |-- doc
464  |-- <modname>.rst
465  |-- examples
466  |-- <modname>-example.cc
467  |-- CMakeLists.txt
468  |-- helper
469  |-- <modname>-helper.cc
470  |-- <modname>-helper.h
471  |-- model
472  |-- <modname>.cc
473  |-- <modname>.h
474  |-- test
475  |-- <modname>-test-suite.cc
476 
477 
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.
488 
489 
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
500 project directory.
501 """
502 
503  epilog = """Examples:
504  %(prog)s module1
505  %(prog)s contrib/module1
506 
507  Creates a new module named module1 under the contrib directory
508 
509  %(prog)s src/module1
510 
511  Creates a new module named module1 under the src directory
512 
513  %(prog)s src/module1 contrib/module2, module3
514 
515  Creates three modules, one under the src directory and two under the
516  contrib directory
517 
518  %(prog)s --project myproject module1 module2
519 
520  Creates two modules under contrib/myproject
521 
522  %(prog)s --project myproject/sub_project module1 module2
523 
524  Creates two modules under contrib/myproject/sub_project
525 
526 """
527 
528  formatter = argparse.RawDescriptionHelpFormatter
529 
530  parser = argparse.ArgumentParser(description=description,
531  epilog=epilog,
532  formatter_class=formatter)
533 
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."))
538 
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 "
545  "directory."))
546 
547  return parser
548 
549 def main(argv):
550  parser = create_argument_parser()
551 
552  args = parser.parse_args(argv[1:])
553 
554  project = args.project
555  modnames = args.modnames
556 
557  base_path = Path.cwd()
558 
559  src_path = base_path.joinpath('src')
560  contrib_path = base_path.joinpath('contrib')
561 
562  for p in (src_path, contrib_path):
563  if not p.is_dir():
564  parser.error("Cannot find the directory '{}'.\nPlease run this "
565  "script from the top level of the ns3 directory".format(
566  p))
567 
568  #
569  # Error check the arguments
570  #
571 
572  # Alphanumeric and '-' only
573  allowedRE = re.compile('^(\w|-)+$')
574 
575  project_path = None
576 
577  if project:
578  #project may be a path in the form a/b/c
579  #remove any leading or trailing path separators
580  project_path = Path(project)
581 
582  if project_path.is_absolute():
583  #remove leading separator
584  project_path = project_path.relative_to(os.sep)
585 
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_-].')
588  #
589  # Create each module, if it doesn't exist
590  #
591  modules = []
592  for name in modnames:
593  if name:
594  #remove any leading or trailing directory separators
595  name = name.strip(os.sep)
596 
597  if not name:
598  #skip empty modules
599  continue
600 
601  name_path = Path(name)
602 
603  if len(name_path.parts) > 2:
604  print("Skipping {}: module name can not be a path".format(name))
605  continue
606 
607  #default target directory is contrib
608  modpath = contrib_path
609 
610  if name_path.parts[0] == 'src':
611  if project:
612  parser.error("{}: Cannot specify src/ in a module name when --project option is used".format(name))
613 
614  modpath = src_path
615 
616  #create a new path without the src part
617  name_path = name_path.relative_to('src')
618 
619  elif name_path.parts[0] == 'contrib':
620  modpath = contrib_path
621 
622  #create a new path without the contrib part
623  name_path = name_path.relative_to('contrib')
624 
625  if project_path:
626  #if a project path was specified, that overrides other paths
627  #project paths are always relative to the contrib path
628  modpath = contrib_path.joinpath(project_path)
629 
630  modname = name_path.parts[0]
631 
632  if not allowedRE.match(modname):
633  print("Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(modname))
634  continue
635 
636  modules.append((modpath, modname))
637 
638  if all(make_module(*module) for module in modules):
639  print()
640  print("Successfully created new modules")
641  print("Run './ns3 configure' to include them in the build")
642 
643  return 0
644 
645 if __name__ == '__main__':
646  return_value = 0
647  try:
648  return_value = main(sys.argv)
649  except Exception as e:
650  print("Exception: '{}'".format(e), file=sys.stderr)
651  return_value = 1
652 
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()