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