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