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